Skip to main content
Available today: session.analysis_complete, meeting.created, meeting.ended, transcript.ready, recording.ready, participant.joined, participant.left, analysis.failed — all over the v2 delivery pipeline (signed with replay protection, durable retries, replay API). See Events for the full catalog and payload schemas.

Concepts

A webhook endpoint is a URL you own that Waterr POSTs events to. You create one per account in the dashboard or via the API. Each endpoint has:
  • A url (HTTPS required in production)
  • A signing_secret (whsec_…) shown once at create-time — store it immediately
  • A subscribed_events allowlist (["*"] = all events)
  • An enabled flag for soft-pause
Every event fires to every matching endpoint on your account, with independent retries and an audit log per delivery.

Quickstart

1. Create an endpoint

Response (the only time signing_secret is ever returned):
signing_secret is shown once. Save it to your secret store immediately — there’s no way to retrieve it later. If you lose it, call POST /v1/webhooks/endpoints/:id/rotate-secret to issue a new one.

2. Verify incoming requests

Every POST carries a Waterr-Signature header:
t is a Unix timestamp; v1 is HMAC-SHA256 of ${t}.${rawBody} with your signing secret. Verify both the signature and the timestamp freshness (5 min tolerance is what we recommend) before trusting the payload.
Always use the raw request body — express.json(), request.get_json(), etc. mutate whitespace and break the HMAC.

Event envelope

Every webhook POSTs a JSON body with this shape:
id is stable across retries — use it as your dedupe key. If you’ve processed event id=9b6c… once, ignore any subsequent delivery with the same id.

Events

Common envelope

Every event uses the same envelope. data.meeting_id and data.scenario_id are always present so a single handler can route by event + meeting_id without extra lookups.

Event-specific payloads

Fires once when a meeting is created via the API. Lightweight — hydrate the rest via GET /v1/meetings/:id.

Event ordering

For a single meeting, the typical sequence is:
recording.ready is NOT guaranteed to fire after the analysis events — the Daily.co recording upload runs on its own clock. Don’t gate session.analysis_complete handlers on having a recording yet.

Coming soon

  • meeting.started — requires a real “first participant entered the room” signal. We track it in meetingMLservice today but don’t persist it to CoreBackend; will land when that round-trip ships.

Delivery & retries

  • Timeout per attempt: 10 seconds.
  • Any 2xx = success.
  • 4xx (except 408/429) = permanently failed. We don’t retry — your code rejected the event on purpose.
  • 5xx / 408 / 429 / network errors = retried with exponential backoff: 30s → 5m → 30m → 2h → 12h, then marked dead.
  • Every attempt is logged. View them at GET /v1/webhooks/endpoints/{id}/deliveries or replay one with POST /v1/webhooks/deliveries/{deliveryId}/redeliver.

Managing endpoints

Rotating a secret

POST /v1/webhooks/endpoints/:id/rotate-secret returns a new whsec_… and keeps the previous secret valid for 24 hours. During that window every delivery is signed with both secrets (two v1= segments in the Waterr-Signature header), so receivers can roll their verifier any time before the window expires.
Accept the delivery if any v1= matches.

Per-event subscription

Pass an explicit allowlist to receive only what you need:

Backward compatibility

The pre-v2 per-scenario webhook (configured via PUT /v1/scenarios/{id}/session-options with webhook_url) still works until 2026-09-25. During the transition both paths fire:
  • A Waterr-Signature header (new format, replay-protected) for v2 receivers
  • The legacy X-Waterr-Signature: sha256=<hex> (body-only HMAC) so MVP receivers don’t break
Migrate by creating a v2 endpoint with the same URL and removing session_options.webhook_url. After migration, drop the legacy verifier from your code.

Local development with the waterr CLI

The waterr CLI tunnels live webhook events to your laptop and fires synthetic events for receiver testing — no ngrok required.

Install

Tunnel events to your local server

Every real event fired on your account is streamed over a signed WebSocket to the CLI and POSTed to your local URL. Signature headers come through unchanged, so your local verifier exercises the same code path as prod. Filter to specific events:
Print payloads to stdout instead of forwarding (handy for debugging):

Fire a synthetic event

Endpoint + delivery management

See waterr <command> --help for full flags.