Skip to main content
Webhooks let your backend react asynchronously instead of polling. You register an HTTPS endpoint; Safariat POSTs a signed JSON event to it.

Event catalogue

Event envelope

  • id — idempotent event ID. Deduplicate on it (the same event may be delivered more than once).
  • testtrue when the event comes from an sk_test_* key or from POST /webhooks/{id}/test. Your production handler must ignore or clearly label these.

Delivery headers

The signing_secret is returned once, in the POST /webhooks response (WebhookEndpointWithSecret). It is never displayable again — store it securely.

Verifying the signature

Compare in constant time. Reject on mismatch.
Verify against the exact bytes received, before any JSON re-serialization. Reformatting the body breaks the signature.

Retries

A delivery is successful on a 2xx response within the timeout. Otherwise Safariat retries up to 5 attempts with backoff: 5 min → 15 min → 1 h → 6 h → 24 h. After the last attempt the delivery is dead-lettered. Because retries happen, your handler must be idempotent on X-SafarApi-Event-Id. Respond 2xx immediately and process asynchronously — slow handlers cause timeouts and unnecessary retries.

Delivery semantics

SafarAPI guarantees at-least-once delivery, not exactly-once. Two rules follow:
  • Deduplicate. The same event (X-SafarApi-Event-Id) may be delivered more than once — on retry, and in a narrow window if a delivery is received but our status write is interrupted. Treat the first successful processing as authoritative and make re-processing a no-op (upsert / dedup table keyed by Event-Id).
  • Do not rely on ordering. Events are dispatched concurrently and retried with backoff, so they can arrive out of order (e.g. booking.cancelled before its booking.confirmed, or a retried event after a newer one). Drive your state from the event’s created_at and your own state machine — never assume the arrival order reflects the real sequence.
Missed or dead-lettered a delivery? List and inspect attempts with GET /webhooks/deliveries, and re-drive one with POST /webhooks/deliveries/{id}/replay. You can also reconcile from the authoritative REST resources (GET /bookings/{n}, GET /settlements) at any time — webhooks are an optimization over polling, not the source of truth.

Testing

POST /webhooks/{id}/test sends a synthetic event to your endpoint and returns the HTTP status and response time it observed — use it to validate connectivity and your signature check before going live.