Webhooks are available on the Professional plan and above. Add an endpoint under Settings → Webhooks, choose the events you want, and copy the signing secret. You can send a test delivery from the same screen.
Events
| Event | Sent when |
|---|---|
BOOKING_CREATED | A booking is created |
BOOKING_UPDATED | A booking changes, for example its status or time |
BOOKING_CANCELED | A booking is cancelled |
PAYMENT_SUCCEEDED | A booking payment succeeds |
PAYMENT_FAILED | A booking payment fails |
REFUND_PROCESSED | A refund for a booking payment is processed |
Request format
Triviyo sends an HTTPS POST with a JSON body:
{
"event": "BOOKING_CREATED",
"timestamp": "2026-10-04T10:00:00.000Z",
"data": {
"booking": {
"id": "clx...",
"bookingNumber": "TRV-...",
"status": "CONFIRMED",
"guestEmail": "[email protected]",
"guestName": "Alex Guest",
"totalParticipants": 2,
"total": "120.00",
"currency": "EUR",
"createdAt": "2026-10-04T10:00:00.000Z"
},
"experience": { "id": "clx...", "title": "Sunset kayak tour" },
"timeSlot": {
"id": "clx...",
"startTime": "2026-10-10T17:00:00.000Z",
"endTime": "2026-10-10T19:00:00.000Z"
}
}
}
Payment events carry a payment object (id, bookingId, amount, currency, stripePaymentIntentId, createdAt) and a short booking object (bookingNumber, guestEmail).
REFUND_PROCESSED carries a refund object (id, bookingId, paymentId, amount, currency, reason, processedAt) and the same short booking object.
Headers sent with every request:
| Header | Value |
|---|---|
Content-Type | application/json |
X-Webhook-Signature | Hex-encoded HMAC-SHA256 of the raw request body, keyed with your signing secret |
X-Webhook-Signature-Algorithm | sha256 |
User-Agent | Triviyo-Webhook/1.0 |
Verifying the signature
Compute the HMAC over the exact raw body you received, before parsing JSON, and compare it in constant time.
import crypto from "node:crypto";
export function isValidTriviyoWebhook(rawBody, signatureHeader, secret) {
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const received = Buffer.from(signatureHeader ?? "", "utf8");
const computed = Buffer.from(expected, "utf8");
return received.length === computed.length && crypto.timingSafeEqual(received, computed);
}
Delivery, timeouts and retries
- Respond with any
2xxstatus within 10 seconds to acknowledge a delivery. - A delivery that times out, cannot connect, or gets a
408,429or5xxresponse is retried automatically, with up to 6 attempts in total: up to three in the first run (immediately, then after about 1 and 5 seconds), then up to two and one on the next two runs of the delivery queue. After the last attempt the delivery is marked failed. - Any other
4xxresponse (for example400,401,404or410) is treated as permanent: the delivery is marked failed immediately, its HTTP status is recorded in the delivery log, and it is not retried. Fix the endpoint, then send a test delivery under Settings → Webhooks. - After 10 consecutive failed deliveries the endpoint is disabled automatically. Fix the endpoint and re-enable it under Settings → Webhooks.
- Endpoints must be publicly reachable
http(s)URLs; private and internal addresses are rejected.
Deliveries can arrive more than once and out of order. Use the booking or payment id to make your handler idempotent.