Implementation:Elevenlabs Elevenlabs python WebhookSignatureVerification
| Field | Value |
|---|---|
| sources | src/elevenlabs/webhooks_custom.py
|
| domains | Webhook Security, HMAC-SHA256 Signature Verification, Event Construction |
| last_updated | 2026-02-15 |
Overview
The WebhookSignatureVerification module provides custom webhook clients that extend the auto-generated ElevenLabs webhook clients. The primary purpose is to verify incoming webhook payloads using HMAC-SHA256 signature verification, ensuring that events originate from ElevenLabs and have not been tampered with.
Both synchronous (WebhooksClient) and asynchronous (AsyncWebhooksClient) variants are provided. Each exposes a single construct_event method that:
- Validates the presence of a signature header and webhook secret.
- Parses the
t=(timestamp) andv0=(signature) components from the header. - Rejects requests with timestamps older than 30 minutes (replay attack protection).
- Computes the expected HMAC-SHA256 digest of
{timestamp}.{rawBody}using the webhook secret. - Compares the computed digest against the provided signature.
- Returns the parsed JSON event payload if verification succeeds.
Typical usage:
from elevenlabs import ElevenLabs
client = ElevenLabs(api_key="your_api_key")
event = client.webhooks.construct_event(
rawBody=request.body,
sig_header=request.headers["elevenlabs-signature"],
secret="your_webhook_secret",
)
Code Reference
Source file: src/elevenlabs/webhooks_custom.py
WebhooksClient (Synchronous)
class WebhooksClient(AutogeneratedWebhooksClient):
def construct_event(self, rawBody: str, sig_header: str, secret: str) -> Dict: ...
AsyncWebhooksClient (Asynchronous)
class AsyncWebhooksClient(AutogeneratedAsyncWebhooksClient):
def construct_event(self, rawBody: str, sig_header: str, secret: str) -> Dict: ...
Note: The AsyncWebhooksClient.construct_event method is not declared as async because all operations (HMAC computation, string parsing, JSON decoding) are CPU-bound and do not require awaiting I/O.
Import statement:
from elevenlabs.webhooks_custom import WebhooksClient, AsyncWebhooksClient
Dependencies
| Module | Usage |
|---|---|
hmac |
HMAC computation for signature verification |
hashlib |
SHA-256 hash algorithm |
json |
Parsing the raw body into a Python dictionary |
time |
Current time for timestamp tolerance check |
elevenlabs.errors.BadRequestError |
Error raised on verification failure |
elevenlabs.types.bad_request_error_body.BadRequestErrorBody |
Error body structure |
I/O Contract
construct_event
Input Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
rawBody |
str |
Yes | The raw webhook request body as a string. Must be the raw body, not a parsed JSON object. |
sig_header |
str |
Yes | The signature header from the webhook request (e.g., t=1234567890,v0=abcdef...).
|
secret |
str |
Yes | Your webhook secret provided by ElevenLabs for HMAC verification. |
Return Value:
| Type | Description |
|---|---|
Dict |
The verified webhook event payload parsed from the raw body JSON string. |
Raises:
| Exception | Condition | Error Message |
|---|---|---|
BadRequestError |
sig_header is empty or falsy |
"Missing signature header"
|
BadRequestError |
secret is empty or falsy |
"Webhook secret not configured"
|
BadRequestError |
No t= or v0= components found in header |
"No signature hash found with expected scheme v0"
|
BadRequestError |
Timestamp is older than 30 minutes | "Timestamp outside the tolerance zone"
|
BadRequestError |
Computed HMAC does not match provided signature | "Signature hash does not match the expected signature hash for payload"
|
Signature Format
The signature header follows the format:
t=<unix_timestamp_seconds>,v0=<hex_encoded_hmac_sha256>
The HMAC message is constructed as:
{timestamp}.{rawBody}
The digest is computed using:
"v0=" + hmac.new(secret.encode("utf-8"), message.encode("utf-8"), hashlib.sha256).hexdigest()
Timestamp Tolerance
The verification enforces a 30-minute tolerance window. The timestamp from the header (in seconds) is converted to milliseconds and compared against the current time minus 30 minutes:
req_timestamp = int(timestamp) * 1000
tolerance = int(time.time() * 1000) - 30 * 60 * 1000
if req_timestamp < tolerance:
raise BadRequestError(...)
Usage Examples
Flask Webhook Endpoint
from flask import Flask, request, jsonify
from elevenlabs import ElevenLabs
from elevenlabs.errors import BadRequestError
app = Flask(__name__)
client = ElevenLabs(api_key="your_api_key")
WEBHOOK_SECRET = "whsec_your_secret_here"
@app.route("/webhook", methods=["POST"])
def handle_webhook():
try:
event = client.webhooks.construct_event(
rawBody=request.get_data(as_text=True),
sig_header=request.headers.get("elevenlabs-signature", ""),
secret=WEBHOOK_SECRET,
)
except BadRequestError as e:
return jsonify({"error": "Invalid signature"}), 400
# Process the verified event
event_type = event.get("type")
print(f"Received event: {event_type}")
return jsonify({"status": "ok"}), 200
FastAPI Webhook Endpoint
from fastapi import FastAPI, Request, HTTPException
from elevenlabs import ElevenLabs
from elevenlabs.errors import BadRequestError
app = FastAPI()
client = ElevenLabs(api_key="your_api_key")
WEBHOOK_SECRET = "whsec_your_secret_here"
@app.post("/webhook")
async def handle_webhook(request: Request):
body = await request.body()
sig_header = request.headers.get("elevenlabs-signature", "")
try:
event = client.webhooks.construct_event(
rawBody=body.decode("utf-8"),
sig_header=sig_header,
secret=WEBHOOK_SECRET,
)
except BadRequestError as e:
raise HTTPException(status_code=400, detail="Invalid webhook signature")
# Process the verified event
return {"status": "received", "event_type": event.get("type")}
Manual Verification for Testing
import hmac
import hashlib
import time
from elevenlabs import ElevenLabs
client = ElevenLabs(api_key="your_api_key")
# Simulate a webhook payload for testing
secret = "test_secret"
body = '{"type": "tts.completed", "data": {"id": "abc123"}}'
timestamp = str(int(time.time()))
message = f"{timestamp}.{body}"
signature = "v0=" + hmac.new(
secret.encode("utf-8"),
message.encode("utf-8"),
hashlib.sha256,
).hexdigest()
sig_header = f"t={timestamp},{signature}"
# Verify it
event = client.webhooks.construct_event(
rawBody=body,
sig_header=sig_header,
secret=secret,
)
print(event) # {"type": "tts.completed", "data": {"id": "abc123"}}
Related Pages
- Elevenlabs_Elevenlabs_python_Package_Exports - Package-level exports including the webhooks submodule
- Elevenlabs_Elevenlabs_python_MusicCustomClient - Custom music client with multipart response parsing
- Elevenlabs_Elevenlabs_python_RawBaseClient - Low-level raw HTTP client used by the SDK