> assemblyai-webhooks-events

Implement AssemblyAI webhook handling for transcription completion events. Use when setting up webhook endpoints, handling transcription callbacks, or processing async transcription results via webhooks. Trigger with phrases like "assemblyai webhook", "assemblyai events", "assemblyai transcription callback", "handle assemblyai webhook".

fetch
$curl "https://skillshub.wtf/jeremylongshore/claude-code-plugins-plus-skills/assemblyai-webhooks-events?format=md"
SKILL.mdassemblyai-webhooks-events

AssemblyAI Webhooks & Events

Overview

Handle AssemblyAI webhooks for transcription completion. When you submit a transcript with webhook_url, AssemblyAI sends a POST request to your URL when the transcript is completed or fails. One webhook per transcript — no complex event routing needed.

Prerequisites

  • HTTPS endpoint accessible from the internet
  • assemblyai package installed
  • API key configured

How AssemblyAI Webhooks Work

  1. You submit a transcription with webhook_url parameter
  2. AssemblyAI processes the audio asynchronously
  3. When done (completed or error), AssemblyAI sends a POST to your URL
  4. Your endpoint receives transcript ID and status, then fetches the full transcript

Key difference from other APIs: AssemblyAI webhooks are per-transcript (set at submission time), not a global webhook registration. There are no event types to subscribe to — you get one callback per transcript.

Instructions

Step 1: Submit Transcription with Webhook

import { AssemblyAI } from 'assemblyai';

const client = new AssemblyAI({
  apiKey: process.env.ASSEMBLYAI_API_KEY!,
});

// submit() queues the job and returns immediately (doesn't poll)
const transcript = await client.transcripts.submit({
  audio: 'https://example.com/meeting-recording.mp3',
  webhook_url: 'https://your-app.com/webhooks/assemblyai',

  // Optional: auth header for webhook verification
  webhook_auth_header_name: 'X-Webhook-Secret',
  webhook_auth_header_value: process.env.ASSEMBLYAI_WEBHOOK_SECRET!,

  // Enable features — results will be available when webhook fires
  speaker_labels: true,
  sentiment_analysis: true,
  auto_highlights: true,
});

console.log('Submitted:', transcript.id);
// Returns immediately, webhook fires when processing completes

Step 2: Webhook Endpoint (Express.js)

import express from 'express';
import { AssemblyAI, type Transcript } from 'assemblyai';

const app = express();
const client = new AssemblyAI({
  apiKey: process.env.ASSEMBLYAI_API_KEY!,
});

app.post('/webhooks/assemblyai', express.json(), async (req, res) => {
  // Step 1: Verify authenticity via custom auth header
  const secret = req.headers['x-webhook-secret'];
  if (secret !== process.env.ASSEMBLYAI_WEBHOOK_SECRET) {
    console.warn('Webhook auth failed');
    return res.status(401).json({ error: 'Unauthorized' });
  }

  // Step 2: Extract payload
  const { transcript_id, status } = req.body;
  console.log(`Webhook received: ${transcript_id} — ${status}`);

  // Step 3: Respond quickly (within 10 seconds)
  res.status(200).json({ received: true });

  // Step 4: Process asynchronously
  try {
    if (status === 'completed') {
      const transcript = await client.transcripts.get(transcript_id);
      await processCompletedTranscript(transcript);
    } else if (status === 'error') {
      await handleFailedTranscript(transcript_id, req.body.error);
    }
  } catch (error) {
    console.error('Webhook processing error:', error);
  }
});

async function processCompletedTranscript(transcript: Transcript) {
  console.log(`Processing transcript ${transcript.id}:`);
  console.log(`  Text: ${transcript.text?.length} chars`);
  console.log(`  Duration: ${transcript.audio_duration}s`);
  console.log(`  Speakers: ${transcript.utterances?.length ?? 0} utterances`);

  // Store in database, notify user, trigger LeMUR analysis, etc.

  // Example: Run LeMUR summarization after transcription completes
  if (transcript.text && transcript.text.length > 100) {
    const { response } = await client.lemur.summary({
      transcript_ids: [transcript.id],
      answer_format: 'bullet points',
    });
    console.log('Auto-summary:', response);
  }
}

async function handleFailedTranscript(transcriptId: string, error?: string) {
  console.error(`Transcript ${transcriptId} failed: ${error}`);
  // Alert ops team, retry with different settings, etc.
}

app.listen(3000, () => console.log('Listening on :3000'));

Step 3: Webhook Endpoint (Next.js App Router)

// app/api/webhooks/assemblyai/route.ts
import { AssemblyAI } from 'assemblyai';
import { NextRequest, NextResponse } from 'next/server';

const client = new AssemblyAI({
  apiKey: process.env.ASSEMBLYAI_API_KEY!,
});

export async function POST(req: NextRequest) {
  const secret = req.headers.get('x-webhook-secret');
  if (secret !== process.env.ASSEMBLYAI_WEBHOOK_SECRET) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  const body = await req.json();
  const { transcript_id, status } = body;

  if (status === 'completed') {
    const transcript = await client.transcripts.get(transcript_id);
    // Process transcript...
    console.log(`Completed: ${transcript_id}, ${transcript.text?.length} chars`);
  }

  return NextResponse.json({ received: true });
}

Step 4: Idempotent Processing

// Prevent duplicate processing if webhook is retried
const processedTranscripts = new Set<string>();
// In production, use Redis or a database instead of in-memory Set

async function idempotentProcess(transcriptId: string, handler: () => Promise<void>) {
  if (processedTranscripts.has(transcriptId)) {
    console.log(`Already processed: ${transcriptId}`);
    return;
  }

  await handler();
  processedTranscripts.add(transcriptId);
}

// Usage in webhook handler:
await idempotentProcess(transcript_id, async () => {
  const transcript = await client.transcripts.get(transcript_id);
  await processCompletedTranscript(transcript);
});

Step 5: Testing Webhooks Locally

# Option 1: ngrok
ngrok http 3000
# Use the HTTPS URL as your webhook_url

# Option 2: Simulate webhook manually
curl -X POST http://localhost:3000/webhooks/assemblyai \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Secret: your-secret" \
  -d '{
    "transcript_id": "test-id-123",
    "status": "completed"
  }'

Webhook Payload Reference

AssemblyAI sends a POST with this JSON body:

{
  "transcript_id": "6wij2z3g66-...",
  "status": "completed"
}

For errors:

{
  "transcript_id": "6wij2z3g66-...",
  "status": "error",
  "error": "Download error: unable to download audio from URL"
}

If redact_pii_audio was enabled, a second webhook fires when redacted audio is ready.

Output

  • Webhook endpoint that receives transcription completion events
  • Auth header verification for secure webhook handling
  • Idempotent processing to handle retries
  • LeMUR auto-analysis triggered on completion

Error Handling

IssueCauseSolution
Webhook not receivedURL not accessible from internetVerify HTTPS URL, check firewall
401 on webhookWrong auth header valueMatch webhook_auth_header_value from submission
Duplicate processingWebhook retried after timeoutImplement idempotency (check transcript_id)
Webhook timeoutProcessing > 10 secondsReturn 200 immediately, process async
Missing transcript dataFetching too earlyFetch with client.transcripts.get() after webhook

Resources

Next Steps

For performance optimization, see assemblyai-performance-tuning.

┌ stats

installs/wk0
░░░░░░░░░░
github stars1.7K
██████████
first seenMar 23, 2026
└────────────

┌ repo

jeremylongshore/claude-code-plugins-plus-skills
by jeremylongshore
└────────────