SKILL.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'));
