Events & webhooks
Most API calls let an agent observe a turn it started. Events let an agent listen for workspace state changes it did not start — a turn completing, a confirmation resolving, a run finishing — without polling.
There are two complementary surfaces. Use either or both:
- Outbound webhooks — register an endpoint; we POST a signed, retried delivery when a subscribed event fires.
GET /v1/events— a long-lived Server-Sent Events stream of the same events, with a resume cursor, for agents that prefer to hold a connection open rather than host an endpoint.
Both carry the identical event envelope, and all payloads are snake_case.
Event catalog
Section titled “Event catalog”type | Fires when | data fields |
|---|---|---|
conversation.created | A new conversation is opened | conversation_id, channel, contact_id? |
turn.completed | An agent turn finishes | conversation_id, surface, reply_preview, finish_reason, model |
confirmation.requested | The agent parks a risky action for confirmation | action_id, action_code, summary, conversation_id? |
confirmation.resolved | A parked action is approved/rejected/expired | action_id, action_code, resolution |
spend.recorded | Billable spend is recorded | amount_usd, category, conversation_id?, balance_usd? |
run.completed | A workflow run finishes | run_id, workflow_id, status |
Envelope
Section titled “Envelope”Every event — over a webhook or the SSE feed — has this shape:
{ "id": "evt_9f8c…", "type": "turn.completed", "workspace_id": "ws-…", "created_at": "2026-07-15T22:31:04.001Z", "data": { "conversation_id": "cv-…", "surface": "api", "reply_preview": "On it —…", "finish_reason": "stop", "model": "octo" }}Outbound webhooks
Section titled “Outbound webhooks”Register an endpoint
Section titled “Register an endpoint”curl https://api.omniocto.com/v1/webhooks \ -H "Authorization: Bearer $OCTO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.example.com/hooks/octo", "event_types": ["turn.completed", "confirmation.requested"] }'event_types is optional and defaults to ["*"] (all events). Omit it to
subscribe to everything. The response returns the endpoint plus a secret
— this is shown once and is used to verify signatures:
{ "id": "whe-0123456789", "object": "webhook_endpoint", "url": "https://your-app.example.com/hooks/octo", "event_types": ["turn.completed", "confirmation.requested"], "enabled": true, "secret": "whsec_…"}Manage endpoints with GET /v1/webhooks, GET /v1/webhooks/{id},
PATCH /v1/webhooks/{id} (toggle enabled, change url/event_types), and
DELETE /v1/webhooks/{id}. Reads never return the secret again.
Verify a delivery
Section titled “Verify a delivery”Deliveries are signed with the Standard Webhooks scheme (svix-compatible). Each POST carries:
webhook-id: evt_9f8c… (the event id)webhook-timestamp: 1752619864 (unix seconds)webhook-signature: v1,K5f2… (base64 HMAC-SHA256)The signed content is ${webhook-id}.${webhook-timestamp}.${raw-body}, keyed
by your whsec_ secret (base64-decoded). Verify with the svix /
standardwebhooks libraries, or manually:
import { createHmac } from "node:crypto";
function verify(secret: string, id: string, ts: string, body: string, header: string) { const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64"); const expected = "v1," + createHmac("sha256", key).update(`${id}.${ts}.${body}`).digest("base64"); return header.split(" ").includes(expected);}Retries
Section titled “Retries”A delivery that does not return a 2xx is retried with exponential backoff
(5s, 10s, 20s, … capped at 1h) up to 6 attempts, then abandoned. An endpoint
that keeps failing is automatically disabled; re-enable it with a PATCH.
GET /v1/events (SSE)
Section titled “GET /v1/events (SSE)”curl -N https://api.omniocto.com/v1/events \ -H "Authorization: Bearer $OCTO_API_KEY"The stream first replays any events after your cursor, then tails new
events live. Each frame is a standard SSE event whose id: is a monotonic
cursor and whose event: is the event type:
id: 1041event: turn.completeddata: {"id":"evt_…","type":"turn.completed","workspace_id":"ws-…","created_at":"…","data":{…}}Resume
Section titled “Resume”Pass ?after=<cursor> to resume after a known event, or rely on the standard
Last-Event-ID request header — reconnecting clients that echo the last id
they saw pick up exactly where they left off with no gaps or duplicates.
curl -N "https://api.omniocto.com/v1/events?after=1041" -H "Authorization: Bearer $OCTO_API_KEY"Filter
Section titled “Filter”Pass ?types=turn.completed,run.completed to receive only those event types.
A :ping comment is sent every 15s to keep the connection alive through
proxies.