Get access

Docs · Webhooks

Webhooks.

Webhooks let your systems react to changes inside Guest Talk in near real time without polling. Guest Talk emits a webhook for every material event: a new reservation, a payment success, a channel-manager parity break, an inbound guest message. This page describes the event catalogue, the delivery guarantees, and how to verify signatures.

Event catalogue

  • reservation.created
  • reservation.updated
  • reservation.cancelled
  • reservation.checked_in
  • reservation.checked_out
  • reservation.no_show
  • guest.created
  • guest.merged
  • guest.consent_changed
  • folio.opened
  • folio.closed
  • payment.succeeded
  • payment.failed
  • payment.refunded
  • rate.updated
  • channel.parity_break
  • channel.reservation_failed
  • message.received
  • message.delivered
  • message.opt_out
  • room.status_changed
  • task.completed

Delivery guarantees

Guest Talk guarantees at-least-once delivery. Consumers must be idempotent (see below). Ordering is not strictly guaranteed across events but is best-effort within a single subject (for example, updates to the same reservation are delivered in order). Delivery latency is typically under 3 seconds from the underlying event; 99th percentile latency is under 30 seconds.

Payload structure

{ "id": "evt_01H8XY5ZK4Q2R7P9N3M6WVJXBC", "type": "reservation.created", "created_at": "2026-07-26T09:14:22.317Z", "workspace_id": "wsp_...", "data": { ... }, "previous_attributes": null }

For update events, previous_attributes contains the changed fields' prior values, enabling diff-based consumers.

Signature verification

Every webhook request carries a Guesttalk-Signature header, a timestamp and HMAC-SHA256 of the raw request body signed with the webhook signing secret. Verify by computing the same HMAC and comparing in constant time. Reject requests older than 5 minutes to prevent replay attacks. Example (TypeScript):

import crypto from "node:crypto"; function verify(rawBody, signatureHeader, secret) { const [t, v1] = signatureHeader.split(",").map(s => s.split("=")[1]); const signed = `$${t}.$${rawBody}`; const expected = crypto.createHmac("sha256", secret).update(signed).digest("hex"); if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) throw new Error("bad signature"); if (Date.now() / 1000 - Number(t) > 300) throw new Error("stale"); }

Retries

Failed deliveries (non-2xx response or timeout after 10 seconds) are retried with exponential backoff for up to 72 hours: 30 seconds, 1 minute, 5 minutes, 15 minutes, 1 hour, 6 hours, 24 hours. If a consumer never returns 2xx within 72 hours the event is dead-lettered. Dead-lettered events remain retrievable from the workspace webhook dashboard for 30 days.

Idempotency

Consumers must be idempotent because at-least-once delivery means a small percentage of events will be delivered more than once. The id field on every event is unique and stable; deduplicate by storing the last N event IDs (Guest Talk recommends 10,000).

Testing locally

The Guest Talk CLI includes a webhook forward command that opens a tunnel from your local machine to Guest Talk's webhook infrastructure, letting you test with a real payload before deploying. Details in the CLI docs; the command is a wrapper around ngrok with a Guest Talk-managed public endpoint.

Managing subscriptions

Webhook subscriptions are managed via the API (POST /v1/webhooks) or the workspace UI (Settings > Webhooks). Each subscription lists the events it receives, the target URL, the signing secret and delivery statistics for the past 30 days.

Common pitfalls

200 with error body. A consumer that returns 200 for a bad payload silently absorbs errors. Return 4xx for permanent failures (bad signature, malformed payload) so Guest Talk stops retrying.

Long-running consumers. Do not do heavy work synchronously; ack quickly (under 10 seconds) and enqueue work locally. Otherwise Guest Talk will retry unnecessarily.

Ignoring dead-letter alerts. The workspace webhook dashboard highlights dead-lettered events; investigate them promptly because they usually indicate a persistent consumer bug.

Building a webhook consumer?

Ask for a sandbox with a pre-configured webhook subscription and sample payloads for offline development.

Get access See pricing