Webhooks
Subscribe to events instead of polling. Signed, retried, inspectable, redeliverable.
Webhooks let your systems react when something changes in Tourical — a booking is confirmed, a payment fails, a dispute opens — without polling for it.
Mental model
Tourical event happens
↓
The payload is validated against the published schema (see the event catalog)
↓
We find every active subscription for this tenant whose event list includes the type
↓
For each, we create a delivery row and POST your URL with a signed JSON body
↓
2xx → success
non-2xx / timeout → retry on the cascade (1m, 5m, 30m, 2h, 12h), then dead-letter
↓
24 consecutive 4xx spanning 24h → auto-pause the subscription, notify the operatorDelivery is at-least-once: dedupe on the Tourical-Event-Id header on your side.
Wire format
Every delivery is a POST with these headers:
POST /your-endpoint HTTP/1.1
Content-Type: application/json
User-Agent: Tourical-Webhooks/1.0 (+https://developers.tourical.com)
Tourical-Signature: t=1716724800,v1=8b2c9a8d6f...
Tourical-Event-Type: booking.confirmed
Tourical-Event-Id: 7d3f0e2c-9aa1-4f2c-bd56-1c4a8e4f0b91
Tourical-Request-Id: whd_01j9….1
Tourical-Delivery-Attempt: 1Tourical-Request-Idis<deliveryId>.<attempt>— unique per HTTP attempt. Quote it when you contact support.Tourical-Delivery-Attemptcounts attempts for this delivery row (1-based). A redelivery is a new row with attempt 1 and the sameTourical-Event-Id.- During a signing-key rotation the signature header carries two
v1=values for 24 hours — see Signing.
And a body shaped like:
{
"id": "7d3f0e2c-9aa1-4f2c-bd56-1c4a8e4f0b91",
"type": "booking.confirmed",
"created": 1716724800,
"data": {
"bookingId": "bk_a1b2c3",
"tripId": "trp_x9y8z7",
"status": "confirmed",
"totalRetailCents": 245000,
"currency": "EUR"
}
}idmatches theTourical-Event-Idheader. It's a UUID. Use it as a dedupe key.typematchesTourical-Event-Type. Switch on it.createdis the unix-second timestamp at the moment we sent the event.datais the per-event-type payload. See Events for shapes; the JSON Schemas live in thewebhookssection of/openapi.json.
Subscribe
Two ways to create a subscription:
- Operator dashboard → Settings → Developer access → Add endpoint. Easiest for one-off setup.
POST /v1/webhooksprogrammatically. Needed for marketplace integrations that install themselves.
curl https://app.tourical.com/api/v1/webhooks \
-H "Authorization: Bearer tk_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.your-product.com/tourical/webhooks",
"events": ["booking.confirmed", "booking.cancelled", "payment.failed"]
}'The response carries the plaintext signing key, exactly once:
{
"data": {
"id": "whsub_...",
"url": "https://api.your-product.com/tourical/webhooks",
"events": ["booking.confirmed", "booking.cancelled", "payment.failed"],
"active": true,
"createdAt": "2026-05-26T12:00:00.000Z"
},
"signingKey": "zyXBHGFEDCBA9876..."
}Store the signingKey immediately. It's the only sensitive material in the subscription, and it cannot be re-shown. To rotate it without downtime call POST /v1/webhooks/{id}/rotate-key: the old key keeps co-signing for 24 hours.
Your subscription URL is validated at create-time: HTTPS only, must resolve to a public IP. Localhost, RFC1918, link-local, CGNAT are all rejected.
Inspect and redeliver
Every attempt is recorded. GET /v1/webhooks/{id}/deliveries lists the rows (newest first, filter with ?status=dead_letter), and POST /v1/webhooks/{id}/deliveries/{deliveryId}/redeliver queues a fresh delivery of the same event. The operator dashboard exposes the same two actions under the subscription's Recent deliveries. See Retries.
Next
Signature algorithm
Exactly how the signature is computed, the header format, and key rotation.
Verify a signature
Node and Python verifiers you can copy-paste.
Event catalog
Every event type and its payload shape, generated from the runtime schema.
Retries, auto-pause, redelivery
What happens when your endpoint is down, and how to recover.

