Platform / WebhooksSign inRequest access

Webhooks

Get a signed request when a transcription finishes, instead of polling.

Register an endpoint

Create an endpoint in the console (Webhooks → Add endpoint) or with the API. The URL must be HTTPS and publicly reachable. We immediately send it a signed verification event; answer it with any 2xx and the endpoint is verified. The response includes the signing secret — shown once — and verified.

curl "$TWENTYSIX_BASE_URL/v1/webhook_endpoints" \
  -H "Authorization: Bearer $TWENTYSIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/hooks/transcripts"}'

The verification event looks like this — verify its signature like any other event and reply 2xx:

JSON
{ "type": "webhook_endpoint.verification", "data": { "id": "wh_…" } }

Only verified endpoints can be named in a transcription; an unverified one is refused with 409 conflict. If verification failed, fix the endpoint and call POST /v1/webhook_endpoints/{id}/verify (or choose Verify in the console).

Then name the endpoint in each transcription that should notify it:

JSON
{ "audio": { "upload_id": "upl_…" }, "language": "bn-IN", "webhook_endpoint_id": "wh_…" }

Events

When the transcription finishes we send one POST with a small JSON body. It never contains the transcript — fetch that with your API key.

JSON
{ "type": "transcription.completed", "data": { "id": "tr_…", "state": "completed" } }
EventWhen
transcription.completedThe full transcript is ready.
transcription.partialA partial transcript is ready (see partial results).
transcription.failedIt could not be transcribed; data.error.code says why.
transcription.cancelledIt was cancelled.

Reply with any 2xx status within ten seconds. Other replies, timeouts and redirects count as failures and are retried with increasing delays for about a day. Every attempt is listed in the console under Webhooks. A transcription's state never depends on your endpoint: if deliveries fail, the result is still there to fetch.

Verify the signature

Every request carries three headers:

HeaderValue
webhook-idUnique per event — use it to ignore duplicates.
webhook-timestampUnix seconds when it was sent.
webhook-signaturev1, followed by the base64 HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{raw body}, keyed with the base64-decoded part of your secret after whsec_.

Check the signature against the raw body before parsing it, and reject timestamps more than five minutes old.

from twentysix import verify_webhook

@app.post("/hooks/transcripts")
def hook(request):
    if not verify_webhook(SECRET, request.headers, request.body):
        return Response(status=400)
    event = json.loads(request.body)
    if event["type"] in ("transcription.completed", "transcription.partial"):
        result = client.result(event["data"]["id"])
    return Response(status=204)

Manage endpoints

CallDoes
GET /v1/webhook_endpointsList endpoints (secrets are never listed).
POST /v1/webhook_endpoints/{id}/verifySend the verification event again.
POST /v1/webhook_endpoints/{id}/rotate_secretNew secret, shown once. For 24 hours every event carries signatures from both the new and the previous secret, so you can switch without missing events.
DELETE /v1/webhook_endpoints/{id}Disable the endpoint; no further events.
GET /v1/webhook_endpoints/{id}/deliveriesDelivery history: status, attempts, last error.
POST /v1/webhook_deliveries/{id}/replaySend an event again — same webhook-id, so your de-duplication still works.

The same actions are in the console under Webhooks (Verify, Rotate secret, Resend).

Delivery behaviour

  • Events can arrive more than once; deduplicate on webhook-id.
  • Events are not guaranteed to arrive in order. Fetch the transcription if you need its latest state.
  • Endpoints that resolve to private or internal addresses are refused, at registration and on every delivery.
Scriba is a product of 26AI Private Limited, Pune, India.© 2026 26AI Private Limited