Webhooks

Pass a callback_url and Speech Revolutions POSTs a signed notification to it when the job finishes, so your server can submit work and move on instead of holding a connection open.

Get your signing secret

Every webhook is signed with your organization's signing secret. Find it in the console under API Keys → Webhook signing secret: choose Reveal, copy it, and store it where your receiver can read it — the examples below use SPEECHREVOLUTIONS_WEBHOOK_SECRET.

bash
export SPEECHREVOLUTIONS_WEBHOOK_SECRET="whsec_…"

The secret belongs to the organization, not to an API key: webhooks for jobs from any of its keys are signed with it. Anyone who can create API keys can reveal it; owners and admins can rotate it.

Ask for a webhook

Add callback_url when you submit a job. submit() returns as soon as the job is queued; the webhook tells you when it is done.

from speechrevolutions import SpeechRevolutions

client = SpeechRevolutions()  # reads SPEECHREVOLUTIONS_API_KEY
client.submit("meeting.mp3", callback_url="https://you.example.com/webhooks/stt")

The callback URL must be https or http and resolve to a public address.

What we send

One POST when the job completes or fails permanently. The body is JSON:

json
{
  "job_id": "3f1c…",
  "status": "completed",          // or "failed"
  "download_url": "https://…",    // completed only; fetch the transcript from here
  "step": "…",                    // failed only: where it failed
  "reason": "…"                   // failed only: why
}
HeaderValue
X-SR-Signaturesha256=<hex>: HMAC-SHA256 of the raw body, keyed with your signing secret.
X-SR-Eventcompleted or failed, the same as status.
X-SR-DeliveryA unique id per delivery. Retries reuse it, so use it to drop duplicates.
User-AgentSpeechRevolutions-Webhook/1

Respond with any 2xx to acknowledge. A 5xx, a timeout (10 seconds per attempt) or a connection error is retried with backoff, up to 4 attempts in all. A 4xx is final and not retried.

Verify the signature

Compute the HMAC over the exact bytes you received — not a re-serialized object, which can reorder keys or change spacing — and compare with a constant-time check. Reject anything that doesn't match.

import hashlib
import hmac
import json
import os

from fastapi import FastAPI, HTTPException, Request

SECRET = os.environ["SPEECHREVOLUTIONS_WEBHOOK_SECRET"]
app = FastAPI()


def verify_signature(raw_body: bytes, signature_header: str) -> bool:
    expected = "sha256=" + hmac.new(SECRET.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header or "")


@app.post("/webhooks/stt")
async def receive(request: Request):
    raw = await request.body()  # the exact bytes received
    if not verify_signature(raw, request.headers.get("X-SR-Signature", "")):
        raise HTTPException(status_code=401, detail="bad signature")
    event = json.loads(raw)
    if event["status"] == "completed":
        ...  # fetch event["download_url"]
    else:
        ...  # event["step"], event["reason"]
    return {"ok": True}

Test your receiver locally

Sign a sample body with your secret and send it the way we would. A receiver that verifies correctly accepts this and rejects the same request with the body changed.

bash
BODY='{"job_id":"test","status":"completed","download_url":"https://example.com/t.json"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SPEECHREVOLUTIONS_WEBHOOK_SECRET" -hex | sed 's/^.* //')

curl -X POST http://localhost:8000/webhooks/stt \
  -H "Content-Type: application/json" \
  -H "X-SR-Signature: sha256=$SIG" \
  --data "$BODY"

Rotate the secret

If the secret leaks, an owner or admin can choose Rotate on the same console card. Webhooks sent after that are signed with the new secret, and a receiver still using the old one rejects them — so update your receivers right after rotating. Every reveal and rotation is recorded in the organization's activity log.

Keep it server-side

Anyone with the signing secret can forge webhooks that your receiver will accept. Keep it in your server's environment or secret manager, never in client-side code or a repository.

Next steps