Skip to main content
Webhooks push events to you; the event stream lets you pull them. Every event on your account is written to a durable, ordered stream you can page through, tail live, or re-send to your webhooks — so downtime never loses an event.

Read the stream

GET /v1/events pages the stream oldest-first, so you read events in the order they happened. Each page is the standard list envelope — a data array and a pagination cursor — plus a gaps array unique to this stream. Reading uses the events:read scope.
A page looks like this:
Each entry carries the same eventId, type, and data as the webhook delivery for that event, and adds the stream’s join keys: the per-partition sequence number seq and the loop-wide idempotencyKey threaded from the originating send. Page forward with the cursor until pagination.nextCursor comes back null.

Detect gaps

Every event carries a seq — a contiguous, 1-based sequence number within its partition. The partitionKey names the resource the events belong to, such as inbox:<id> or letter:<id>. If a page’s window is missing a sequence number it should contain, the page reports it under gaps as { "partitionKey": …, "missingSeq": [...] } — a lost event is something you can see and act on, never a silent hole. Detection is window-local: it checks the sequence range present on the returned page, not the whole history. On a filtered read (type or inboxId), events excluded by the filter surface as gaps too — expected, not lost. Audit gaps on unfiltered pages.

Tail a live feed

To follow events in near-real time, seed at the current head with tail: true, then long-poll forward with wait. A request with wait set holds until a matching event arrives or the seconds elapse (capped at 12), so a steady listener never hammers the API; if nothing arrives in time you get an empty page and poll again. When events are already waiting, they return immediately.
tail: true returns data: [] plus the current head cursor (or null on an empty stream), so your next poll streams only events from that point on. It honors the same inboxId and type filters as a normal read. wait: 0 — the default — returns immediately.

Replay to your webhooks

POST /v1/events/replay re-sends a slice of history through your webhooks — to recover after your receiver was down, or to backfill a newly added endpoint. Replay uses the events:write scope. Give exactly one lower bound:
The response counts what happened and names the first and last event of the slice:
Replay goes to your active webhooks, re-matched per event on their subscribed types; skipped counts events no active subscription matched. Narrow a slice with until (an upper timestamp), types (an event-type list), or inboxId. Pass webhookId to replay to one endpoint only — useful for backfilling a new receiver without re-firing the others; a paused or unknown webhookId returns a 404. Events are re-sent in (partitionKey, seq) order, and every replayed delivery is recorded in that webhook’s delivery log. maxEvents caps the slice at 1–5,000 events (default 1,000). A slice that exceeds the cap is refused with a 400 rather than silently truncated — narrow the range or page through it with until.
Replayed events travel the normal signing and delivery path with the same event ID, so your receiver verifies and deduplicates them exactly like live ones. Keep your handler idempotent — a replay can re-send events you already processed.

Next steps

  • Webhooks — push delivery, signature verification, and the full event catalog.
  • Track delivery — delivery, bounce, and complaint events per message.
  • CLIdairo listen, the live tail built on this stream.
  • API referenceGET /v1/events and POST /v1/events/replay.