Conventions
Webhooks
Receive event notifications via outbound HTTP callbacks
Beta
Outbound webhooks let your server react to changes (event created, guest
RSVP'd, expense logged) without polling. Register endpoints via
/api/webhooks.
Delivery format
POST https://your.app/hook
Content-Type: application/json
X-TE-Event: event.created
X-TE-Delivery: 7f3a1c2b-4d8e-49f0-b1e3-2c5f6a8b9d10
X-TE-Signature: t=1714521600,v1=8f4d…
User-Agent: ToursEvents-Webhooks/1.0{
"id": "evt_01H7V…",
"type": "event.created",
"createdAt": "2026-05-20T10:14:00Z",
"data": { "id": "event-uuid", "name": "Annual Conference", "tourId": "tour-uuid" }
}Signature verification
Each delivery is signed with HMAC-SHA256 using your webhook's secret. Always verify the signature before trusting the payload.
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const [tPart, v1Part] = header.split(",");
const t = tPart.slice(2);
const v1 = v1Part.slice(3);
const signed = `${t}.${rawBody}`;
const expected = crypto.createHmac("sha256", secret).update(signed).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}- Verify against the raw request body, not the parsed JSON.
- Reject deliveries where
tis more than 5 minutes old to prevent replay. - Use
timingSafeEqualto avoid timing attacks.
Retries
Non-2xx responses trigger retries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | immediate |
| 2 | 30 s |
| 3 | 5 min |
| 4 | 1 h |
| 5 | 6 h |
| 6 | 24 h |
After the 6th failure the delivery is marked failed and surfaced on the
webhook dashboard.
Idempotency
Retries reuse the same X-TE-Delivery ID. Store seen IDs (e.g. in Redis with
a 7-day TTL) and skip duplicates.
Outbound webhooks are currently in beta. Available event types and signing details may evolve — pin against the changelog.