# Omnara Webhooks

An Omnara agent's `event_webhook` sends its timeline events and tool-call updates to a public
HTTPS endpoint as JSON `POST` requests. The body is `{ "event": "<type>", "data": {...} }`:
the event type is the top-level `event`, and timeline events repeat it in `data.event_kind`.
Every request carries `Webhook-Id` (stable across retries) and `Webhook-Timestamp`; signed
requests also carry `Webhook-Signature`, per Standard Webhooks.

## Signing is opt-in

Deliveries are signed only when the agent config sets `event_webhook.signing_secret_id`.
Without it they still carry `Webhook-Id` and `Webhook-Timestamp` but cannot be verified.
Hookdeck's configuration alert for this source reads exactly:

> "Signing is opt-in per agent: set event_webhook.signing_secret_id, then paste that secret here as whsec_ followed by the secret. Unsigned deliveries fail verification."

See [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/omnara-webhooks/references/setup.md).

## When to Use This Skill

* How do I receive Omnara webhooks (`event_webhook`)?
* How do I verify Omnara webhook signatures (`webhook-signature`)?
* How do I handle `tool_call_update`, `tool_result` or `model_output` events?
* Why is my Omnara webhook signature verification failing, or rejecting Hookdeck retries?

## Verification (core)

Signed content is `{webhook-id}.{webhook-timestamp}.{raw body}`, signed with HMAC-SHA256 and
base64-encoded. `webhook-signature` holds one or more space-separated `v1,<base64>` entries.
The secret is bare base64 (from `openssl rand -base64 32`); the HMAC key is its base64-decoding.
The official `standardwebhooks` library (npm and pip) checks every `v1` entry in constant time,
strips a `whsec_` prefix if present, and otherwise base64-decodes the whole secret, so the bare
value and the `whsec_`-prefixed form Hookdeck needs give the same key. Read the `webhook-*`
headers and, when `webhook-id` is absent, fall back to the `svix-*` headers (`svix-id`,
`svix-timestamp`, `svix-signature`), as Hookdeck does.

```javascript
const { Webhook } = require('standardwebhooks');

const prefix = req.headers['webhook-id'] ? 'webhook' : 'svix';
// rawBody: the raw Buffer/string, never re-serialized JSON
const payload = new Webhook(process.env.OMNARA_WEBHOOK_SECRET).verify(rawBody, {
  'webhook-id': req.headers[`${prefix}-id`],
  'webhook-timestamp': req.headers[`${prefix}-timestamp`],
  'webhook-signature': req.headers[`${prefix}-signature`],
}); // throws on a bad signature or a timestamp outside the window; returns the parsed JSON

```

```python
from standardwebhooks.webhooks import Webhook

prefix = "webhook" if headers.get("webhook-id") else "svix"
payload = Webhook(os.environ["OMNARA_WEBHOOK_SECRET"]).verify(raw_body, {
    "webhook-id": headers.get(f"{prefix}-id"),
    "webhook-timestamp": headers.get(f"{prefix}-timestamp"),
    "webhook-signature": headers.get(f"{prefix}-signature"),
})

```

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/omnara-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/omnara-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/omnara-webhooks/examples/fastapi/).

Keep the secret exactly as Omnara shows it in `OMNARA_WEBHOOK_SECRET`. Handlers fail closed:
with the variable unset they return 500. A missing header or bad signature gets 400. With
Express, read the body with `express.raw({ type: '*/*' })` so any content type reaches the
verifier as the exact bytes Omnara signed, and parse JSON only after the check.

### Hookdeck retries and replays hit the 5-minute window

The `standardwebhooks` library always enforces a fixed 5-minute timestamp window that can't be
configured. Hookdeck retries and replays resend the **original** `webhook-timestamp`, so the
library rejects any retry or replay more than 5 minutes after the original delivery. If you
verify behind Hookdeck (or replay old events), use the manual verifier in
[references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/omnara-webhooks/references/verification.md), where the window can be dropped or
widened. Deduplicate on `webhook-id` instead of relying on the timestamp.

## Common Event Types

5 event types are documented (the list may not be exhaustive). The name is `event` in the body,
and `event_webhook.events` is a required, nonempty allowlist of them. The examples handle all five:

| Event | Triggered When |
| --- | --- |
| `agent_input` | Input (user content, etc.) is added to an agent's timeline |
| `model_output` | The model produces output (`stop_reason`, `content_blocks`) |
| `tool_result` | A tool call finishes (`tool_call_id`, `outcome`) |
| `context_checkpoint` | The context is summarized (`summary`, `summarized_through_event_sequence`) |
| `tool_call_update` | A tool call or interaction changes state (a notification to fetch the details) |

Delivery is at-least-once and unordered; see [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/omnara-webhooks/references/overview.md)
for payload shapes, retries and deduplication.

## Environment Variables

```bash
# Signing secret exactly as stored in Omnara (bare base64; a whsec_ prefix also works)
OMNARA_WEBHOOK_SECRET=your_base64_secret

```

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 omnara --path /webhooks/omnara

```

Set the agent's `event_webhook.url` to the tunnel URL, with `events` and `signing_secret_id` set.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/omnara-webhooks/references/overview.md) - Omnara webhook concepts, payloads, events, delivery
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/omnara-webhooks/references/setup.md) - Configure `event_webhook`, create the signing secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/omnara-webhooks/references/verification.md) - Verification details, header fallback, manual verifier, timestamp window