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_eventsallowlist (["*"]= all events) - An
enabledflag for soft-pause
Quickstart
1. Create an endpoint
signing_secret is ever returned):
2. Verify incoming requests
Every POST carries aWaterr-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.
- Node.js (Express)
- Python (Flask)
- Go
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
- meeting.created
- meeting.ended
- participant.joined
- participant.left
- transcript.ready
- recording.ready
- session.analysis_complete
- analysis.failed
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}/deliveriesor replay one withPOST /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.
v1= matches.
Per-event subscription
Pass an explicit allowlist to receive only what you need:Backward compatibility
The pre-v2 per-scenario webhook (configured viaPUT /v1/scenarios/{id}/session-options with webhook_url) still works
until 2026-09-25. During the transition both paths fire:
- A
Waterr-Signatureheader (new format, replay-protected) for v2 receivers - The legacy
X-Waterr-Signature: sha256=<hex>(body-only HMAC) so MVP receivers don’t break
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
Fire a synthetic event
Endpoint + delivery management
waterr <command> --help for full flags.
