# Webhooks Webhooks allow you to receive real-time notifications when events occur in your Understory account. Instead of polling the API for changes, webhooks push updates to your application as they happen. ## About thin events Understory webhooks follow a notification-based approach, sometimes called "thin events". Rather than including the complete resource data in each webhook payload, we send lightweight notifications containing only the resource identifiers. This design ensures you always work with the most current data. When you receive a webhook, fetch the latest resource state via the API. This avoids issues where webhook payloads might be stale by the time you process them. For example, when a booking is created, you receive the `booking_id`, `event_id`, `experience_id`, and `host_id`. Use these identifiers to fetch the complete booking details from the [Bookings API](/apis/booking). ## Event envelope All webhook events follow this general structure: ```json { "id": "62564ee5-1a3d-5671-b4df-ca3bab46165a", "type": "v1.booking.created", "timestamp": "2026-01-29T19:29:01.856844592Z", "payload": { "booking_id": "bkg_abc123", "host_id": "host_xyz", "experience_id": "exp_456", "event_id": "evt_789" } } ``` | Field | Description | | --- | --- | | `id` | Unique identifier for this event. Use for logging and debugging. | | `type` | The event type, indicating what occurred (e.g., `v1.booking.created`). | | `timestamp` | ISO 8601 timestamp with nanosecond precision of when the event occurred. | | `payload` | Event-specific data containing resource identifiers. | ## Security Webhook security is critical. Always verify that incoming webhooks are genuinely from Understory before processing them. ### Signature verification Every webhook request includes three headers for verification: | Header | Description | | --- | --- | | `webhook-id` | Unique identifier for this webhook message. The same ID is used when retrying failed deliveries. Use this for deduplication. | | `webhook-timestamp` | Unix timestamp (seconds since epoch) of when the webhook was sent. | | `webhook-signature` | HMAC-SHA256 signature in the format `v1,` for verifying authenticity. | The signature is computed over the string: `{webhook-id}.{webhook-timestamp}.{body}` When you create a webhook subscription, you receive a `secret` in the response. Store this securely as it cannot be retrieved again. Use this secret to verify incoming webhooks. Webhook signature verification import { createHmac, timingSafeEqual } from 'node:crypto'; // These values come from the incoming webhook request const webhookId = 'msg_loFOjxBNrRLzqYUf'; const webhookTimestamp = '1731705121'; const webhookSignature = 'v1,rAvfW3dJ/X/qxhsaXPOyyCGmRKsaKWcsNccKXlIktD0='; const body = '{"event_type":"ping","data":{"success":true}}'; // This is the secret you receive when registering the webhook subscription const secret = 'whsec_plJ3nmyCDGBKInavdOK15jsl'; // Build the signed content string const signedContent = `${webhookId}.${webhookTimestamp}.${body}`; // Base64 decode the secret (remove the "whsec_" prefix first) const secretBytes = Buffer.from(secret.split('_')[1], 'base64'); // Compute the expected signature const expectedSignature = createHmac('sha256', secretBytes) .update(signedContent) .digest('base64'); // Extract the signature from the header (format: "v1,") const actualSignature = webhookSignature.split(',')[1]; // Compare signatures using constant-time comparison to prevent timing attacks const valid = timingSafeEqual( Buffer.from(expectedSignature), Buffer.from(actualSignature) ); console.log('Signature valid:', valid); The example above includes test values you can use to verify your implementation. Security Always verify webhook signatures before processing. Never trust webhook data without verification, as attackers could send forged requests to your endpoint. ### Replay attack prevention An attacker could capture a valid webhook and replay it later. Prevent this by validating the `webhook-timestamp` header. Reject webhooks with timestamps that are too old. A tolerance of 5 minutes is recommended. ## Retries If your endpoint fails to respond with a `2xx` status code within 15 seconds, the webhook is retried using an exponential backoff schedule: - Immediately - 5 seconds - 5 minutes - 30 minutes - 2 hours - 5 hours - 10 hours - 10 hours After all retry attempts are exhausted, the message is marked as failed. Failing endpoints If all delivery attempts to an endpoint fail for a period of 5 days, the endpoint is automatically disabled. The 5-day period starts after multiple failures occur within a 24-hour span. ## Best practices Follow these recommendations for a reliable webhook integration: - **Verify signatures**: Always validate the webhook signature before processing any data. - **Check timestamps**: Reject webhooks older than 5 minutes to prevent replay attacks. - **Use idempotency**: Track processed `webhook-id` values to handle duplicate deliveries gracefully. The same webhook may be sent multiple times if your endpoint returns an error. - **Respond quickly**: Return a `2xx` status code within a few seconds. Process webhook data asynchronously to avoid timeouts. - **Fetch fresh data**: After receiving a notification, fetch the current resource state via the API. The webhook payload contains identifiers, not the full resource. - **Use HTTPS**: Only configure HTTPS endpoints to protect data in transit. ## Testing webhooks Before building your webhook endpoint, you can inspect webhook payloads using testing tools. For example, [Svix Play](https://play.svix.com) is a free webhook testing tool that requires no signup. It provides an instant HTTPS endpoint you can use to receive and inspect webhooks. 1. Visit [play.svix.com](https://play.svix.com) to get a unique endpoint URL 2. Create a webhook subscription in Understory using that URL 3. Trigger events (e.g., create a booking) and inspect the incoming webhooks in your browser 4. View the full request including headers, body, and signature This is useful for understanding the webhook format before writing any code. ## See also - [Webhook API Reference](/apis/webhook) - [Authentication](/docs/usage/authentication) - [Testing](/docs/usage/testing)