TouricalDevelopers
Webhooks

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 operator

Delivery 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: 1
  • Tourical-Request-Id is <deliveryId>.<attempt> — unique per HTTP attempt. Quote it when you contact support.
  • Tourical-Delivery-Attempt counts attempts for this delivery row (1-based). A redelivery is a new row with attempt 1 and the same Tourical-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"
  }
}
  • id matches the Tourical-Event-Id header. It's a UUID. Use it as a dedupe key.
  • type matches Tourical-Event-Type. Switch on it.
  • created is the unix-second timestamp at the moment we sent the event.
  • data is the per-event-type payload. See Events for shapes; the JSON Schemas live in the webhooks section of /openapi.json.

Subscribe

Two ways to create a subscription:

  • Operator dashboard → Settings → Developer access → Add endpoint. Easiest for one-off setup.
  • POST /v1/webhooks programmatically. 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

On this page