Register an endpoint
POST /webhooks (scope manage:webhooks) registers an endpoint, and your channel may have up to five:
urlis a public HTTPS address on port 443. A private, reserved, loopback or link-local target is refused (url_not_allowed), and redirects are never followed.eventslists up to 20 event types or patterns (*,property.*,availability.updated), each covered by your key’s scopes.- The answer contains the endpoint’s signing
secret(whsec_…), shown once: store it now.
POST /webhooks/{webhook_id}/test sends a signed ping and reports the outcome. GET /webhooks/{webhook_id}/deliveries lists the deliveries made to an endpoint, newest first, and GET /webhooks/{webhook_id}/deliveries/{delivery_id} every attempt of one; deliveries are kept for 35 days.
Verify the signature
Each notification is aPOST with three headers:
To verify it:
- Reject a timestamp more than 5 minutes away from your clock, so a captured notification cannot be replayed later.
- Compute the HMAC-SHA256 of
id.timestamp.bodyover the raw body exactly as received, before any JSON parsing. The key is the secret’s base64 part afterwhsec_, decoded. - Accept the notification if any
v1,value matches, compared in constant time. While a secret is being rotated, two signatures are sent.
Apply a notification
The body is the event:- Delivery is at least once, in no particular order. Deduplicate on
webhook-id. - Read the resource the event names, and apply it only when its
versionis higher than the one you hold. - An
availability.updatedevent names a property and a range of nights (from,toexclusive) and carries no version: always re-read that range. - Answer with any
2xxwithin 10 seconds, and do the work afterwards.
Retries and suspension
A non-2xx answer or a timeout is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then every 12 hours, each with ±20 % jitter, for 3 days. After that the endpoint becomes inactive with suspended_reason: failing, and your technical contact is e-mailed. A 410 Gone answer makes it inactive at once (gone). A redirect is never followed, and counts as not delivered.
Nothing is delivered to an inactive endpoint. POST /webhooks/{webhook_id}/enable enables it again once a signed ping to it succeeds. Nothing is replayed: recover what it missed from GET /events (Synchronisation).
Rotate a secret
POST /webhooks/{webhook_id}/rotate-secret issues a new secret, shown once. The previous one keeps signing beside it for 24 hours (previous_secret_expires_at), so both signatures arrive and a verifier holding either secret accepts the notification; send "expire_immediately": true when the old secret has leaked. Deleting an endpoint cancels its pending deliveries and destroys its secrets.