Polling
GET /v1/streams/{id}/status only works while a stream is live. Once
the pipeline is torn down the gateway returns state: OFFLINE with no
timestamp, so there is nothing left to read after the fact. The stream.ended
webhook carries that timestamp to you instead.Quickstart
1
Register your endpoint
secret now. Later reads return only a secret_preview; if you lose it,
your only option is POST /v1/webhooks/{id}/rotate-secret.2
Verify it reaches you
stream.ended and returns exactly what
your endpoint responded with — status, body, and round-trip time. Use it to
check both connectivity and your signature verification before real traffic
arrives.3
Handle the event
Respond
2xx quickly. Anything else — or no response within 10 seconds — counts
as a failure and gets retried.Events
A stream that reconnects emits a fresh
stream.started, so a single stream id
can cycle started → ended → started over its lifetime.
Envelope
Every event has the same shape:stream.started
stream.ended
ended_at is the last moment the stream was confirmed live, not the moment
we noticed it had stopped. Those are different: detection takes up to 45 seconds
after the last signal, and ended_at is backdated past that window. It is the
timestamp to bill and report against.
reason is one of:
Verifying signatures
Every request carries these headers:
The signature is an HMAC-SHA256 over
`${timestamp}.${rawBody}` keyed with
your secret. Sign the raw request body, before any JSON parsing —
re-serializing changes the bytes and the signature will not match.
Delivery, retries, and idempotency
A delivery is one POST to one endpoint. Non-2xx, a timeout past 10 seconds, or a connection error all count as failures and are retried with exponential backoff:id. Retries reuse the same event id, and a delivery that
timed out on your side may well have been processed. Treat handlers as
idempotent.
If an endpoint fails 20 deliveries in a row it is automatically disabled and
stops receiving events. Any successful delivery resets that counter. Re-enable
with:
Debugging deliveries
Each delivery is recorded for 14 days: the exact payload sent, how many attempts it took, and the status and body your endpoint returned. The record is updated in place as retries happen, soattempts is a running count while
response_status, response_body and error describe the most recent
attempt rather than each one individually.
status (pending, succeeded, failed), by event_type, or by
stream_id to answer “did the stream.ended for this specific stream reach
me?”:
pending means retries are still in flight; failed means every attempt was
used up.
Endpoint requirements
- HTTPS only. Plaintext
http://is rejected. - Publicly routable. Loopback, private (RFC1918), link-local and carrier-grade NAT addresses are rejected at registration. For local development, put a tunnel (ngrok, Cloudflare Tunnel) in front of your server.
- Respond within 10 seconds. Acknowledge with a
2xxfirst and do the real work asynchronously; a slow handler turns into a retry storm. - Up to 10 endpoints per account. Useful for pointing production, staging and a test receiver at the same events.