Gareth Wilson Gareth Wilson

Guide to ElevenLabs Webhooks: Features and Best Practices

Published


ElevenLabs webhooks notify your application when an async voice or call job reports back: a call transcription is ready, or a voice is scheduled for removal or removed. If you're building on ElevenLabs, webhooks are how these long-running jobs tell you they're done without polling.

This guide covers how ElevenLabs webhooks work, the events you'll handle, how to verify the ElevenLabs-Signature with the official SDK, and the best practices for production.

What are ElevenLabs webhooks?

ElevenLabs webhooks are JSON POSTs delivered to a URL you configure. Each is signed with an ElevenLabs-Signature header formatted t=<timestamp>,v0=<signature>. The signature is an HMAC-SHA256 (hex) over <timestamp> + "." + raw_body, keyed with your webhook signing secret. The header can carry more than one v0= value, and any one matching is valid. ElevenLabs rejects deliveries outside a 30-minute window to guard against replay. The official @elevenlabs/elevenlabs-js SDK verifies all of this for you.

ElevenLabs webhook features

FeatureDetails
ConfigurationElevenLabs dashboard > Settings > General > Webhooks
Signature headerElevenLabs-Signature, formatted t=<ts>,v0=<sig>
Signature schemeHMAC-SHA256 (hex) over <timestamp>.<raw_body>
Replay window30 minutes; multiple v0= values, any match is valid
SDK@elevenlabs/elevenlabs-js webhooks.constructEvent (Node); manual HMAC for Python
Delivery200 expected within 10s; auto-disable after 10 consecutive failures; may arrive out of order

Common events

ElevenLabs has four event types:

EventFires when
post_call_transcriptionCall analysis and transcription complete
voice_removal_noticeA voice is scheduled for removal
voice_removal_notice_withdrawnA voice removal notice is cancelled
voice_removedA voice is removed from the account

The payload envelope is { type, data, event_timestamp }; branch on type.

Setting up ElevenLabs webhooks

In the ElevenLabs dashboard, go to Settings > General > Webhooks, add your endpoint URL, select events, and create the webhook. Copy the signing secret immediately (it won't be shown again) into ELEVENLABS_WEBHOOK_SECRET. Install the current SDK with npm install @elevenlabs/elevenlabs-js, not the outdated elevenlabs v1.x package.

Securing ElevenLabs webhooks

Use the official SDK's webhooks.constructEvent, which verifies the ElevenLabs-Signature and returns the parsed event (throwing on an invalid signature). It needs the raw request body, so capture it before parsing.

const { ElevenLabsClient } = require("@elevenlabs/elevenlabs-js");

const elevenlabs = new ElevenLabsClient({ apiKey: process.env.ELEVENLABS_API_KEY || "webhook-only" });
const SECRET = process.env.ELEVENLABS_WEBHOOK_SECRET;

app.post("/webhooks/elevenlabs", express.raw({ type: "application/json" }), async (req, res) => {
  const signature = req.headers["elevenlabs-signature"];
  const rawBody = Buffer.isBuffer(req.body) ? req.body.toString("utf8") : req.body;
  try {
    const event = await elevenlabs.webhooks.constructEvent(rawBody, signature, SECRET);
    res.sendStatus(200); // acknowledge fast
    processQueue.add(event); // branch on event.type, async
  } catch {
    res.sendStatus(401); // invalid signature or stale timestamp
  }
});

The ElevenLabs Python SDK doesn't provide webhook verification, so verify manually, mirroring the t=,v0= scheme, the 30-minute window, and multiple signatures:

import hashlib
import hmac
import os
import time

SECRET = os.environ["ELEVENLABS_WEBHOOK_SECRET"].encode()

def verify(raw_body: bytes, signature_header: str) -> bool:
    params = dict(kv.split("=", 1) for kv in (signature_header or "").split(",") if "=" in kv)
    timestamp = params.get("t", "")
    if abs(time.time() - int(timestamp)) > 1800:  # 30-minute window
        return False
    signed = (timestamp + ".").encode() + raw_body
    expected = hmac.new(SECRET, signed, hashlib.sha256).hexdigest()
    # The header may carry multiple v0= values; any match is valid
    provided = [v[3:] for v in (signature_header or "").split(",") if v.startswith("v0=")]
    return any(hmac.compare_digest(expected, sig) for sig in provided)

ElevenLabs webhook limitations and pain points

The signed message is timestamp.body

The Problem: The HMAC is over <timestamp> + "." + raw_body, not the body alone. Signing the body by itself never matches.

Why It Happens: ElevenLabs binds the timestamp into the signed message, Stripe-style.

Workarounds:

  • Let the SDK reconstruct and verify it, or build timestamp + "." + body manually.

How Hookdeck Can Help: Hookdeck verifies the signature at the edge, so your app receives pre-verified events.

Use the current SDK, not the old package

The Problem: The verification helper lives in @elevenlabs/elevenlabs-js. The outdated elevenlabs (v1.x) package doesn't provide it, and the Python SDK doesn't verify webhooks at all.

Why It Happens: The SDK was renamed, and Python lacks a verification helper.

Workarounds:

  • Install @elevenlabs/elevenlabs-js for Node; verify manually in Python.

How Hookdeck Can Help: Hookdeck verifies at the edge, so a missing or renamed SDK isn't a blocker.

The 30-minute replay window

The Problem: Deliveries older than 30 minutes are rejected. A slow queue, clock skew, or replaying old events for testing trips this.

Why It Happens: ElevenLabs enforces a 30-minute freshness window.

Workarounds:

  • Keep server clocks in sync, and don't replay events older than the window into the verifier.

How Hookdeck Can Help: Hookdeck verifies freshness at the edge and durably queues events, so downstream slowness doesn't fail verification.

Out-of-order delivery

The Problem: Events may arrive out of order, so a later state can land before an earlier one.

Why It Happens: Delivery isn't strictly ordered.

Workarounds:

  • Order by event_timestamp, and make handlers idempotent.

How Hookdeck Can Help: Hookdeck durably queues events so your worker can reconcile ordering at its own pace.

Best practices

Verify with the official SDK over the raw body

Use webhooks.constructEvent(rawBody, signature, secret) in Node; verify the t=,v0= HMAC manually in Python.

Respect the 30-minute window and multiple signatures

Reject stale deliveries, and accept if any v0= value matches.

Order by event_timestamp

Events can arrive out of order, so sort on event_timestamp and dedupe.

Acknowledge fast, process asynchronously

Return 200 within 10 seconds and defer work to a queue. See why to process webhooks asynchronously.

Make ElevenLabs webhooks production-ready

Hookdeck verifies ElevenLabs-Signature, deduplicates, and durably queues every event

Conclusion

ElevenLabs webhooks are verified with an ElevenLabs-Signature HMAC-SHA256 over <timestamp>.<raw_body>, formatted t=<ts>,v0=<sig> with a 30-minute window and possibly multiple signatures. Use the official @elevenlabs/elevenlabs-js constructEvent in Node (verify manually in Python), respect the freshness window, and order by event_timestamp.

Hookdeck verifies the signature, deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique events.

Get started with Hookdeck for free and handle ElevenLabs webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.