Loading...
Loading...
Receive real-time HTTP notifications when events happen in your account. Subscribe to the events you care about and we will send POST requests to your endpoint with the event payload.
Webhooks allow your application to receive push notifications when events occur in our system. Instead of continuously polling our API for changes, you register an endpoint URL and we deliver events to you in real time.
Each webhook delivery is an HTTP POST request containing a JSON payload that describes the event. Every request includes cryptographic signatures so you can verify the payload originated from us and has not been tampered with.
Events are organized into categories: User, Auth, and API Key. You choose which events to subscribe to when creating a webhook endpoint.
Setting up webhooks takes just a few steps:
Create an endpoint
Set up an HTTP endpoint on your server that accepts POST requests. The endpoint must return a 2xx status code within 30 seconds to acknowledge receipt.
Register in the dashboard
Navigate to Settings → Webhooks in your dashboard and add a new webhook. Enter your endpoint URL and select the events you want to receive.
Save your signing secret
When you create a webhook, we generate a unique signing secret. Store it securely -- you will use it to verify that incoming requests are genuine.
Verify and process
When a webhook arrives, verify the signature using your signing secret, then process the event payload. See the Verifying Signatures section below for implementation details.
Below is the complete list of events you can subscribe to. Expand any event to view its JSON Schema or an example payload.
user.createdFired when a new user account is created.
user.updatedFired when a user profile is updated.
user.deletedFired when a user account is permanently deleted.
user.signed_inFired when a user signs in to their account.
user.signed_outFired when a user signs out of their account.
api_key.createdFired when a new API key is created.
api_key.revokedFired when an API key is revoked.
Every webhook delivery includes these HTTP headers:
| Header | Description | Example |
|---|---|---|
webhook-id | Unique identifier for this delivery. Use as an idempotency key. | msg_2xAu8K0QmL... |
webhook-timestamp | Unix timestamp (seconds) of when the webhook was sent. | 1705312800 |
webhook-signature | HMAC-SHA256 signature in the format v1,<base64>. | v1,K7gNU3sdo+O... |
content-type | Always application/json. | application/json |
Every webhook is signed using your endpoint's unique secret. You should always verify signatures before processing a webhook to ensure it was sent by us and has not been modified.
The signed content is constructed as:
{webhookId}.{timestamp}.{body}Where body is the raw JSON request body as a string.
The signature is then computed as:
v1,{base64(HMAC-SHA256(secret, signedContent))}Additionally, check the webhook-timestamp header and reject requests older than 5 minutes to prevent replay attacks.
import crypto from "crypto";
function verifyWebhookSignature(
rawBody: string,
headers: Record<string, string>,
secret: string,
): boolean {
const webhookId = headers["webhook-id"];
const timestamp = headers["webhook-timestamp"];
const signature = headers["webhook-signature"];
// Guard against replay attacks (5 minute tolerance)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - Number(timestamp)) > 300) {
return false;
}
// Build the signed content
const signedContent = `${webhookId}.${timestamp}.${rawBody}`;
// Compute the expected signature
const expected = crypto
.createHmac("sha256", secret)
.update(signedContent)
.digest("base64");
// Compare with timing-safe equality
const received = signature.replace("v1,", "");
return crypto.timingSafeEqual(
Buffer.from(received, "base64"),
Buffer.from(expected, "base64"),
);
}If your endpoint does not return a 2xx status code within 30 seconds, we treat the delivery as failed and retry with exponential backoff. Each webhook is attempted up to 5 times.
| Attempt | Delay | Time Since First Attempt |
|---|---|---|
| 1 | 1 minute | 1 minute |
| 2 | 5 minutes | 6 minutes |
| 3 | 15 minutes | 21 minutes |
| 4 | 1 hour | 1 hour 21 minutes |
| 5 | 4 hours | 5 hours 21 minutes |
Auto-disable policy
If an endpoint fails 10 consecutive deliveries, it is automatically disabled. You will receive an email notification and can re-enable it from the dashboard after fixing the issue.
Respond quickly with 2xx
Return a 200 or 202 status as soon as possible. Process the webhook payload asynchronously (e.g., in a background job) to avoid timeouts.
Use webhook-id for idempotency
Store the webhook-id header value and check it before processing. This prevents duplicate handling if the same event is delivered more than once due to retries.
Always verify signatures
Never process a webhook without verifying its signature. This ensures the request came from us and has not been tampered with in transit.
Handle retries gracefully
Design your webhook handler to be idempotent. The same event may be delivered multiple times, and your handler should produce the same result regardless.
Log received webhooks
Keep a log of all received webhooks with their headers and payloads. This makes debugging and auditing significantly easier.
We provide several ways to test your webhook integration before going live.
From the Webhooks page in your dashboard, click the "Send Test" button on any endpoint. Choose an event type and we will send a test payload with example data to your endpoint. The delivery result appears in real time so you can verify your handler is working correctly.
Use webhook.site to generate a temporary public URL. Register it as your webhook endpoint to inspect incoming payloads, headers, and response codes without writing any code.
Run ngrok http 3000 to expose your local development server to the internet. Use the generated URL as your webhook endpoint to test your handler against real webhook deliveries.