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".
What this skill does
# 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
```typescript
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)
```typescript
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)
```typescript
// 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
```typescript
// 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
```bash
# 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:
```json
{
"transcript_id": "6wij2z3g66-...",
"status": "completed"
}
```
For errors:
```json
{
"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
- [AssemblyAI Webhooks Guide](https://www.assemblyai.com/docs/getting-started/webhooks)
- [Webhook API Reference](https://www.assemblyai.com/docs/api-reference/transcripts/submit)
- [Streaming Webhooks](https://www.assemblyai.com/docs/streaming/webhooks)
## Next Steps
For performance optimization, see `assemblyai-performance-tuning`.
Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.