Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Implement ElevenLabs webhook HMAC signature verification and event handling. Use when setting up webhook endpoints for transcription completion, call recording, or agent conversation events from ElevenLabs. Trigger with "elevenlabs webhook", "elevenlabs events", "elevenlabs webhook signature", "handle elevenlabs notifications", "elevenlabs post-call webhook", "elevenlabs transcription webhook".
.claude/skills/jeremylongshore-elevenlabs-webhooks-events/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 28% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 9% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 34% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 17% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 80% | 0% |
ElevenLabs webhooks send HTTP POST notifications when async operations complete: transcription completion, post-call data from Conversational AI agents, and call initiation failures. Every delivery is signed with an HMAC-SHA256 signature you must verify before processing. This skill builds a secure endpoint that verifies signatures, routes events by type, and acks fast to avoid auto-disable.
The full, copy-ready code for each step lives in references/implementation.md; per-event handlers live in references/examples.md. The high-level workflow:
"<timestamp>.<raw_body>", using a timing-safe compare and a 5-minute replay window. See the full verifier.200 immediately, then process asynchronously. See the Express handler.| 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 |
typescript// src/elevenlabs/webhook-verify.ts — Header: t=<unix_ts>,v1=<hex_sig> export function verifyWebhookSignature(rawBody, signatureHeader, secret) { const parts = new Map(signatureHeader.split(",").map(p => { const [k, ...v] = p.split("="); return [k, v.join("=")]; })); const timestamp = parts.get("t"), signature = parts.get("v1"); if (Math.floor(Date.now() / 1000) - parseInt(timestamp) > 300) { return { valid: false, reason: "Timestamp too old" }; // replay guard } const expected = crypto.createHmac("sha256", secret) .update(`${timestamp}.${rawBody.toString()}`).digest("hex"); return { valid: crypto.timingSafeEqual( Buffer.from(signature, "hex"), Buffer.from(expected, "hex")) }; }
See references/implementation.md for the production-hardened version with full error handling.
Applying this skill produces:
src/elevenlabs/webhook-verify.ts — reusable HMAC-SHA256 verifier with replay protection and timing-safe comparison.src/api/webhooks/elevenlabs.ts — Express route that verifies signatures, acks 200 immediately, and routes events to per-type handlers.handleTranscription, handleCallAudio, handleCallFailure, handleSTTCompleted) extracting the fields each payload carries.At runtime a verified delivery returns { "received": true } with HTTP 200; a bad signature or expired timestamp returns HTTP 401 { "error": "Invalid signature" }.
| 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 |
| 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) |
Route a decoded event to the right handler:
typescriptswitch (event.type || event.event_type) { 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:", event.type); }
Simulate a delivery locally with curl:
bashcurl -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"}}'
Full per-event handlers (transcript, audio, call-failure, STT) with the exact fields each payload carries are in references/examples.md.
For performance optimization, see the elevenlabs-performance-tuning skill, which covers connection pooling and batching to keep webhook handlers fast enough to ack within the ElevenLabs timeout window.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 43,830 | 32,661 | -25% | 1 | 1 | 0% | 5,641 | 7,212 | +28% | 0 | 0 | — |
case-02 | fail→pass | 32,955 | 26,997 | -18% | 1 | 1 | 0% | 5,522 | 6,043 | +9% | 0 | 0 | — |
case-03 | fail→pass | 33,056 | 24,202 | -27% | 1 | 1 | 0% | 5,402 | 7,216 | +34% | 0 | 0 | — |
case-04 | pass→pass | 25,593 | 17,447 | -32% | 1 | 1 | 0% | 3,927 | 4,465 | +14% | 0 | 0 | — |
case-05 | fail→pass | 13,594 | 7,363 | -46% | 1 | 1 | 0% | 2,541 | 2,979 | +17% | 0 | 0 | — |
case-06 | pass→pass | 11,046 | 13,052 | +18% | 1 | 1 | 0% | 2,188 | 3,568 | +63% | 0 | 0 | — |
case-07 | pass→pass | 14,194 | 18,230 | +28% | 1 | 1 | 0% | 2,525 | 3,884 | +54% | 0 | 0 | — |
case-08 | fail→pass | 10,354 | 7,889 | -24% | 1 | 1 | 0% | 1,532 | 2,763 | +80% | 0 | 0 | — |
case-09 | fail→fail | 20,746 | 17,808 | -14% | 1 | 1 | 0% | 3,575 | 4,442 | +24% | 0 | 0 | — |
case-10 | fail→fail | 7,583 | 3,443 | -55% | 1 | 1 | 0% | 1,477 | 2,285 | +55% | 0 | 0 | — |
case-11 | fail→pass | 20,531 | 4,432 | -78% | 1 | 1 | 0% | 4,139 | 2,217 | -46% | 0 | 0 | — |
case-12 | fail→pass | 11,460 | 4,820 | -58% | 1 | 1 | 0% | 1,701 | 2,282 | +34% | 0 | 0 | — |
case-13 | fail→pass | 10,851 | 3,964 | -63% | 1 | 1 | 0% | 1,796 | 2,230 | +24% | 0 | 0 | — |
case-14 | pass→pass | 17,226 | 13,933 | -19% | 1 | 1 | 0% | 2,944 | 4,208 | +43% | 0 | 0 | — |
case-15 | pass→pass | 13,481 | 12,077 | -10% | 1 | 1 | 0% | 2,774 | 4,031 | +45% | 0 | 0 | — |
case-16 | fail→fail | 6,358 | 2,617 | -59% | 1 | 1 | 0% | 1,117 | 2,084 | +87% | 0 | 0 | — |
case-17 | fail→fail | 4,694 | 5,111 | +9% | 1 | 1 | 0% | 863 | 2,355 | +173% | 0 | 0 | — |
case-18 | pass→pass | 11,261 | 7,444 | -34% | 1 | 1 | 0% | 2,079 | 3,055 | +47% | 0 | 0 | — |
case-19 | pass→pass | 13,952 | 9,900 | -29% | 1 | 1 | 0% | 2,169 | 3,051 | +41% | 0 | 0 | — |
case-20 | pass→pass | 27,452 | 22,767 | -17% | 1 | 1 | 0% | 3,380 | 5,134 | +52% | 0 | 0 | — |
case-21 | pass→pass | 14,058 | 22,516 | +60% | 1 | 1 | 0% | 2,643 | 5,196 | +97% | 0 | 0 | — |
case-22 | pass→pass | 20,952 | 26,264 | +25% | 1 | 1 | 0% | 4,470 | 6,288 | +41% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +36 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.