Endpoints
Manage endpoints in the dashboard or via the API:201 response carries the endpoint’s whsec_… secret — this once. Store it;
it cannot be read back.
Alternatively, pass a per-run webhook_url on session/extraction create. Ad-hoc URLs
sign with your account default secret — provisioned lazily on first use, readable
and rotatable in the dashboard’s webhook settings (the one secret that isn’t
shown-once, because ad-hoc URLs have no creation moment to show it at).
Payload versioning: each endpoint pins the fn-version current at its creation;
payloads serialize under that pin whatever your account pin does later. PATCH the
endpoint to advance it deliberately. Ad-hoc webhook_url deliveries use the account
pin.
Delivery targets are SSRF-checked as a hard wall: https only, hosts must resolve
to public unicast addresses — re-resolved and re-checked at every delivery attempt —
redirects are not followed, and private/loopback/link-local/metadata ranges are
refused with invalid_param.
Delivery contract
Every delivery carries:- Your endpoint must return 2xx within 10 seconds. Do the work after you respond.
- Retries at 1 m, 5 m, 15 m, 1 h, 6 h, 24 h — then dead-lettered, visible in the attempts log.
- At-least-once: dedupe on
webhook-id. - Every attempt is kept and queryable:
GET /v1/webhooks/{id}/attempts. POST …/attempts/{msg}/redeliver(not yet available — planned) will redeliver a specific message by hand.POST /v1/webhooks/{id}/test(not yet available — planned) will send a sample delivery of each subscribed event type on demand — signed, retried, and logged like a real one, with"test": truein the envelope, working onfn_test_keys. Until it ships, verify your delivery path by registering an endpoint and watching the attempts log on a real (or test-key replayed) run terminal.
Verifying
Verify against the raw request body (before any JSON parsing middleware touches it); reject timestamps older than 5 minutes.Payload envelope
id, branch on type, ignore unknown types.
Event catalog
session.answered
session.abstained · session.error · session.stopped
stopped additionally carries stop
(spend-budget | query-budget | stopped).
session.proposal
extraction.delivered · extraction.failed
monitor.changed · monitor.unchanged · monitor.failed
unchanged deliveries are opt-in per endpoint (default: change-only). failed
carries reason and consecutive_failures; after 3 consecutive failures the monitor
pauses itself and sends a final monitor.failed with "paused": true.
balance.low
Local development
There is no first-party tunnel tool, and delivery targets must be public https (the SSRF wall refuseslocalhost). Use a tunnel of your choice (cloudflared, ngrok) to
receive real deliveries locally. POST /v1/webhooks/{id}/test for CI is planned but
not yet available — until it ships, drive a test-key replay run to a terminal and
verify your handler against the real delivery it produces.