TouricalDevelopers
Webhooks

Event catalog

Every event type, when it fires, and what's in the payload.

The events listed here are stable contracts — every one of them has an emitter in the platform and every payload below is validated against the same schema at emit time (an event that would not match its schema is not sent). The examples are generated from that schema, so they cannot drift from what you receive.

The wire format wraps every payload in the standard envelope — see Webhooks → Wire format. The data field of each example shows what's inside that envelope. Machine-readable schemas: the webhooks section of /openapi.json.

Ordering. Events are delivered independently per subscription and may arrive out of order (a retry of payment.succeeded can land after booking.confirmed). Order on your side by the created timestamp of the envelope, and treat every event as at-least-once — dedupe on Tourical-Event-Id.

Booking events

booking.confirmed

A booking moved into the confirmed state — payment succeeded for the deposit or in full.

{
  "bookingId": "bk_a1b2c3",
  "bookingNumber": "TR-2026-01041",
  "tripId": "trp_x9y8z7",
  "tripVersion": 3,
  "status": "confirmed",
  "totalRetailCents": 245000,
  "currency": "EUR",
  "travelerName": "Jane Doe",
  "travelerEmail": "jane@example.com",
  "departureId": "dep_445566",
  "confirmedAt": "2026-05-26T12:00:00.000Z"
}

booking.cancelled

A booking was cancelled by the operator (operator_initiated, force_majeure), by the traveller (customer_request), or by an applied change request (change_request). Refund state depends on policy; the refund itself arrives as booking.refunded.

{
  "bookingId": "bk_a1b2c3",
  "bookingNumber": "TR-2026-01041",
  "tripId": "trp_x9y8z7",
  "status": "cancelled",
  "cancelledAt": "2026-05-26T12:05:00.000Z",
  "cancellationReason": "operator_initiated"
}

booking.refunded

A refund row was issued against a booking. One event per refund; for partial refunds refundedAmountCents is less than the booking total.

{
  "bookingId": "bk_a1b2c3",
  "bookingNumber": "TR-2026-01041",
  "refundId": "rf_zzaaqq",
  "refundedAmountCents": 24500,
  "currency": "EUR",
  "reason": "customer_request",
  "refundedAt": "2026-05-26T14:10:00.000Z"
}

Payment events

payment.succeeded

An on-session deposit / full charge or an off-session installment succeeded.

{
  "bookingId": "bk_a1b2c3",
  "paymentId": "pay_998877",
  "amountCents": 50000,
  "currency": "EUR",
  "label": "Deposit",
  "chargedAt": "2026-05-26T12:00:00.000Z"
}

payment.failed

A charge failed (after automatic retries for off-session installments). failureCode is Stripe's decline/error code when known.

{
  "bookingId": "bk_a1b2c3",
  "paymentId": "pay_998877",
  "amountCents": 50000,
  "currency": "EUR",
  "label": "Balance",
  "failureCode": "card_declined",
  "failureMessage": "Your card was declined.",
  "failedAt": "2026-06-20T09:30:00.000Z"
}

Dispute events

dispute.opened

Stripe notified us of a chargeback against a booking payment. evidenceDueBy is the deadline to respond.

{
  "bookingId": "bk_a1b2c3",
  "disputeId": "dp_112233",
  "reason": "fraudulent",
  "amountCents": 245000,
  "currency": "EUR",
  "evidenceDueBy": "2026-06-30T23:59:59.000Z",
  "openedAt": "2026-06-21T08:00:00.000Z"
}

dispute.closed

A dispute was closed. outcome is won, lost or warning_closed.

{
  "bookingId": "bk_a1b2c3",
  "disputeId": "dp_112233",
  "outcome": "won",
  "amountCents": 245000,
  "currency": "EUR",
  "closedAt": "2026-07-05T10:00:00.000Z"
}

Trip events

trip.published

A trip became publicly bookable on the storefront (publicUrl is the live page).

{
  "tripId": "trp_x9y8z7",
  "title": "Balkan Loop",
  "publicSlug": "balkan-loop",
  "publicUrl": "https://app.tourical.com/tour/balkan-loop",
  "publishedAt": "2026-05-20T09:00:00.000Z"
}

trip.unpublished

A previously public trip was hidden from the storefront.

{
  "tripId": "trp_x9y8z7",
  "publicSlug": "balkan-loop",
  "unpublishedAt": "2026-08-01T09:00:00.000Z"
}

CRM events

client.created

A new CRM client was created — by the dashboard (dashboard), by the API (api), by a booking that captured a new email (booking_capture), or by a prepared date request (date_request).

{
  "clientId": "cl_778899",
  "kind": "individual",
  "displayName": "Jane Doe",
  "email": "jane@example.com",
  "source": "booking_capture",
  "createdAt": "2026-05-26T12:00:00.000Z"
}

Test event

webhook.test

Sent when an operator clicks Test connection or calls POST /v1/webhooks/test. Delivered to every active subscription (or the one named by subscriptionId) regardless of its events list — useful for verifying signature handling end-to-end.

{
  "test": true,
  "subscriptionId": "whs_123",
  "sentBy": {
    "userId": "usr_1"
  },
  "timestamp": "2026-05-26T12:00:00.000Z"
}

sentBy is a free-form string map whose key names the trigger, so branch on its presence rather than assuming a shape: the dashboard's Test connection button sends { "userId": … } (as above), while POST /v1/webhooks/test sends { "tokenId": … } for the calling API token.

On this page