BashBrainTeammatesPricingDevelopersGet a test key

events

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.

Stream a conversation live

You get its stored events first, then new ones as they happen.

TypeScript
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.

TypeScript
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.

TypeScript
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.

TypeScript
const failed = await vestra.events.list({ type: 'task.failed' });console.log(trace, failed.data.length);

Stream a conversation live

You get its stored events first, then new ones as they happen.

Python
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.

Python
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.

Python
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.

Python
failed = vestra.events.list(type="task.failed")print(trace, len(failed.data))

Catch up on what you missed

List the events your webhooks were sent, newest first.

CLI
vestra events --type task.completed --user traveller-42

Read a task afterwards

You get each step, and what it cost.

CLI
vestra events task_91d

Send events to your server

They arrive signed, and the signing secret is shown only once.

CLI
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.

Stream a conversation live

If the connection drops, reconnect with the last seq you saw.

HTTP
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.

HTTP
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.

HTTP
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.

HTTP
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.

HTTP
curl "$VESTRA_BASE_URL/v1/events?type=task.failed" \  -H "Authorization: Bearer $VESTRA_API_KEY"
MCP
{  "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.

Every event

One catalog names every event, on the stream and in your webhooks. The events reference lists each, where it arrives, and every field it carries.

The routes behind events13 routes, each with every field in the API reference
GET/v1/conversations/{conversation_id}/eventsThe 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}/traceEvery step: tools called with a summary of inputs and outputs, timings, tokens and cost per turn.Key scope: runtime (or all)
POST/v1/webhooksRegister a URL and the events it wants. The signing secret is shown once.Key scope: setup (or all)
GET/v1/webhooksYour endpoints.Key scope: setup (or all)
GET/v1/webhooks/{endpoint_id}One endpoint.Key scope: setup (or all)
PATCH/v1/webhooks/{endpoint_id}Change URL or events, switch it on or off.Key scope: setup (or all)
DELETE/v1/webhooks/{endpoint_id}Remove it.Key scope: setup (or all)
POST/v1/webhooks/{endpoint_id}/roll-secretNew signing secret; the old one is accepted for 24 hours.Key scope: setup (or all)
POST/v1/webhooks/{endpoint_id}/testSend a sample event now.Key scope: setup (or all)
GET/v1/webhooks/{endpoint_id}/deliveriesEvery attempt with status code and timing.Key scope: setup (or all)
POST/v1/webhooks/{endpoint_id}/deliveries/{delivery_id}/retrySend one again.Key scope: setup (or all)
GET/v1/eventsThe same events as a list you can pull, for anything you missed.Key scope: runtime (or all)
GET/v1/events/{event_id}One event.Key scope: runtime (or all)