Skip to content
V2 (Legacy) API ReferenceGet started
Webhooks

Webhooks

Real-time event notifications for messages, reactions, chats, and more.

Webhooks deliver real-time notifications to your server when events occur in your messaging infrastructure. For troubleshooting webhook issues, see Debugging.

Create a webhook subscription to start receiving events. Each subscription targets a URL you own, filters to the events you care about, and returns a signing secret you use to verify inbound requests. See Webhook Subscriptions for the full subscription lifecycle — create, list, retrieve, update, delete, and phone-number filtering.

Webhook payloads are versioned using dates. Specify a version by adding ?version=YYYY-MM-DD to your subscription URL:

https://your-server.com/webhook?version=2026-02-03
Subscription createdWebhook version
Before 2026-02-032025-01-01
2026-02-03 or later2026-02-03

If no version is specified, the subscription uses the latest available version at creation time.

Tip: Always specify a version explicitly to avoid unexpected payload format changes.

All webhook requests include these headers:

HeaderDescription
X-Webhook-SignatureHMAC-SHA256 signature (hex-encoded)
X-Webhook-TimestampUnix timestamp (seconds) when the webhook was sent
X-Webhook-EventEvent type (e.g., message.received)
X-Webhook-Subscription-IDYour webhook subscription ID

All webhooks are signed with HMAC-SHA256 using your webhook signing secret. The signature is computed over {timestamp}.{payload}, not just the payload.

  1. Extract the X-Webhook-Timestamp and X-Webhook-Signature headers
  2. Get the raw request body (exact bytes, not parsed/re-serialized JSON)
  3. Compute: HMAC-SHA256(secret, "{timestamp}.{raw_body}")
  4. Compare your computed signature (hex-encoded) with X-Webhook-Signature

Node.js:

const crypto = require('crypto');
function verifyWebhookSignature(secret, rawBody, timestamp, signature) {
const signedData = `${timestamp}.${rawBody}`;
const expected = crypto.createHmac('sha256', secret).update(signedData).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(signature, 'hex'));
}
// Express middleware example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.headers['x-webhook-timestamp'];
const signature = req.headers['x-webhook-signature'];
const rawBody = req.body.toString();
if (!verifyWebhookSignature(WEBHOOK_SECRET, rawBody, timestamp, signature)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(rawBody);
// Process event asynchronously
processEvent(event);
res.status(200).send('OK');
});

Python:

import hmac
import hashlib
def verify_webhook_signature(secret, raw_body, timestamp, signature):
message = f"{timestamp}.{raw_body}"
expected = hmac.new(
secret.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)

Warning: Common mistakes that break verification:

  • Signing just the payload without prepending {timestamp}.
  • Using JSON.stringify(parsedBody) instead of the raw body — this changes key order or whitespace
  • Not using constant-time comparison (vulnerable to timing attacks)
GuaranteeValue
Response timeout10 seconds
Retry attempts10 per endpoint
Retry backoffExponential with jitter, capped at 10 minutes
Total retry window~25 minutes
Delivery modelAt-least-once (duplicates possible)

Retried: HTTP 5xx, HTTP 429, connection timeout, connection refused. Not retried: HTTP 4xx (except 429), DNS failures, invalid hostnames.

Your endpoint should:

  1. Return 200 quickly — process asynchronously if needed
  2. Verify the HMAC signature
  3. Deduplicate using event_id
  4. Be idempotent

For the full list of event types, the shared envelope, and a representative payload per category, see Webhook Events. The Webhook Events API Reference has the complete schemas.