This is in beta. This is only available to Enterprise customers.
Webhooks are how services notify each other of events.
At their core they are just a POST request to a pre-determined endpoint.
The endpoint can be whatever you want, and you can just add them from the UI.
You normally use one endpoint per service, and that endpoint listens to all of the event types.
The way to indicate that a webhook has been processed is by returning a 2xx (status code 200-299) response to the webhook message within a reasonable time-frame (15s).
It’s also important to disable CSRF protection for this endpoint if the framework you use enables them by default.
Another important aspect of handling webhooks is to verify the signature and timestamp when processing them.
Once you’ve added an endpoint, you’ll want to make sure its working.
The “Testing” tab lets you send test events to your endpoint.
After sending an example event, you can click into the message to view the message payload, all of the message attempts, and whether it succeeded or failed.
The rate limit is defined as a limit for the number of messages per second to send to the endpoint. After the limit is reached, requests will get throttled so to keep a consistent rate under the limit.
Due to the nature of distributed systems the actual rate of messages can sometimes be slightly above the enforce rate limit. So for example, if you set a rate limit of 1,000 per seconds, an endpoint may potentially get messages at a rate of 1,050 or even higher.
You can set the rate limit on each of the endpoints you create.
We attempt to deliver each webhook message based on a retry schedule with exponential backoff.
Each message is attempted based on the following schedule, where each period is started following the failure of the preceding attempt:
Immediately
5 seconds
5 minutes
30 minutes
2 hours
5 hours
10 hours
10 hours (in addition to the previous)
For example, an attempt that fails three times before eventually succeeding will be delivered roughly 35 minutes and 5 seconds following the first attempt.
If an endpoint is removed or disabled delivery attempts to the endpoint will be disabled as well.
The way to indicate that a webhook has been processed is by returning a 2xx (status code 200-299) response to the webhook message within a reasonable time-frame (15s). Any other status code, including 3xx redirects are treated as failures.
After the conclusion of the above attempts the message will be marked as Failed for this endpoint, and you will get a webhook of type message.attempt.exhausted notifying you of this error.
Transformations are a powerful feature that allows the modification of the received webhook data in-flight. When you enable Transformations, you can write JavaScript code on the endpoints that can change a webhook’s HTTP method, target URL, and body payload.
You can enable Transformations in the Advanced tab of the endpoint configuration.
We expect a Transformation to declare a function named handler. We will pass a WebhookObject to this function as its only argument, and expect the function to always return a WebhookObject.
WebhookObject is a JSON object containing 4 properties:
method, a string representing the HTTP method the webhook will be sent with. It is always POST by default, and its only valid values are POST or PUT
url, a string representing the endpoint’s URL. It can be changed to any valid URL.
payload, which contains the webhook’s payload as a JSON object. It can be changed as needed.
cancel, a Boolean which controls whether or not to cancel the dispatch of a webhook. This value defaults to false. Note that canceled messages appear as successful dispatches.
The Transformation will only work if the handler function returns the modified WebhookObject.
Suppose that sometimes, you want to redirect webhooks to a custom URL instead of the endpoint’s defined URL. You only want to do this redirect if a custom URL is present in the webhook payload. You can write a transformation like this:
Copy
Ask AI
function handler(webhook) { if (webhook.payload.customUrl) { webhook.url = webhook.payload.customUrl; } return webhook;}
Great, the webhook is redirected to the custom URL if the customUrl property exists on the payload. Otherwise, it is sent to the endpoint’s defined URL.
Webhook signatures let you verify that webhook messages are actually sent by us and not a malicious actor.
Each webhook call includes three headers with additional information that are used for verification:
webhook-id: the unique message identifier for the webhook message. This identifier is unique across all messages, but will be the same when the same webhook is being resent (e.g. due to a previous failure).
The content to sign is composed by concatenating the id, timestamp and payload, separated by the full-stop character (.). In code, it will look something like:
Where body is the raw body of the request. The signature is sensitive to any changes, so even a small change in the body will cause the signature to be completely different. This means that you should not change the body in any way before verifying.
So to calculate the expected signature, you should HMAC the signed_content from above using the base64 portion of your signing secret (this is the part after the whsec_ prefix) as the key.
For example, given the secret whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw you will want to use MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw.
For example, this is how you can calculate the signature in Node.js:
Copy
Ask AI
const crypto = require('crypto');const signedContent = `${webhook_id}.${webhook_timestamp}.${body}`;const secret = "whsec_5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH";// Need to base64 decode the secretconst secretBytes = Buffer.from(secret.split('_')[1], "base64");const signature = crypto .createHmac('sha256', secretBytes) .update(signedContent) .digest('base64');console.log(signature);
This generated signature should match one of the ones sent in the webhook-signature header.
The webhook-signature header is composed of a list of space delimited signatures and their corresponding version identifiers. The signature list is most commonly of length one. Though there could be any number of signatures. For example:
As mentioned above, we also send the timestamp of the attempt in the webhook-timestamp header. You should compare this timestamp against your system timestamp and make sure it’s within your tolerance in order to prevent timestamp attacks.
Here is an example you can use to verify you implemented everything correctly. Please note that this may fail verification due to the timestamp being old.
Copy
Ask AI
secret = 'whsec_plJ3nmyCDGBKInavdOK15jsl';payload = '{"event_type":"ping","data":{"success":true}}';msg_id = 'msg_loFOjxBNrRLzqYUf';timestamp = '1731705121';// Would generate the following signature:signature = 'v1,rAvfW3dJ/X/qxhsaXPOyyCGmRKsaKWcsNccKXlIktD0=';
Additionally, you can use the webhook simulation tool to generate as many examples as you need.
We sign all webhooks in order to ensure the security and authenticity of all of the webhooks being sent.
This security mechanism is already sufficient (and better) than other methods such as HTTP Basic Authentication, and using an authentication token. However, some systems and IT departments have varying requirements for any HTTP request hitting their services (including webhooks), so we’ve built in support for these additional authentication modes.
HTTP Basic Authentication (Basic Auth), is a common way of sending a server a pair of username and password, or more often a username and auth token. While there are different ways of passing these credentials, the simplest and most common way is by including it as part of the URL.
You can add it to the URL by prefixing the URL with the username and password (or token) and the @ symbol as such:
Some services require specific headers to be passed in order to be processed by their load balancers or application servers. These services often require a specific authentication token passed in the Authorization header, but sometimes there could also be different headers and values.
You can set custom headers using the Advanced tab in the endpoint configuration.
Many organizations have strict firewall rules for which IPs are allowed to send traffic to their systems. While this is not a very strong security mechanism on its own, it’s often useful when used in conjunction with other methods (such as webhook signatures).
We only send webhooks requests from a specific set of IPs as detailed below:
There are some common reasons why your webhook endpoint is failing:
Not using the raw payload body
This is the most common issue. When generating the signed content, we use the raw string body of the message payload.
If you convert JSON payloads into strings using methods like stringify, different implementations may produce different string representations of the JSON object, which can lead to discrepancies when verifying the signature. It’s crucial to verify the payload exactly as it was sent, byte-for-byte or string-for-string, to ensure accurate verification.
Missing the secret key
From time to time we see people using the wrong secret key. Remember that keys are unique to endpoints.
Sending the wrong response codes
When we receive a response with a 2xx status code, we interpret that as a successful delivery even if you indicate a failure in the response payload. Make sure to use the right response status codes so we know when message are supposed to succeed vs fail.
Responses timing out
We will consider any message that fails to send a response within 15 seconds a failed message. If your endpoint is also processing complicated workflows, it may timeout and result in failed messages.
We suggest having your endpoint simply receive the message and add it to a queue to be processed asynchronously so you can respond promptly and avoiding getting timed out.
If all attempts to a specific endpoint fail for a period of 5 days, the endpoint will be disabled. To re-enable a disabled endpoint, go to the webhook dashboard, find the endpoint from the list and select “Enable Endpoint”.
If your service has downtime or if your endpoint was misconfigured, you probably want to recover any messages that failed during the downtime.
If you want to replay a single event, you can find the message from the UI and click the options menu next to any of the attempts.
From there, click “resend” to have the same message send to your endpoint again.
If you need to recover from a service outage and want to replay all the events since a given time, you can do so from the Endpoint page. On an endpoint’s details page, click “Options > Recover Failed Messages”.
From there, you can choose a time window to recover from.
For a more granular recovery - for example, if you know the exact timestamp that you want to recover from - you can click the options menu on any message from the endpoint page.
From there, you can click “Replay…” and choose to “Replay all failed messages since this time.”