Skip to main content
Everything that happens in a conversation reaches your backend as an event — over the realtime stream (recommended — one WebSocket, lower latency, nothing to expose), HMAC-signed webhooks, or both. Event types and envelopes are identical on both channels; this page covers the shared catalog and the webhook delivery contract.

Registering endpoints

The response contains the signing secret (whsec_…) exactly once — store it in your secret manager. Up to 5 endpoints per project; GET /v3/s2s/webhooks lists them (secrets masked), DELETE /v3/s2s/webhooks/{id} removes one. URLs must be public https:// endpoints. An empty events array means all events except typingagent.typing.* is strictly opt-in and only delivered to endpoints that list it explicitly.

Event catalog

Data retention: bot-only conversations are permanently deleted ~33 days after creation; archived conversations follow the project’s retention window. conversation.deleted (and a 404 on any fetch) means “remove from your mirror” — there are no tombstones.

Envelope

  • sequence is monotonic per conversation — order and deduplicate by (conversation id, sequence). Events can arrive out of order on the wire.
  • sequence is null on agent.typing.* and contact.updated (no conversation context).
  • Treat data as the authoritative current state of what it carries.

Verifying signatures

Every delivery carries:
v1 = HMAC-SHA256(secret, t + "." + <raw request body>). Verify against the raw body bytes, allow a 5-minute tolerance on t, compare constant-time, and reject anything that fails.
Test vector — your implementation must produce exactly this signature:

Delivery semantics & recovery

  • Respond 2xx within 5 seconds; anything else (or a timeout) is retried 3 times over ~20 seconds, then dropped. Endpoints that fail persistently are circuit-broken for a few minutes.
  • Delivery is therefore at-least-once within a short window — not guaranteed. Your durable safety net is per-conversation catch-up: whenever your app opens a contact’s support screen (or your consumer suspects a gap), re-pull GET /v3/s2s/contacts/{userId}/conversations and GET /v3/s2s/conversations/{id}/messages?after=<last id you have>.
  • Signed file URLs inside payloads expire (~30 days) and rotate — never dedupe or persist by URL.