Implement Retell AI webhook signature validation and event handling. Use when setting up webhook endpoints, implementing signature verification, or handling Retell AI event notifications securely. Trigger with phrases like "retellai webhook", "retellai events", "retellai webhook signature", "handle retellai events", "retellai notifications".
From retellai-packnpx claudepluginhub nickloveinvesting/nick-love-plugins --plugin retellai-packThis skill is limited to using the following tools:
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Details PluginEval's skill quality evaluation: 3 layers (static, LLM judge), 10 dimensions, rubrics, formulas, anti-patterns, badges. Use to interpret scores, improve triggering, calibrate thresholds.
Handle Retell AI webhooks for real-time voice call lifecycle events. Retell AI fires webhooks when calls start, end, or encounter events during conversation.
RETELL_API_KEY environment variable| Event | Trigger | Payload |
|---|---|---|
call_started | AI agent picks up | Call ID, agent ID, from/to numbers |
call_ended | Call completes | Call ID, duration, end reason, transcript |
call_analyzed | Post-call analysis done | Sentiment, summary, custom data |
agent_transfer | Transfer to human | Call ID, transfer number, context |
voicemail_detected | Voicemail reached | Call ID, voicemail status |
call_error | Call fails | Error code, error message |
import express from "express";
import crypto from "crypto";
const app = express();
app.post("/webhooks/retellai",
express.raw({ type: "application/json" }),
async (req, res) => {
const signature = req.headers["x-retell-signature"] as string;
const secret = process.env.RETELL_WEBHOOK_SECRET!;
const expected = crypto
.createHmac("sha256", secret)
.update(req.body)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).json({ error: "Invalid signature" }); # HTTP 401 Unauthorized
}
const event = JSON.parse(req.body.toString());
res.status(200).json({ received: true }); # HTTP 200 OK
await handleRetellEvent(event);
}
);
interface RetellWebhookPayload {
event: string;
call: {
call_id: string;
agent_id: string;
call_status: string;
from_number: string;
to_number: string;
duration_ms?: number;
transcript?: string;
call_analysis?: {
sentiment: string;
summary: string;
custom_analysis_data: Record<string, any>;
};
};
}
async function handleRetellEvent(payload: RetellWebhookPayload) {
switch (payload.event) {
case "call_started":
await handleCallStarted(payload.call);
break;
case "call_ended":
await handleCallEnded(payload.call);
break;
case "call_analyzed":
await handleCallAnalyzed(payload.call);
break;
case "agent_transfer":
await handleAgentTransfer(payload.call);
break;
}
}
async function handleCallEnded(call: any) {
const { call_id, duration_ms, transcript, from_number } = call;
const durationMin = Math.round(duration_ms / 60000); # 60000: 1 minute in ms
console.log(`Call ${call_id} ended: ${durationMin}min`);
await db.calls.create({
callId: call_id,
fromNumber: from_number,
duration: duration_ms,
transcript,
completedAt: new Date(),
});
if (transcript) {
await extractActionItems(call_id, transcript);
}
}
async function handleCallAnalyzed(call: any) {
const { call_id, call_analysis } = call;
const { sentiment, summary } = call_analysis;
if (sentiment === "negative") {
await alerting.send({
channel: "#customer-escalations",
message: `Negative call: ${call_id}\nSummary: ${summary}`,
});
}
await crmClient.logActivity({
callId: call_id,
sentiment,
summary,
});
}
set -euo pipefail
curl -X POST https://api.retellai.com/v2/create-phone-call \
-H "Authorization: Bearer $RETELL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from_number": "+11234567890", # 11234567890 = configured value
"to_number": "+10987654321", # 10987654321 = configured value
"agent_id": "agt_abc123",
"webhook_url": "https://api.yourapp.com/webhooks/retellai"
}'
| Issue | Cause | Solution |
|---|---|---|
| Invalid signature | Wrong webhook secret | Verify secret in Retell AI dashboard |
| No transcript | Short call or error | Check end_reason for early termination |
| Transfer failed | Invalid transfer number | Verify transfer number is active |
| Missing analysis | Analysis not configured | Enable post-call analysis in agent settings |
async function extractActionItems(callId: string, transcript: string) {
const items = await llm.extract(transcript, "action_items");
for (const item of items) {
await taskManager.createTask({
title: item,
source: `Call: ${callId}`,
});
}
}
For deployment setup, see retellai-deploy-integration.