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_name and webhook_auth_header_value?
  • How do I handle completed and error transcript 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_auth_header_value; 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:

StatusSent When
completedThe transcript finished; GET /v2/transcript/{transcript_id} for the text
errorTranscription failed; the same GET returns the error message

The body carries nothing else: {"transcript_id": "5552493-16d8-42d8-8feb-c2a16b56f6e8", "status": "completed"}.

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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 3, 2026

View on GitHub →