# Ultravox Webhooks

Ultravox (ultravox.ai) is a realtime voice-AI platform. Account webhooks notify
your server about the call lifecycle. Each delivery is a `POST` signed with
HMAC-SHA256 using the webhook's secret.

## When to Use This Skill

* How do I receive Ultravox webhooks?
* How do I verify Ultravox webhook signatures?
* Why is my Ultravox webhook signature verification failing?
* How do I handle `call.started`, `call.joined`, `call.ended` or `call.billed` events?
* Why do I get a 401 for a webhook with several secrets, or none?

## Secret Setup (important)

> Give the Ultravox webhook exactly one secret and paste it here. A webhook without a secret is unsigned, and one with several secrets sends a list of signatures, so both fail verification.

## Verification (core)

Headers: `X-Ultravox-Webhook-Signature` (bare lowercase hex digest, no prefix) and
`X-Ultravox-Webhook-Timestamp` (ISO 8601). The signature is hex HMAC-SHA256, keyed
with the secret (UTF-8 bytes, used as-is), over the **raw body bytes followed
directly by the timestamp header value, with no separator**.

```javascript
const HEX_DIGEST = /^[0-9a-fA-F]{64}$/;
// rawBody: Buffer from express.raw({ type: '*/*' }) - NOT parsed JSON
if (!sig || !ts || !HEX_DIGEST.test(sig)) return res.status(401).end();
const signed = Buffer.concat([rawBody, Buffer.from(ts, 'latin1')]); // body, then timestamp
const expected = crypto.createHmac('sha256', secret).update(signed).digest();
const received = Buffer.from(sig.toLowerCase(), 'hex'); // safe: format validated above
if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {
  return res.status(401).end();
}

```

* Compare the **whole** header as one digest. Do not split on commas or strip a prefix.
* Missing/empty signature or timestamp, malformed value, or mismatch: respond `401`.
* Verify first, then `JSON.parse`. Never re-serialize the body.
* **Timestamp window is off by default.** Hookdeck retries and replays resend the original
  timestamp, so a fixed window would reject legitimate retries. To opt in, set
  `ULTRAVOX_WEBHOOK_TOLERANCE_SECONDS` (unset or `0` = off); Ultravox's own example rejects
  anything older than 1 minute.
* Respond `204` (Ultravox's recommendation). Errors and unavailability are retried 10 times,
  starting around 30 s and doubling each time; duplicates can occur, so handle events idempotently.

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

## Common Event Types

Dispatch on the body's `event` field.

| Event | Fires When |
| --- | --- |
| `call.started` | A call has started |
| `call.joined` | A participant has joined the call |
| `call.ended` | A call has ended |
| `call.billed` | A call has been billed |

> **For payload details**, see [Ultravox webhook events](https://docs.ultravox.ai/webhooks/available-webhooks).

## Environment Variables

```bash
ULTRAVOX_WEBHOOK_SECRET=your_ultravox_webhook_secret_here   # the webhook's single secret
ULTRAVOX_WEBHOOK_TOLERANCE_SECONDS=0                       # optional; 0/unset = no timestamp check

```

## Local Development

For local webhook testing, run the Hookdeck CLI via `npx` (no install, no account required):

```bash
npx hookdeck-cli listen 3000 ultravox --path /webhooks/ultravox

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/ultravox-webhooks/references/overview.md) - Webhook basics, events, delivery and retries
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/ultravox-webhooks/references/setup.md) - Create a webhook and set its secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/ultravox-webhooks/references/verification.md) - Signature verification details and gotchas
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/ultravox-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests