技能 人工智能 ElevenLabs Webhook事件处理

ElevenLabs Webhook事件处理

v20260423
elevenlabs-webhooks-events
本指南提供了处理ElevenLabs异步Webhooks的完整方案。它详细演示了如何接收和验证来自ElevenLabs的POST通知(包括转录完成、通话音频和失败事件),重点实现HMAC-SHA256签名验证,确保数据传输的安全性。适用于将ElevenLabs集成到对话式AI流程中,实现可靠的后台数据处理和事件管理。
获取技能
371 次下载
概览

ElevenLabs Webhooks & Events

Overview

ElevenLabs webhooks send HTTP POST notifications when async operations complete. Supported event types include transcription completion, post-call data from Conversational AI agents, and call initiation failures. Webhooks use HMAC-SHA256 signatures for verification.

Prerequisites

  • ElevenLabs account (webhooks configured in Settings > Webhooks)
  • HTTPS endpoint accessible from the internet
  • Webhook secret (generated during webhook creation in dashboard)

Instructions

Step 1: Webhook Event Types

Event Type Payload When Triggered
post_call_transcription Full conversation transcript, analysis, metadata After Conversational AI call ends
post_call_audio Base64-encoded call audio, minimal metadata After call ends (if audio recording enabled)
call_initiation_failure Failure reason, metadata When an outbound call fails to connect
speech_to_text.completed Transcription result, word timestamps Async STT job completes

Step 2: Webhook Setup

# Create webhook in ElevenLabs dashboard:
# Settings > Webhooks > Create Webhook
# - URL: https://your-app.com/webhooks/elevenlabs
# - Select event types to subscribe to
# - Copy the generated HMAC secret

Step 3: HMAC Signature Verification

// src/elevenlabs/webhook-verify.ts
import crypto from "crypto";

/**
 * Verify the ElevenLabs-Signature header using HMAC-SHA256.
 *
 * Header format: t=<unix_timestamp>,v1=<hex_signature>
 * Signed payload: "<timestamp>.<raw_body>"
 */
export function verifyWebhookSignature(
  rawBody: string | Buffer,
  signatureHeader: string,
  secret: string
): { valid: boolean; reason?: string } {
  if (!signatureHeader || !secret) {
    return { valid: false, reason: "Missing signature header or secret" };
  }

  // Parse header: t=1234567890,v1=abcdef...
  const parts = new Map(
    signatureHeader.split(",").map(p => {
      const [key, ...val] = p.split("=");
      return [key, val.join("=")] as [string, string];
    })
  );

  const timestamp = parts.get("t");
  const signature = parts.get("v1");

  if (!timestamp || !signature) {
    return { valid: false, reason: "Malformed signature header" };
  }

  // Replay protection: reject if older than 5 minutes
  const age = Math.floor(Date.now() / 1000) - parseInt(timestamp);
  if (age > 300) {
    return { valid: false, reason: `Timestamp too old: ${age}s` };
  }

  // Compute expected HMAC
  const signedPayload = `${timestamp}.${rawBody.toString()}`;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(signedPayload)
    .digest("hex");

  // Timing-safe comparison
  try {
    const isValid = crypto.timingSafeEqual(
      Buffer.from(signature, "hex"),
      Buffer.from(expected, "hex")
    );
    return { valid: isValid };
  } catch {
    return { valid: false, reason: "Signature length mismatch" };
  }
}

Step 4: Express Webhook Handler

// src/api/webhooks/elevenlabs.ts
import express from "express";
import { verifyWebhookSignature } from "../../elevenlabs/webhook-verify";

const router = express.Router();

// CRITICAL: Use raw body parser for signature verification
router.post("/webhooks/elevenlabs",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const signature = req.headers["elevenlabs-signature"] as string;
    const secret = process.env.ELEVENLABS_WEBHOOK_SECRET!;

    const { valid, reason } = verifyWebhookSignature(req.body, signature, secret);

    if (!valid) {
      console.error("Webhook verification failed:", reason);
      return res.status(401).json({ error: "Invalid signature" });
    }

    // Return 200 immediately to prevent webhook auto-disable
    res.status(200).json({ received: true });

    // Process asynchronously
    const event = JSON.parse(req.body.toString());
    processEvent(event).catch(err =>
      console.error("Webhook processing failed:", err)
    );
  }
);

// Event routing
async function processEvent(event: any) {
  const eventType = event.type || event.event_type;

  switch (eventType) {
    case "post_call_transcription":
      await handleTranscription(event);
      break;
    case "post_call_audio":
      await handleCallAudio(event);
      break;
    case "call_initiation_failure":
      await handleCallFailure(event);
      break;
    case "speech_to_text.completed":
      await handleSTTCompleted(event);
      break;
    default:
      console.log("Unhandled event type:", eventType);
  }
}

Step 5: Event Handlers

// Conversational AI post-call transcript
async function handleTranscription(event: any) {
  const {
    conversation_id,
    transcript,       // Full conversation text
    analysis,         // AI analysis of the call
    metadata,         // Custom metadata from agent config
    recording_url,    // Audio recording URL (if enabled)
  } = event.data;

  console.log(`[Transcript] Conversation ${conversation_id}`);
  console.log(`Transcript: ${transcript?.substring(0, 200)}...`);

  // Store in your database
  // await db.conversations.upsert({ conversation_id, transcript, analysis });
}

// Post-call audio recording
async function handleCallAudio(event: any) {
  const {
    conversation_id,
    audio_base64,     // Base64-encoded audio of the full conversation
  } = event.data;

  if (audio_base64) {
    const audioBuffer = Buffer.from(audio_base64, "base64");
    console.log(`[Audio] Received ${audioBuffer.length} bytes for ${conversation_id}`);
    // Save audio: await fs.writeFile(`recordings/${conversation_id}.mp3`, audioBuffer);
  }
}

// Failed outbound call
async function handleCallFailure(event: any) {
  const {
    conversation_id,
    failure_reason,
    metadata,
  } = event.data;

  console.error(`[Call Failed] ${conversation_id}: ${failure_reason}`);
  // Alert: await alerting.notify("Call initiation failed", { conversation_id, failure_reason });
}

// Async Speech-to-Text completion
async function handleSTTCompleted(event: any) {
  const {
    transcription_id,
    text,
    words,           // Word-level timestamps
    language,
  } = event.data;

  console.log(`[STT Complete] ${transcription_id}: ${language}`);
  console.log(`Text: ${text?.substring(0, 200)}...`);
  // Process transcription results
}

Step 6: Idempotency Protection

// Prevent duplicate processing if ElevenLabs retries delivery
const processedEvents = new Set<string>();

async function withIdempotency(
  eventId: string,
  handler: () => Promise<void>
): Promise<void> {
  if (processedEvents.has(eventId)) {
    console.log(`Event ${eventId} already processed, skipping`);
    return;
  }

  await handler();
  processedEvents.add(eventId);

  // Clean up old entries (in production, use Redis with TTL)
  if (processedEvents.size > 10000) {
    const oldest = Array.from(processedEvents).slice(0, 5000);
    oldest.forEach(id => processedEvents.delete(id));
  }
}

Step 7: Local Testing with ngrok

# Expose local server to internet
ngrok http 3000

# Use the ngrok URL as webhook endpoint in ElevenLabs dashboard
# https://abc123.ngrok.io/webhooks/elevenlabs

# Test with curl (simulated event)
curl -X POST http://localhost:3000/webhooks/elevenlabs \
  -H "Content-Type: application/json" \
  -H "ElevenLabs-Signature: t=$(date +%s),v1=test" \
  -d '{"type":"speech_to_text.completed","data":{"text":"Hello world"}}'

Webhook Reliability

Behavior Detail
Retry policy ElevenLabs retries failed deliveries
Auto-disable After 10 consecutive failures AND 7+ days since last success
Timeout Your endpoint must respond within a few seconds
Re-enable Manually re-enable in dashboard after fixing the endpoint
Authentication HMAC-SHA256 via ElevenLabs-Signature header

Error Handling

Issue Cause Solution
Signature mismatch Wrong secret or body parsing Use express.raw(), verify secret matches dashboard
Webhook auto-disabled 10+ consecutive failures Fix endpoint, re-enable in dashboard
Duplicate events Retried delivery Implement idempotency with event ID tracking
Handler timeout Slow processing Return 200 immediately, process async
Replay attack Old timestamp reused Check timestamp age (reject > 5 min)

Resources

Next Steps

For performance optimization, see elevenlabs-performance-tuning.

信息
Category 人工智能
Name elevenlabs-webhooks-events
版本 v20260423
大小 9.33KB
更新时间 2026-04-28
语言