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.
Setting up webhooks
Section titled “Setting up webhooks”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 versioning
Section titled “Webhook versioning”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 created | Webhook version |
|---|---|
| Before 2026-02-03 | 2025-01-01 |
| 2026-02-03 or later | 2026-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.
Webhook headers
Section titled “Webhook headers”All webhook requests include these headers:
| Header | Description |
|---|---|
X-Webhook-Signature | HMAC-SHA256 signature (hex-encoded) |
X-Webhook-Timestamp | Unix timestamp (seconds) when the webhook was sent |
X-Webhook-Event | Event type (e.g., message.received) |
X-Webhook-Subscription-ID | Your webhook subscription ID |
Signature verification
Section titled “Signature verification”All webhooks are signed with HMAC-SHA256 using your webhook signing secret. The signature is computed over {timestamp}.{payload}, not just the payload.
- Extract the
X-Webhook-TimestampandX-Webhook-Signatureheaders - Get the raw request body (exact bytes, not parsed/re-serialized JSON)
- Compute:
HMAC-SHA256(secret, "{timestamp}.{raw_body}") - 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 exampleapp.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 hmacimport 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)
Delivery guarantees
Section titled “Delivery guarantees”| Guarantee | Value |
|---|---|
| Response timeout | 10 seconds |
| Retry attempts | 10 per endpoint |
| Retry backoff | Exponential with jitter, capped at 10 minutes |
| Total retry window | ~25 minutes |
| Delivery model | At-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:
- Return
200quickly — process asynchronously if needed - Verify the HMAC signature
- Deduplicate using
event_id - 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.