# Webhooks
Webhooks allow you to subscribe to a stream's events and get notified of them in real time. You can then use this to trigger ci apps, automation workflows, and more! The world is your oyster. Let's have a quick look at how to work with them.
# Setting Up A Stream Webhook
On the sidebar of a stream, you'll find a "⚙ Settings" button. Click this and head to the "Webhooks" tab to add webhooks to your stream.
There are a few different fields to fill out when setting up a webhook:
- URL: The URL that will be sent
POST
requests when your webhook is triggered - Description: Optional identifying text (will be shown when viewing all your webhooks in a list)
- Secret: An optional token that will help you verity that requests you receive are actually coming from the Speckle Server. Requests will be secured with an
X-WEBHOOK-SIGNATURE
which you can verify upon receiving. - Triggers: The events you want to trigger the webhook
- Enabled: Toggle the webhook on and off
You can of course also manage webhooks via the GraphQL API using the webhookCreate
, webhookUpdate
, and webhookDelete
mutations. There is a webhooks
field on the stream
schema which you can query to get the webhooks for a stream and the history
of previous requests.
# Receiving the Webhook
When the condition defined in the webhook definition is met, a POST
request will be made to the provided URL.
The POST request will contain 1 string parameter, called payload
, that is the JSON representation of the webhook payload.
Important note on the JSON encoding
The way of passing the POST parameter is application/json
, and the parameter is again encoded as JSON for compatibility with various frameworks that don't handle nested json properties.
So the actual POST request body might look something like:
{"payload":"{\"streamId\":\"a35c7a8bdd\",\"userId\":\"bc4e472126\",\"activityMessage\":\"Stream metadata changed\", ...
To ensure that only the configured Speckle Server will successfully call your publicly available webhook server, there is the X-WEBHOOK-SIGNATURE
header that can be used to validate the authenticity of the call.
It is the sha256 HMAC on the payload json string, using the shared secret configured on the webhook.
Example python code to validate the signature:
import hmac
# ...
expected_signature = hmac.new(SHARED_SECRET.encode(), payload_json.encode(), 'sha256').hexdigest()
if not hmac.compare_digest(expected_signature, SIGNATURE_FROM_HEADER):
print('Ignoring request with invalid signature')
return
2
3
4
5
6
7
And javascript (node):
const * as crypto from 'crypto'
const secret = 'YOUR_SECRET_HERE'
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(request.body.payload)
.digest('hex')
const signatureFromHeader = request.headers['x-webhook-signature']
if (signatureFromHeader != expectedSignature) response.status(401).send('Ignoring request with invalid signature')
2
3
4
5
6
7
8
9
10
# Reporting Success
The endpoint code should return a HTTP response status header. The server recognises statuses 200 OK
to 204 NO_CONTENT
as a successful execution. Alternatively, your endpoint can respond with other statuses in its execution. The webhook execution log shows these as error with additional payload data you supply to diagnose issues.
Go back to the main "Webhooks" tab to view all the webhooks on the stream. Click on a webhook to open the edit menu. The symbol to the left to the description indicates the status of the last webhook request; hover over it to get more information. Here, the green ✔ indicates the last request was successfully sent. The grey circle with an ❕ indicates no requests have been sent yet (you've likely just created the webhook and none of the triggers have happened yet). If the last request failed, you will see a red ❌ which you can hover over to see the error message.
# Avoid Looping
⚠️ Warning: Any time you make a commit in response to a triggered stream webhook event, you are at risk of creating an infinite loop on both your handling code and your speckle server. Use caution and ensure that you safely exit your handling code when no change is needed. |
---|
If you happen to have triggered an infinite loop, either make your endpoint inaccessible, deploy new code or delete the webhook on the server stream.
# The Webhook Payload
Here is an example of what a webhook payload looks like. The structure will always be the same, except for the ["event"]["data"]
which will change depending on the event that triggered the request.
{
"streamId": "48364aff6b",
"userId": "1234abcdef",
"activityMessage": "Stream metadata changed",
"event": {
"event_name": "stream_update",
"data": {
"old": {
"id": "48364aff6b",
"name": "Golden Nugget \ud83c\udf3b",
"description": "revit model",
"isPublic": false,
"clonedFrom": null,
"createdAt": "2021-06-29T09:09:02.993Z",
"updatedAt": "2021-07-23T14:17:18.660Z"
},
"new": {
"id": "48364aff6b",
"name": "Golden Nugget \ud83c\udf3b",
"description": "revit model for cool and
good times \ud83c\udf1e",
"isPublic": true
}
}
},
"server": { ... },
"stream": { ... },
"user": { ... },
"webhook": { ... }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
There are a few key fields that will always be present in the payload including:
streamId
: the id of the stream the webhook was added touserId
: the id of the user who triggered the eventactivityMessage
: a human readable summary about what event has occurredevent
: the event that triggered the message; it will always include theevent_name
anddata
, though the structure of the eventdata
will vary depending on the event typeserver
: details about the server that sent the message including itsname
,description
, andcanonicalUrl
stream
: details about the stream the event was triggered fromuser
: details about the user who caused the eventwebhook
: details about the webhook itself