> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chariot.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Transcription webhooks

> Receive signed notifications when asynchronous files or batches reach a terminal state.

Add a `webhook_url` when you [create a batch](https://docs.chariot.in/guides/speech-to-text/async-transcription.md). Chariot sends an HTTP POST when a subscribed terminal event occurs. Intermediate transcripts and progress updates are available through polling, not webhooks.

## Subscribe to events

```json theme={null}
{
  "inputs": [
    { "custom_id": "meeting-1", "audio_url": "https://media.example.com/meeting.wav" }
  ],
  "webhook_url": "https://your-app.example.com/webhooks/chariot",
  "webhook_events": [
    "transcription.completed",
    "transcription.failed",
    "transcription.cancelled",
    "batch.completed",
    "batch.partially_completed",
    "batch.failed",
    "batch.cancelled"
  ]
}
```

The default events are `transcription.completed`, `transcription.failed`, and `transcription.cancelled`, one per file. Explicitly include batch events if you also want an aggregate notification. An explicit `webhook_events` list replaces the default list.

Your destination must be a publicly reachable HTTPS endpoint without URL credentials or fragments. Redirects are not followed. The accepted response includes a `webhook_signing_secret` for that batch. Save it securely on your server with the batch ID. A matching idempotent resubmission also returns the secret; batch GET responses do not.

## Receive a file event

```json theme={null}
{
  "id": "cdd7a254-a774-421f-882a-6d736499a848",
  "type": "transcription.completed",
  "created_at": "2026-10-26T14:50:00Z",
  "batch_id": "1f4b3eed-2fdf-4495-b988-2b7145127058",
  "transcription_id": "9cb6f4c5-b2bf-4d75-96e3-716f6f87d994",
  "custom_id": "meeting-1",
  "status": "completed",
  "result_url": "/v1/asr/transcriptions/9cb6f4c5-b2bf-4d75-96e3-716f6f87d994"
}
```

The file event `transcription_id` matches the accepted response `items[].id` and the transcription resource `id`. `custom_id` echoes your input identifier and is only unique within its batch. The event `id` identifies the notification, not the audio or transcription.

The notification contains a result path, not the transcript. Fetch that path from `https://api.chariot.in` using your API key. For a failed or cancelled file, the same route provides its error, billing state, and any saved partial result.

A batch event has `counts` instead of `transcription_id` and `custom_id`. Its `result_url` points to `/v1/asr/batches/{batch_id}`. Counts include `total` and the statuses present in that batch.

## Verify the signature

Each delivery includes:

| Header | Value |
| - | - |
| `Chariot-Event-Id` | Event ID, also present as `id` in the body. |
| `Chariot-Signature` | `t=<unix_seconds>,v1=<hex_signature>`. |

Calculate HMAC-SHA256 over the exact bytes of `<timestamp>.<event_id>.<raw_body>`, using the returned batch secret as a UTF-8 string. Do not hex-decode the secret or parse and reserialize the body before verifying it.

```python theme={null}
import hashlib
import hmac
import time


def verify_chariot_webhook(raw_body: bytes, headers, secret: str) -> bool:
    # headers must provide case-insensitive HTTP header lookup.
    try:
        fields = dict(
            part.strip().split("=", 1)
            for part in headers["Chariot-Signature"].split(",")
        )
        timestamp = fields["t"]
        event_id = headers["Chariot-Event-Id"]
        # This example uses a five-minute replay window. Keep your clock synced.
        if abs(time.time() - int(timestamp)) > 300:
            return False
        message = f"{timestamp}.{event_id}.".encode() + raw_body
        expected = hmac.new(
            secret.encode(), message, hashlib.sha256
        ).hexdigest()
        return hmac.compare_digest(expected, fields["v1"])
    except (KeyError, ValueError, TypeError):
        return False
```

Use the batch ID only to look up the expected secret before verification. Treat all other payload contents as untrusted until verification succeeds. Check that the verified body's `id` matches `Chariot-Event-Id` and that the batch belongs to your application.

## Acknowledge and deduplicate

After verification, durably record the event and return a `2xx` response promptly. Process the notification in your own background work if needed. Non-`2xx` responses and connection failures trigger retries with backoff.

Delivery can happen more than once, and file and batch events can arrive out of order. Deduplicate by `Chariot-Event-Id`. Retries retain the event ID and body but use a fresh signature timestamp. Acknowledge already-recorded events successfully.

Retries are bounded, so polling remains your recovery path if a notification never arrives. Retrieve the current resource state instead of treating delivery order as processing order.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.