--- title: Webhooks | API Docs description: 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](/guides/platform/debugging/index.md). ## 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](/guides/webhooks/subscriptions/index.md) for the full subscription lifecycle — create, list, retrieve, update, delete, and phone-number filtering. ## 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 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 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) ## 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: 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](/guides/webhooks/events/index.md). The [Webhook Events API Reference](/api/resources/webhooks/index.md) has the complete schemas.