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.
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:
{
"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
}| Header | Value |
|---|---|
X-SR-Signature | sha256=<hex>: HMAC-SHA256 of the raw body, keyed with your signing secret. |
X-SR-Event | completed or failed, the same as status. |
X-SR-Delivery | A unique id per delivery. Retries reuse it, so use it to drop duplicates. |
User-Agent | SpeechRevolutions-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.
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.
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
- Job lifecycle: fetch a job's status yourself, for example to reconcile after downtime.
- FastAPI, Django and Next.js: a receiver wired into a full app.
- Cookbook: batch submission with webhooks.