Webhooks
Real-time event delivery via HMAC-signed HTTPS POST requests with automatic retry and delivery logging.
Overview
Ananas GDS can push event notifications to any HTTPS endpoint you register. When a matching event occurs — a stop sale changes, an invoice is sent, a property updates — Ananas GDS immediately dispatches a signed POST request to all subscribed endpoints. This eliminates the need for polling and allows your systems to react in real time.
Registering a Webhook
HTTPS required
Webhook endpoints must use HTTPS. HTTP endpoints are rejected at registration time. Your server must present a valid TLS certificate.
Payload Structure
Every webhook delivery is a JSON POST body with a consistent envelope:
{
"event": "stopsale.created",
"timestamp": "2026-05-11T14:32:00Z",
"delivery_id": "del_a1b2c3d4e5f6",
"data": {
// Event-specific data object
}
}
Event Data Objects
| Event | Data Object |
|---|---|
property.updated | Full property object with changed fields highlighted. |
stopsale.created | Stop sale event: property_id, date, status, allotment, room_type, source. |
stopsale.updated | Same as created, with previous values included. |
stopsale.cancelled | Stop sale event ID and property_id. |
invoice.sent | Invoice summary: id, invoice_number, from, to, amount, currency, due_date. |
invoice.paid | Invoice summary plus paid_at timestamp and payment reference. |
invoice.disputed | Invoice summary plus dispute note text. |
invoice.cancelled | Invoice ID, invoice_number, cancellation timestamp. |
availability.updated | Property ID, affected date range, allotment values. |
Signature Verification
Every delivery includes an X-Ananas-Signature header. This is an HMAC-SHA256 digest of the raw request body, keyed with the endpoint's signing secret. Always verify this signature before processing the payload.
X-Ananas-Signature: sha256=a1b2c3d4e5f6...
import hmac
import hashlib
def verify_signature(payload_body: bytes, signature_header: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload_body,
hashlib.sha256
).hexdigest()
received = signature_header.replace("sha256=", "")
return hmac.compare_digest(expected, received)
Use constant-time comparison
Always use hmac.compare_digest() (or your language's equivalent) rather than == for comparing signatures. This prevents timing-based attacks.
Retry Policy
A delivery is considered successful when your endpoint responds with any 2xx HTTP status code within 30 seconds. If delivery fails (non-2xx, timeout, or connection error), Ananas GDS retries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 5 minutes |
| 2nd retry | 30 minutes |
| 3rd retry | 2 hours |
| 4th retry | 8 hours |
| 5th retry (final) | 24 hours |
After 5 failed attempts the delivery is marked failed and no further retries are attempted. You can manually retry from the delivery log in the dashboard.
Delivery Log
Every delivery attempt is recorded in WebhookDelivery with the response code, response body, attempt count, and next retry timestamp. Access the log for any endpoint from Developer Tools → Webhooks → Deliveries, or via the API:
GET /api/dev/integrations/webhooks/{id}/deliveries/
Authorization: Token <token>
POST /api/dev/integrations/webhooks/deliveries/{delivery_id}/retry/
Authorization: Token <token>
Test Ping
Send a synthetic test event to verify your endpoint without waiting for a real event to occur:
POST /api/dev/integrations/webhooks/{id}/test/
Authorization: Token <token>
The test payload uses the event type test.ping with a minimal data object. Inspect the delivery log to confirm receipt.
Idempotency
Each delivery has a unique delivery_id in the envelope. If your endpoint receives the same delivery_id twice (e.g. after a retry following a timeout where your server actually processed the request), treat the second as a duplicate and return 2xx without re-processing. Store processed delivery IDs for at least 24 hours.