ElevenLabs signs each webhook with a secret key and includes:
A timestamp (t=) to prevent replay attacks
A signature hash (v0=) to verify authenticity
from elevenlabs import ElevenLabs, BadRequestErrorclient = ElevenLabs(api_key="YOUR_API_KEY")try: event = client.webhooks.construct_event( rawBody=request_body, # Must be raw string, not parsed JSON sig_header="t=1234567890,v0=hash_value", secret="your_webhook_secret" ) # Event is verified and safe to use print(event)except BadRequestError as e: # Signature verification failed print(f"Error: {e.body.error}")
The async client also supports webhook verification:
from elevenlabs import AsyncElevenLabs, BadRequestErrorfrom fastapi import FastAPI, Request, HTTPExceptionapp = FastAPI()client = AsyncElevenLabs(api_key="YOUR_API_KEY")@app.post("/webhook")async def handle_webhook(request: Request): raw_body = await request.body() signature = request.headers.get("elevenlabs-signature") try: # Note: construct_event is synchronous even on async client event = client.webhooks.construct_event( rawBody=raw_body.decode("utf-8"), sig_header=signature, secret="your_webhook_secret" ) # Process asynchronously await process_event_async(event) return {"success": True} except BadRequestError as e: raise HTTPException(status_code=400, detail=str(e.body.error))async def process_event_async(event: dict): # Your async processing logic event_type = event.get("type") print(f"Processing event: {event_type}")
The construct_event method is synchronous even when using AsyncElevenLabs because it only performs local computation (HMAC verification). No API calls are made.
During development, use tools like ngrok to expose your local server:
# Install ngrokbrew install ngrok # macOS# or download from https://ngrok.com# Start your webhook serverpython webhook_server.py# In another terminal, expose itngrok http 5000# Use the ngrok URL in ElevenLabs dashboard# Example: https://abc123.ngrok.io/webhook
Use ngrok or a similar tool to test webhooks locally without deploying to a server.
Always return a 200 status code when the webhook is processed successfully, even if your business logic encounters non-critical errors.
Return 400 for invalid signatures
Return 400 when signature verification fails to indicate a client error.
Handle retries gracefully
ElevenLabs will retry failed webhooks. Make your endpoint idempotent to handle duplicate events.
Respond quickly
Process webhooks asynchronously if needed. Respond within 30 seconds to avoid timeouts.
Important: Always use the raw request body for signature verification. Parsing it as JSON first will cause verification to fail due to formatting differences.