> 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".
curl "https://skillshub.wtf/jeremylongshore/claude-code-plugins-plus-skills/assemblyai-webhooks-events?format=md"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
assemblyaipackage installed- API key configured
How AssemblyAI Webhooks Work
- You submit a transcription with
webhook_urlparameter - AssemblyAI processes the audio asynchronously
- When done (completed or error), AssemblyAI sends a POST to your URL
- 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
| Issue | Cause | Solution |
|---|---|---|
| Webhook not received | URL not accessible from internet | Verify HTTPS URL, check firewall |
| 401 on webhook | Wrong auth header value | Match webhook_auth_header_value from submission |
| Duplicate processing | Webhook retried after timeout | Implement idempotency (check transcript_id) |
| Webhook timeout | Processing > 10 seconds | Return 200 immediately, process async |
| Missing transcript data | Fetching too early | Fetch with client.transcripts.get() after webhook |
Resources
Next Steps
For performance optimization, see assemblyai-performance-tuning.
> related_skills --same-repo
> fathom-cost-tuning
Optimize Fathom API usage and plan selection. Trigger with phrases like "fathom cost", "fathom pricing", "fathom plan".
> fathom-core-workflow-b
Sync Fathom meeting data to CRM and build automated follow-up workflows. Use when integrating Fathom with Salesforce, HubSpot, or custom CRMs, or creating automated post-meeting email summaries. Trigger with phrases like "fathom crm sync", "fathom salesforce", "fathom follow-up", "fathom post-meeting workflow".
> fathom-core-workflow-a
Build a meeting analytics pipeline with Fathom transcripts and summaries. Use when extracting insights from meetings, building CRM sync, or creating automated meeting follow-up workflows. Trigger with phrases like "fathom analytics", "fathom meeting pipeline", "fathom transcript analysis", "fathom action items sync".
> fathom-common-errors
Diagnose and fix Fathom API errors including auth failures and missing data. Use when API calls fail, transcripts are empty, or webhooks are not firing. Trigger with phrases like "fathom error", "fathom not working", "fathom api failure", "fix fathom".