# Mistral Webhooks

## When to Use This Skill

* How do I receive Mistral voice agent webhooks?
* How do I verify the `x-mistral-signature` header?
* How do I handle `call_started`, `call_ended`, and `call_failed` events?
* Why is my Mistral webhook signature verification failing?
* How do I store call transcripts from Mistral AI Studio voice agents?

## Verification (core)

Mistral signs the **raw request body** with HMAC-SHA256, keyed with the endpoint signing secret (used as a UTF-8 string). The hex digest is sent in `x-mistral-signature` as `hmac-sha256=<hex>`. Strip the `hmac-sha256=` prefix, then compare timing-safe. There is no timestamp in the signed content.

Node:

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

function verify(rawBody, header, secret) {
  if (!header || !header.startsWith('hmac-sha256=')) return false;
  const received = header.slice('hmac-sha256='.length);
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  try {
    return crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  } catch {
    return false; // different lengths
  }
}

```

Python:

```python
import hmac, hashlib

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    if not header or not header.startswith("hmac-sha256="):
        return False
    received = header.removeprefix("hmac-sha256=")
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(received.encode(), expected.encode())

```

Mistral does not ship a webhook verification helper in its SDKs, so verification is manual in every framework.

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

## Common Event Types

Every delivery is a JSON `POST` with the envelope `{ "event": "<type>", "created_at": "<ISO 8601>", "data": { ... } }`. The event type is in the body, not a header.

| Event | Sent When |
| --- | --- |
| `call_started` | A session is created and the voice pipeline is ready |
| `call_ended` | A session ends normally (includes `duration_seconds` and a best-effort `transcript`) |
| `call_failed` | A session fails to start or hits a fatal error (`error` field, e.g. `livekit_error`) |

> **For payload fields**, see [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/mistral-webhooks/references/overview.md) or [Mistral webhooks docs](https://docs.mistral.ai/studio/audio/voice-agents/webhooks).

## Important Headers

| Header | Description |
| --- | --- |
| `x-mistral-signature` | `hmac-sha256=<hex digest>` of the raw body |
| `content-type` | `application/json` |

## Delivery and Retries

Respond within 10 seconds. Any 2xx is success. Mistral retries 5xx, 429 and transport errors (3 attempts in total, backoff 2s then 4s) and does not retry other 4xx. Deduplicate with `session_id` + `event`.

## Environment Variables

```bash
MISTRAL_WEBHOOK_SECRET=your_endpoint_signing_secret   # Generated when you create the endpoint; cannot be rotated

```

## Local Development

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

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/mistral-webhooks/references/overview.md) - Mistral webhook concepts and payloads
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/mistral-webhooks/references/setup.md) - Endpoint and subscription setup
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/mistral-webhooks/references/verification.md) - Signature verification details