# Soniox Webhooks

Soniox sends a webhook when an async speech-to-text transcription finishes. Requests are **not
signed**. You choose a secret header when you create each transcription
(`webhook_auth_header_name` and `webhook_auth_header_value`), Soniox sends that header on the
webhook request, and your endpoint authenticates the request by comparing it with the value you
stored.

> Soniox webhooks are not signed. Send webhook_auth_header_name and webhook_auth_header_value with every async transcription, and enter the same name and value here.

## When to Use This Skill

* How do I receive Soniox webhooks?
* How do I authenticate Soniox async transcription webhooks?
* Does Soniox sign webhook payloads? (No: use the custom header)
* How do I set `webhook_auth_header_name` and `webhook_auth_header_value`?
* How do I handle a transcription with status `completed` or `error`?
* Why does my Soniox webhook endpoint return 401?

## Verification (core)

```javascript
const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifySonioxRequest(headers, {
  name = process.env.SONIOX_WEBHOOK_HEADER || 'Authorization',
  secret = process.env.SONIOX_WEBHOOK_SECRET,
} = {}) {
  if (!secret) return false;                  // never accept when unconfigured
  const received = headers[name.toLowerCase()]; // Node lowercases header names
  return Boolean(received) && safeEqual(received, secret); // whole value, no Bearer parsing
}

```

* The header name is yours: whatever you pass as `webhook_auth_header_name`. Read it from
  `SONIOX_WEBHOOK_HEADER` (default `Authorization`) and look it up case-insensitively.
* Compare the **whole** header value with `SONIOX_WEBHOOK_SECRET`, exactly as you passed it as
  `webhook_auth_header_value`. If you sent `Bearer <my-secret-token>`, the secret is that full
  string including `Bearer `. Don't strip or parse the prefix.
* Return `401` for a missing header or a mismatch, `400` for a body that isn't a JSON object,
  `200` once handled.
* Read the body raw (`express.raw({ type: '*/*' })`) and authenticate before parsing it.
* **A static shared credential authenticates the sender, not the body.** There is no signature
  and no timestamp, so there is no replay protection. Use HTTPS only, and rotate the credential
  (use a new header value on new transcriptions and update `SONIOX_WEBHOOK_SECRET`) if it leaks.

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

## Payload and Statuses

The body is a small JSON object with no event name: `{ "id": "...", "status": "completed" }`.
`id` is the transcription ID; fetch the transcript with the Soniox API using that ID.

| `status` | Meaning |
| --- | --- |
| `completed` | The transcription finished; fetch the transcript by `id` |
| `error` | The transcription failed; look up the transcription by `id` for details |

## Environment Variables

```bash
SONIOX_WEBHOOK_HEADER=Authorization                 # same as webhook_auth_header_name
SONIOX_WEBHOOK_SECRET=Bearer your-secret-token      # same as webhook_auth_header_value, whole value

```

## Local Development

Soniox can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

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

```

Use the printed URL as `webhook_url` when you create a transcription.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/soniox-webhooks/references/overview.md) - What Soniox webhooks are, statuses and payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/soniox-webhooks/references/setup.md) - Create a transcription with the webhook and header fields
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/soniox-webhooks/references/verification.md) - Header check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/soniox-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests