Booking webhooks push committed lifecycle changes to an HTTPS endpoint so an
integration does not have to poll every booking continuously. Delivery is at
least once. Deduplicate with the stable event id; retries and manual
redelivery keep the same event ID and exact body bytes.
All management operations require distribution:booking. Endpoints and
secrets belong to the authenticated Partner Account. A subscription may narrow
delivery to one profile and/or property, but it does not change ownership,
authentication, booking visibility, or reconciliation scope.
Register and verify an endpoint
Create an endpoint with POST /api/v1/webhook-endpoints. The URL must use HTTPS
on port 443 and resolve only to public addresses. Redirects are not followed.
An account can own at most five live endpoints, each with 1 to 50 unique
subscriptions.
Creation requires Idempotency-Key and returns state: pending_verification. It also returns a whsec_... signing secret. Store it in
a secret manager immediately. Plaintext is never returned by normal reads. An
exact request replay with the same idempotency key reproduces the same response
for 24 hours; reusing the key with a different request returns
IDEMPOTENCY_KEY_CONFLICT.
Call POST /webhook-endpoints/{id}/verify with another idempotency key. Adrasis
queues a signed webhook.endpoint.verification event. A 2xx response activates
the endpoint. Changing the URL or re-enabling a disabled or blocked endpoint
returns it to pending verification.
Once the endpoint is active, POST /webhook-endpoints/{id}/test queues a
signed webhook.endpoint.test event so you can exercise your receiver without
a real booking. Activation is the only precondition, and it is the whole
difference between the two commands: /verify runs against an endpoint in any
state, while /test returns 409 WEBHOOK_ENDPOINT_NOT_ACTIVE until a
verification probe has been answered with 2xx. Neither command takes a request
body.
Subscribe by account, profile, or property
Each subscription contains an event type plus optional profile_id and
property_id filters:
- no filter: every matching booking event owned by the account;
profile_id: only events originating from that account profile;property_id: only events for that hotel, named by its Adrasis hotel id (see Property content);- both: their intersection.
Use PUT /webhook-endpoints/{id}/subscriptions to replace the complete set.
The supplied profile must belong to the authenticated account and the hotel
id must name a property of its tenant. The write requires the current endpoint ETag in
If-Match.
Supported booking event types are booking.created, booking.confirmed,
booking.modified, booking.cancelled, booking.no_show, booking.voided,
booking.suspended, booking.resumed, booking.expired,
booking.rtb.accepted, booking.rtb.declined,
booking.refund.requested, and booking.refund.completed.
Verify a signature
Every delivery includes:
| Header | Meaning |
|---|---|
Adrasis-Webhook-Id |
Stable public event ID used for deduplication. |
Adrasis-Webhook-Timestamp |
Unix seconds for this HTTP attempt. |
Adrasis-Webhook-Signature |
v1= followed by lowercase HMAC-SHA256 hex. |
Adrasis-Webhook-Attempt |
One-based attempt number. |
Compute HMAC-SHA256 over the ASCII timestamp, one period byte, and the exact raw request body. Do not parse and serialize JSON before verification. Compare the expected and received signatures in constant time, reject timestamps more than five minutes from your clock, then deduplicate by event ID.
import hashlib
import hmac
def verify(secret: str, timestamp: str, body: bytes, received: str) -> bool:
signed = timestamp.encode("ascii") + b"." + body
expected = "v1=" + hmac.new(
secret.encode("ascii"), signed, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, received)
Retries, ordering, and DLQ
A 2xx response succeeds. Network errors, 408, 425, 429, and 5xx responses are
retried. Other 3xx and 4xx responses are terminal. Automatic delivery uses up
to eight attempts within 24 hours with bounded full-jitter backoff;
Retry-After is honored for 429 and 503 up to one hour.
Ordering is preserved per endpoint and booking. No global order is promised across unrelated bookings. Return 2xx quickly and move slow work to your own queue.
Inspect outcomes with GET /webhook-endpoints/{id}/deliveries. The response
contains safe attempt evidence but excludes payload bytes, signatures,
resolved addresses, endpoint URLs, and secrets. A delivery in dlq may be
queued for one manual attempt with
POST /webhook-endpoints/{id}/deliveries/{delivery_id}/retry. Manual
redelivery uses the same event ID and frozen body.
Rotate, disable, and delete
POST /webhook-endpoints/{id}/rotate-secret mints a new secret and returns it
once. The previous version remains valid for 24 hours, and deliveries already
pinned to it continue to use it during that grace window. Deploy the new
secret to receivers before removing the old one.
PATCH, subscription replacement, and deletion require If-Match. On
WEBHOOK_VERSION_CONFLICT, read the endpoint again and reapply the change with
the new ETag. Deletion is soft, stops new fanout, and moves pending deliveries
to DLQ.
Repeating the delete returns 204. If-Match is still required on the repeat,
but its value is no longer revalidated once the endpoint is gone, so send the
ETag you were holding before the delete or If-Match: *. Reading the endpoint
back returns 404, so there is no fresh ETag to fetch for it.
Management writes are limited to 30 per minute per account. Manual test and
retry commands have a separate 10-per-minute account budget. Honor
Retry-After on 429 RATE_LIMITED.
Event envelope
Booking lifecycle events have this outer shape. The booking inside
data.object is abbreviated here; the complete object follows the
corresponding booking response:
{
"id": "wh_evt_...",
"type": "booking.confirmed",
"api_version": "2026-08-13",
"created_at": "2026-08-31T12:00:00Z",
"data": {
"account_public_id": "account000000001",
"profile_public_id": "profile000000001",
"property_public_id": "72259",
"object": {
"public_id": "bk_8H2nQ4pZ",
"status": "CONFIRMED"
}
}
}
data.object is the partner-visible booking projection captured with the
committed transition. data.account_public_id identifies the account and
data.profile_public_id records the immutable originating profile for
attribution. Branch on type and api_version, ignore additive unknown
fields, and never infer account ownership from the profile field. When the
booking is for a hotel, data.property_public_id carries its Adrasis hotel id.
Next steps
- API reference: exact management request and response schemas.
- The booking flow: booking lifecycle and reconciliation.
- Idempotency: safe write retry behavior.
- Errors: complete status and error-code handling.