Skip to main content
GET /v1/events returns your organization’s changes in order. You hold the position, so a consumer that dies mid-stream resumes exactly where it stopped. Webhooks push the same events to you. The change feed lets you pull them, which is what you want for a consumer that can be offline, or one that needs to replay.

Read the feed

Response
Pass next_cursor back as cursor to get the next page. Omit cursor to start at the oldest event still inside the retention window. results and has_more are always present. An empty page returns the cursor you sent, unchanged, so a poller can keep the same value and call again.

The three things that will bite you

A cursor is only valid for the filter set that produced it. It encodes a position in your organization’s overall sequence, not a position within a namespace_id or event_type filter.Change either filter while reusing an old cursor and the feed silently skips whatever the previous combination would have matched in between. There is no error. Start from a fresh cursor, or none, whenever the filters change.
Retention is 90 days and expiry is permanent. Events older than that are deleted, not archived, so there is nothing to resume from.A consumer down for less than 90 days resumes from its stored next_cursor and misses nothing. A consumer down longer has to resync current state instead. Budget for that path rather than assuming the cursor always works.
payload is not a stable contract. Its shape is per-event-type and mirrors what the originating call site passes to the webhook delivery, so it can change when that call site changes.Read event_type, resource_type, operation and the ids you need, and treat everything inside payload as advisory. Fetch the resource if you need its current state.

Fields on an event

seq is monotonic per organization, so it orders events across namespaces. Use cursor to resume; use seq when you need to compare two events’ order.

Batch ingest is covered, and how to trust it

Documents created by batch processing appear in the feed the same way direct API writes do. After the pipeline confirms a flush of documents into storage, one collection.documents.written event lands per flushed chunk, carrying namespace_id, collection_id, batch_id, document_ids and count. A large batch produces hundreds of chunk events rather than one event per document, so project by fetching the listed ids. When the batch finishes, the feed carries one terminal collection.documents.batch_completed event per collection the batch wrote, with a total_documents count taken from the store itself. If the store count fails, total_documents is null. It is never a guess.
A terminal marker
The marker exists so a consumer can verify its own completeness. Take the union of document_ids across the batch’s chunk events and compare its size against total_documents. A match means you saw every write. A mismatch means some chunk events were lost, and the fix is to list the collection’s documents filtered by that batch_id and reconcile.
Delivery semantics are at-least-once, with one narrow loss window. A chunk event is emitted only after its documents are confirmed in storage, so the feed never reports phantom writes. A chunk’s event can be lost if the writing process dies between the storage confirm and the emit. The terminal marker is how you detect that: its store-counted total does not depend on the chunk events having survived. Reconcile against it rather than assuming the chunk stream is complete.