# events

> Follow each step your agent takes, live or afterwards, or as webhooks.

Follow what Bash is doing. Every step it takes is an event: the reply as it is written, tasks starting and finishing, approvals, files and costs. You can stream one conversation's events live, read every step of a task afterwards, or have events sent to your server as webhooks.

## TypeScript

```ts
// Stream a conversation live. You get its stored events first, then new ones as they happen.
for await (const event of vestra.events('conv_8f2k')) {
  console.log(event.type, event.data);
  if (event.type === 'conversation.status' && event.data.turn_status === 'completed') break;
}

// Read a task afterwards. You get every step of one task, with tokens and cost per turn.
const trace = await vestra.events.trace('task_91d');

// Send events to your server. Webhooks are signed and have the same shape. The signing secret is shown only once.
const endpoint = await vestra.events.to('https://hooks.example.com/vestra', { events: ['task.completed', 'interaction.created', 'usage.cap_reached'] });
console.log(endpoint.secret);
app.post('/vestra', express.raw({ type: 'application/json' }), vestra.events.handler(async (event) => {
  if (event.type === 'task.completed') await markDone(event.task_id, event.data.cost_usd);
}));

// Catch up on what you missed. List the events your webhooks were sent, newest first.
const failed = await vestra.events.list({ type: 'task.failed' });
console.log(trace, failed.data.length);
```

## Python

```python
# Stream a conversation live. You get its stored events first, then new ones as they happen.
for event in vestra.events("conv_8f2k"):
    print(event.type, event.data)
    if event.type == "conversation.status" and event.data.turn_status == "completed":
        break

# Read a task afterwards. You get every step of one task, with tokens and cost per turn.
trace = vestra.events.trace("task_91d")

# Send events to your server. Webhooks are signed and have the same shape. The signing secret is shown only once.
endpoint = vestra.events.to("https://hooks.example.com/vestra", events=["task.completed", "interaction.created", "usage.cap_reached"])
print(endpoint.secret)


def on_event(event):
    if event.type == "task.completed":
        mark_done(event.task_id, event.data.get("cost_usd"))


handler = vestra.events.handler(on_event)  # reads VESTRA_WEBHOOK_SECRET


@app.post("/vestra")
def vestra_webhook():  # Flask: hand it the raw body, as it came
    status, body = handler(request.get_data(), request.headers)
    return body, status


# Catch up on what you missed. List the events your webhooks were sent, newest first.
failed = vestra.events.list(type="task.failed")
print(trace, len(failed.data))
```

## CLI

```bash
# Catch up on what you missed. List the events your webhooks were sent, newest first.
vestra events --type task.completed --user traveller-42

# Read a task afterwards. You get each step, and what it cost.
vestra events task_91d

# Send events to your server. They arrive signed, and the signing secret is shown only once.
vestra events --to https://hooks.example.com/vestra --type task.completed,interaction.created

# vestra events conv_8f2k follows a conversation as it happens, and
# vestra events --to localhost:3000/vestra forwards events to this computer while you build.
# Both run until you press Ctrl-C.
```

## HTTP

```bash
# Stream a conversation live. If the connection drops, reconnect with the last seq you saw.
curl -N "$VESTRA_BASE_URL/v1/conversations/conv_8f2k/events" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Last-Event-ID: 41"

# Read the same events as JSON. You get them in pages, after the seq you give.
curl "$VESTRA_BASE_URL/v1/conversations/conv_8f2k/events?format=json&after=41" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Read a task afterwards. You get every step of one task, with tokens and cost per turn.
curl "$VESTRA_BASE_URL/v1/tasks/task_91d/trace" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Send events to your server. Register a URL and the events it wants. The signing secret is shown only once.
curl "$VESTRA_BASE_URL/v1/webhooks" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://hooks.example.com/vestra", "events": ["task.completed", "interaction.created", "usage.cap_reached"] }'

# Catch up on what you missed. List the events your webhooks were sent, newest first.
curl "$VESTRA_BASE_URL/v1/events?type=task.failed" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

## MCP

```json
{
  "method": "tools/call",
  "params": {
    "name": "events",
    "arguments": { "user": "traveller-42", "conversation": "conv_8f2k", "after": 0 }
  }
}
```

## Good to know

### The event shape in the SDKs

However an event arrives, the SDK gives you the same shape: `{ id, seq, type, created_at, user, conversation_id, task_id, data, via }`. `user` is your id for the user, or null for your company's own Bash. Once you check `event.type`, `event.data` has the matching type.

### Streaming over HTTP

`GET /v1/conversations/{id}/events` streams Server-Sent Events. Each frame has `id: <seq>`, `event: <type>` and `data: { seq, type, data, created_at }`. Reconnect with `Last-Event-ID: <seq>`, and you miss nothing. `?format=json&after=<seq>` returns the same events as pages of JSON.

### Webhook bodies and signatures

A webhook's body is `{ id, object, type, mode, created_at, user_id, task_id, conversation_id, data }`. It is signed in the `Vestra-Signature: t=<unix seconds>,v1=<hex>` header. The signature is an HMAC-SHA256 of `<t>.<raw body>` with your endpoint's secret, and it is valid for 300 seconds. `vestra.events.handler` checks it for you. Without the SDK, check it yourself before you trust a body.

### Stream or webhooks

The stream carries everything that happens in a conversation. Webhooks carry the events that still matter afterwards: replies, tasks, approvals, files, sessions, routines, usage alerts, and changes to users, connections, deployments, tests and keys. `message.delta`, `task.progress`, `usage.updated` and `screen.control` only arrive on the stream.

### Catching up

`GET /v1/events` lists the events your webhooks were sent, newest first. `?after=evt_…` reads forward from an event you saw.

## The routes behind events

- `GET` [/v1/conversations/{conversation_id}/events](https://vestra.ai/developers/reference/api/conversations/get-v1-conversations-conversation-id-events.md): The one stream (SSE): replies as they are typed, work started and finished, progress, approvals, tool calls, files. Resume with Last-Event-ID. Filter to one task's events with task_id. Key scope: `runtime` (or `all`).
- `GET` [/v1/tasks/{task_id}/trace](https://vestra.ai/developers/reference/api/work/get-v1-tasks-task-id-trace.md): Every step: tools called with a summary of inputs and outputs, timings, tokens and cost per turn. Key scope: `runtime` (or `all`).
- `POST` [/v1/webhooks](https://vestra.ai/developers/reference/api/webhooks-and-events/post-v1-webhooks.md): Register a URL and the events it wants. The signing secret is shown once. Key scope: `setup` (or `all`).
- `GET` [/v1/webhooks](https://vestra.ai/developers/reference/api/webhooks-and-events/get-v1-webhooks.md): Your endpoints. Key scope: `setup` (or `all`).
- `GET` [/v1/webhooks/{endpoint_id}](https://vestra.ai/developers/reference/api/webhooks-and-events/get-v1-webhooks-endpoint-id.md): One endpoint. Key scope: `setup` (or `all`).
- `PATCH` [/v1/webhooks/{endpoint_id}](https://vestra.ai/developers/reference/api/webhooks-and-events/patch-v1-webhooks-endpoint-id.md): Change URL or events, switch it on or off. Key scope: `setup` (or `all`).
- `DELETE` [/v1/webhooks/{endpoint_id}](https://vestra.ai/developers/reference/api/webhooks-and-events/delete-v1-webhooks-endpoint-id.md): Remove it. Key scope: `setup` (or `all`).
- `POST` [/v1/webhooks/{endpoint_id}/roll-secret](https://vestra.ai/developers/reference/api/webhooks-and-events/post-v1-webhooks-endpoint-id-roll-secret.md): New signing secret; the old one is accepted for 24 hours. Key scope: `setup` (or `all`).
- `POST` [/v1/webhooks/{endpoint_id}/test](https://vestra.ai/developers/reference/api/webhooks-and-events/post-v1-webhooks-endpoint-id-test.md): Send a sample event now. Key scope: `setup` (or `all`).
- `GET` [/v1/webhooks/{endpoint_id}/deliveries](https://vestra.ai/developers/reference/api/webhooks-and-events/get-v1-webhooks-endpoint-id-deliveries.md): Every attempt with status code and timing. Key scope: `setup` (or `all`).
- `POST` [/v1/webhooks/{endpoint_id}/deliveries/{delivery_id}/retry](https://vestra.ai/developers/reference/api/webhooks-and-events/post-v1-webhooks-endpoint-id-deliveries-delivery-id-retry.md): Send one again. Key scope: `setup` (or `all`).
- `GET` [/v1/events](https://vestra.ai/developers/reference/api/webhooks-and-events/get-v1-events.md): The same events as a list you can pull, for anything you missed. Key scope: `runtime` (or `all`).
- `GET` [/v1/events/{event_id}](https://vestra.ai/developers/reference/api/webhooks-and-events/get-v1-events-event-id.md): One event. Key scope: `runtime` (or `all`).

Next: [`files`](https://vestra.ai/developers/files.md)
