Verify ElevenLabs post-call webhooks before you trust them. Stdlib only.
The post-call webhook is where the ground truth about a voice agent's conversation arrives: the transcript, the tool calls, the tool results. Everything downstream that matters, billing, CRM writes, call grading (grounded-check runs on exactly this data), inherits its trust from this one endpoint. An unverified webhook receiver is an open door that accepts "the customer agreed to $9,999" from anyone who can find the URL.
ElevenLabs signs every delivery:
elevenlabs-signature: t=<unix_seconds>,v0=<hex>
v0 = HMAC-SHA256(secret, "<timestamp>.<raw_body>")
This tool implements that check, verified against the official SDK source rather than guessed, with two deliberate departures:
- Signatures are compared with
hmac.compare_digest, not!=. A plain comparison leaks how many leading bytes matched through timing. - Timestamps too far in the future are rejected too. The reference check only rejects old ones, so a captured request with a forged far-future timestamp would stay replayable forever if the clock check is your only replay defense.
export ELEVENLABS_WEBHOOK_SECRET=...
python webhook_guard.py serve --port 8000 # reference receiver, verified events as JSONL
python webhook_guard.py sign payload.json # forge a valid header for local testing
The sign command is the part you did not know you wanted: it produces the exact
header ElevenLabs would send for a payload, so you can test your real receiver end to
end with curl instead of waiting for a live call to finish.
As a library it is one function:
from webhook_guard import verify, SignatureError
try:
event = verify(raw_body, request.headers["elevenlabs-signature"], secret)
except SignatureError:
return 401Pass the raw request body, byte for byte. A body that has been parsed and re-serialized will not verify, because key order and whitespace are part of the signed message. There is a test asserting exactly that failure, since it is the single most common way webhook verification goes wrong in production.
Tests: python -m unittest -v.
- It authenticates the envelope, not the contents. A verified webhook proves ElevenLabs sent it, not that the agent behaved. Auditing what the agent said against what its tools returned is the other tool's job.
- No queueing, retries, or storage. Verify, then hand the event to whatever you already trust.
MIT license.