Webhooks
Receive real-time notifications when transcriptions complete or fail instead of polling.
Overview
When you provide a webhook_url in your transcription request, we'll send a POST request to that URL when the job finishes. Webhooks are signed with HMAC-SHA256 so you can verify they came from Scriptivox.
Webhooks fire for both upload-based and URL-based transcription flows.
You can also set a default webhook URL in your account settings, which will be used for all transcriptions that don't specify one.
Setting up webhooks
Pass a webhook_url when starting a transcription:
requests.post("https://api.scriptivox.com/v1/transcribe",headers={"Authorization": "sk_live_YOUR_KEY"},json={"upload_id": "abc-123","webhook_url": "https://your-server.com/webhook"})
Webhook events
transcription.downloading
Sent when the file download begins from the provided URL. URL flow only — not sent for upload-based transcriptions since the file is already uploaded.
{"event": "transcription.downloading","transcription_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901","status": "downloading"}
transcription.processing
Sent when the audio file has been downloaded and validated, and the transcription job has been submitted for processing. Includes the detected duration and reserved cost.
{"event": "transcription.processing","transcription_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901","status": "processing","duration_seconds": 120,"cost_cents": 0.5}
Both URL-based and upload-based transcriptions receive this event after the file has been validated and the job is submitted.
transcription.completed
Sent when a transcription finishes successfully. Includes the full result.
{"event": "transcription.completed","transcription_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901","status": "completed","duration_seconds": 120,"cost_cents": 0.5,"result": {"full_transcript": "Hello, thanks for joining...","language": "en","duration_seconds": 120,"speakers": ["SPEAKER 1", "SPEAKER 2"],"utterances": [{"start": 0.5,"end": 3.2,"text": "Hello, thanks for joining the call today.","speaker": "SPEAKER 1","confidence": 0.95,"words": [...]}]}}
transcription.failed
Sent when a transcription fails. Reserved balance is automatically released. This can fire from the download/validation stage or the transcription processing stage.
{"event": "transcription.failed","transcription_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901","status": "failed","error": {"code": "PROCESSING_ERROR","message": "Failed to process audio file"}}
Events summary
| Event | When | Flows |
|---|---|---|
| transcription.downloading | File download started from URL | URL only |
| transcription.processing | File validated, job submitted | Both |
| transcription.completed | Transcription finished successfully | Both |
| transcription.failed | Download, validation, or processing failed | Both |
Verifying webhook signatures
Every webhook request includes two headers for signature verification:
| Header | Description |
|---|---|
| X-Scriptivox-Signature | HMAC-SHA256 hex digest of the signed payload |
| X-Scriptivox-Timestamp | Unix timestamp (seconds) of when the webhook was sent |
Signing formula
secret = SHA256(api_key)
signature = HMAC-SHA256(secret, timestamp + "." + body)
import hmacimport hashlibimport timedef get_signing_secret(api_key: str) -> str:"""Your signing secret is SHA256 of your API key."""return hashlib.sha256(api_key.encode()).hexdigest()def verify_webhook(body: bytes, signature: str, timestamp: str, api_key: str) -> bool:# Check timestamp is recent (within 5 minutes)now = int(time.time())if abs(now - int(timestamp)) > 300:return False# Compute expected signature using SHA256(api_key) as secretsigning_secret = get_signing_secret(api_key)message = f"{timestamp}.{body.decode()}"expected = hmac.new(signing_secret.encode(),message.encode(),hashlib.sha256).hexdigest()return hmac.compare_digest(expected, signature)# In your webhook handler:# signature = request.headers["X-Scriptivox-Signature"]# timestamp = request.headers["X-Scriptivox-Timestamp"]# is_valid = verify_webhook(request.body, signature, timestamp, API_KEY)
Delivery behavior
Webhooks are delivered on a best-effort, fire-and-forget basis. If your endpoint is unreachable or returns an error, the webhook is not retried. The transcription still completes normally regardless of webhook delivery status.
For reliable delivery, we recommend using webhooks as a speed optimization alongside polling GET /v1/transcribe/{id} as a fallback.
Best practices
- Return 200 quickly — Process the webhook payload asynchronously. Respond with 200 before doing heavy processing.
- Always verify signatures — Check the HMAC-SHA256 signature before trusting any payload.
- Check timestamps — Reject webhooks with timestamps older than 5 minutes to prevent replay attacks.
- Be idempotent — The same webhook may be delivered more than once. Use the transcription ID to deduplicate.
- Use HTTPS — Always use HTTPS for production webhook URLs. HTTP is allowed for local development only.
- Use polling as fallback — Since webhooks are not retried, poll
GET /v1/transcribe/{id}as a backup to ensure you don't miss completions.