Agent skill
AssemblyAI Webhooks Skill
Receive and authenticate AssemblyAI transcript webhooks. Use when setting up an AssemblyAI webhook endpoint, checking the webhook_auth_header_name / webhook_auth_header_value header, debugging 401s on deliveries, or handling completed and error transcript statuses.
Install this skill
npx skills add hookdeck/webhook-skills --skill assemblyai-webhooks
AssemblyAI calls your webhook_url once when a pre-recorded transcript finishes. The request is not signed: you authenticate it with a custom header whose name and value you set on the transcript request. The payload is just {transcript_id, status}.
When to Use This Skill
- How do I receive AssemblyAI webhooks?
- How do I verify an AssemblyAI webhook? (There's no signature: check your auth header)
- How do I use
webhook_auth_header_nameandwebhook_?auth_ header_ value - How do I handle
completedanderrortranscript statuses? - Why does my AssemblyAI webhook endpoint return 401 and never get retried?
Verification (core)
Create the transcript with webhook_url, webhook_auth_header_name and webhook_; AssemblyAI sends that header verbatim. Compare it in constant time:
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyAssemblyAIRequest(headers, config = process.env) {
const name = (config.ASSEMBLYAI_WEBHOOK_AUTH_HEADER_NAME || '').toLowerCase();
const expected = config.ASSEMBLYAI_WEBHOOK_AUTH_HEADER_VALUE;
if (!name || !expected) return false; // never accept when unconfigured
const received = headers[name]; // Node lowercases header names
return typeof received === 'string' && safeEqual(received, expected);
}
Return 401 when this fails and 200 once handled. There is no payload signature, so the raw body isn't part of verification, but authenticate before parsing it (Express: use express.raw({ type: '*/*' })). A 4xx is not retried by AssemblyAI.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Event Types
There is no event-type field; status in the payload is the signal:
| Status | Sent When |
|---|---|
completed | The transcript finished; GET /v2/transcript/{transcript_ for the text |
error | Transcription failed; the same GET returns the error message |
The body carries nothing else: {"transcript_.
Delivery Behavior
Return a 2xx within 10 seconds. Timeouts and 5xx are retried up to 10 attempts, 10 seconds apart; any 4xx marks the delivery failed with no retry. Senders use fixed IPs 44.238.19.20 (US) and 54.220.25.36 (EU), an optional allowlist on top of the header check.
Environment Variables
ASSEMBLYAI_WEBHOOK_AUTH_HEADER_NAME=X-My-Webhook-Secret # your chosen header name
ASSEMBLYAI_WEBHOOK_AUTH_HEADER_VALUE=replace-with-a-long-random-secret
Local Development
AssemblyAI can't reach localhost. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 assemblyai --path /webhooks/assemblyai
Use the printed URL as webhook_url when creating the transcript.
Resources
- references/overview.md - How AssemblyAI webhooks work, payload, retry behavior
- references/setup.md - Set the webhook fields on a transcript request
- references/verification.md - Header check, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests