# Events

> Everything that happens in Kabu is an event, in one ordered, resumable stream, to page, stream or receive by webhook. Types, ordering and recovery.

Every change in Kabu is an **event**: a message sent, delivered, answered or expired, a session started or ended. A
service that needs to react, such as a dashboard, an audit log or a bot in your chat, reads them in one of three
ways, and all three carry the same objects:

| Way | Call | Good for |
|---|---|---|
| Page | `GET /v2/events?after=<checkpoint>&wait=25` | Batch jobs, catching up |
| Stream | `GET /v2/events/stream` (server-sent events, or NDJSON with `Accept: application/x-ndjson`) | A process that stays connected |
| Webhook | Kabu calls your endpoint | Servers that should not hold a connection: see [Webhooks](/docs/webhooks) |

## An event

```json
{
  "object": "event",
  "id": "evt_01J9Z…",
  "type": "message.resolved",
  "project": "payments-api",
  "resource": "msg_01J9Y…",
  "resource_version": 3,
  "session": "ses_01J9X…",
  "created_at": "2026-10-30T09:41:00Z",
  "data": { "object": { "object": "message", "id": "msg_01J9Y…", "status": "answered" }, "previous": { "status": "pending" } }
}
```

`data.object` is the resource as it is when the event reaches you, and `data.previous` the fields that changed. Types:
`message.created`, `message.delivered`, `message.resolved`, `message.expired`, `message.cancelled`, `session.created`,
`session.updated`, `session.ended`, `agent.paused` and `ping`. New types appear over time: ignore the ones you do not
know.

## Order and duplicates

Events are delivered at least once and in order for each resource, not across all of them. Each carries the
`resource_version` it describes: keep the highest version you have applied for each resource, and skip an event whose
version is not newer. That makes repeats and replays harmless.

## Checkpoints

Every page of events returns a `checkpoint`, even an empty one, and every streamed event carries its own (the
server-sent event's `id`). Pass it as `after` to continue exactly where you stopped; a stream that reconnects resumes
from `Last-Event-ID`. A checkpoint works only with the filters it was issued with.

Events are kept for 30 days. An older checkpoint is `410`
[`checkpoint_expired`](/docs/errors/checkpoint_expired). To start again, or to start at all:

1. take a checkpoint first, `GET /v2/events/checkpoint`, with the filters you will use;
2. list the current state, such as `GET /v2/messages`;
3. page or stream from that checkpoint, applying only newer versions.

Nothing is missed and nothing is applied twice.
