Skip to main content
Event webhooks push the same product events as the real-time event feed to an HTTPS endpoint you control. Your application does not hold a connection open to OAO. Each webhook keeps a durable cursor in PostgreSQL, so an outage on either side delays delivery but never skips an event. Use a webhook when your application already has its own real-time backend, such as Convex, and should render agent progress from its own database. Use the SSE feed when a server process can stay connected to OAO.

How delivery works

  • Ordered: each webhook receives events in project-position order, one batch at a time. A batch holds up to 100 events and about 256 KB.
  • At least once: OAO advances the cursor only after a 2xx response. A failed batch is resent with the same webhook-id header and the same events, so deduplicate by event id.
  • Retries: failures back off from 2 seconds, doubling to at most 5 minutes. A Retry-After header is honored within that range. Retries continue until the endpoint recovers; events are never dropped.
  • 410 Gone: the endpoint is disabled until you re-enable it. Its cursor is kept, so re-enabling resumes where delivery stopped.
  • Filtered events: events outside the webhook’s filter advance the cursor without a request.
  • Latency: each event commit wakes the runtime worker, so a batch usually leaves within tens of milliseconds. The worker also polls every second, so a missed notification delays delivery by at most that long.
Delivery runs in the runtime worker. Both the API and the worker need the same OAO_CREDENTIAL_ENCRYPTION_KEY, because the signing secret is stored encrypted.

Create a webhook

1

Open Webhooks

In the console, open Webhooks under Configure and select Add webhook. Creating and managing webhooks requires project:admin.
2

Describe the endpoint

Enter a display name and the HTTPS endpoint URL. Choose All events or the event families you need, such as run.* and message.created.
3

Save the signing secret

The console generates a Standard Webhooks secret that starts with whsec_. Copy it into your receiver’s configuration now. OAO stores it encrypted and never shows it again.
4

Choose where delivery starts

New events only starts after the latest committed event. Replay history delivers the project’s full event history first.
Or create it over HTTP. Generate the secret yourself, for example with echo "whsec_$(openssl rand -base64 32)":
eventKinds accepts exact event kinds and family wildcards such as run.*. Omit it, or send null, to receive every event.

Delivery format

Every request is a POST with a JSON body and three Standard Webhooks headers:
Each event has the same shape as an SSE event body. fromPosition is exclusive and throughPosition is inclusive; together they cover filtered events too. cursor uses the same opaque format as the SSE id, so a receiver can hand it to Last-Event-ID if it ever needs to backfill over SSE. Respond with any 2xx status within 15 seconds. OAO ignores the response body and does not follow redirects.

Verify signatures

Verify every request before parsing its body. The signed content is ${webhook-id}.${webhook-timestamp}.${rawBody}, signed with HMAC-SHA256 using the base64-decoded part of the secret after whsec_. Reject timestamps more than 5 minutes from your clock. This verifier uses only Web Crypto, so it runs unchanged in Node.js 20+, browsers, edge runtimes, and Convex:
oaoSignature.ts
Applications inside the OAO monorepo can import the same check as verifyEventWebhookSignature from @oao/sdk-js. Any Standard Webhooks library also verifies OAO deliveries.

Receive events in Convex

A Convex HTTP action is a natural receiver: it verifies the batch, and one mutation applies it transactionally. Your Convex queries then update every subscribed client, with no connection between your UI and OAO.
1

Store the secret

Copy the verifier above into convex/oaoSignature.ts.
2

Add tables for events and messages

convex/schema.ts
3

Apply a batch idempotently

convex/oao.ts
4

Expose the HTTP action

convex/http.ts
5

Point the webhook at Convex

Use your deployment’s HTTP actions domain, which ends in .convex.site, plus the route path: https://happy-animal-123.convex.site/oao/events.
Convex does not retry failed HTTP actions; OAO does. Because a retried batch carries the same events, the event-ID check keeps the mutation idempotent. A batch stays far below Convex’s per-mutation write limits.
To develop against a local Convex backend, run the OAO API and runtime worker with NODE_ENV=development and OAO_EVENT_WEBHOOKS_ALLOW_PRIVATE_NETWORK=true, then use the local HTTP actions URL printed by npx convex dev. OAO refuses this setting outside development. The console accepts an http:// endpoint only while the API reports this setting as enabled.

Include message content

By default, message.created carries only metadata such as the role. With includeMessageContent: true, OAO adds a message object with the text of user messages and of the assistant’s final reply. That is the same transcript text an authorized reader gets from GET /sessions/{sessionId}.
Message content leaves OAO in every delivery. Enable it only for receivers you control. Tool arguments, tool results, provider reasoning, and file bytes are never included.

Rotate the signing secret

Send a new secret to PUT /event-webhooks/{webhookId}/credential. For 24 hours, OAO signs every delivery with both the new and the previous secret, so you can update your receiver without rejecting deliveries. Set previousCredentialTtlSeconds to change that window, from 0 for immediate revocation up to 7 days.

Monitor delivery

The webhook resource reports delivery health: lastErrorCode is one of http_status, timeout, connection_failed, destination_blocked, endpoint_gone, or signing_key_unavailable. The last code means the worker cannot decrypt the secret; check that the API and worker share the same OAO_CREDENTIAL_ENCRYPTION_KEY. Changing the endpoint URL, event filter, or message-content setting discards a pending batch and forms the next one under the new configuration. Enabling or disabling the webhook keeps a pending batch, which is resent under the same webhook-id. After any of these changes, a request already in flight is not recorded, even if the old endpoint answered 2xx or 410, and its events are delivered again. Delivery resumes immediately, or within 20 seconds when a request was in flight, so batches never overlap. To replay history into a new receiver, create another webhook with deliverFrom: "beginning".

Security

  • Destinations: endpoints must use HTTPS and resolve only to public addresses. OAO pins the connection to the checked address, does not follow redirects, and times out after 15 seconds.
  • Secrets: the signing secret is write-only and stored with AES-256-GCM. Responses, audit entries, events, and logs carry only its fingerprint and version.
  • Endpoint URLs: project admins can read the full URL, and audit entries record only its origin. Authenticate with the signature, and never put credentials in the URL.

Limits