The wrong event arrives too early
Using call_ended for an analysis-driven lead can run before the custom post-call fields are available.
Retell AI CRM lead delivery
Verify Retell AI post-call webhooks, queue only approved analysis fields, and deliver each qualified lead to the CRM with a stable call ID and receipt.
Paid plan uses secure Stripe checkout · plan assigned after verified payment · activate with the checkout emailRetell AI's call_ended event does not include call analysis, while call_analyzed includes the completed call_analysis object. A production handoff must verify the signed raw webhook, acknowledge it within Retell's timeout, avoid forwarding transcripts or recording URLs, and make the later CRM delivery duplicate-safe.
Retell call_analyzed -> signed mapping handler -> durable queue -> LeadProof -> CRMUsing call_ended for an analysis-driven lead can run before the custom post-call fields are available.
Retell retries unsuccessful deliveries, so each attempt must preserve call.call_id as the original event identity.
Transcripts, recordings, logs, and other call artifacts should not travel to a CRM that only needs approved lead fields.
Implementation
Keep the tools that create and process the lead. Standardize only the fragile handoff between them.
Platform reference: Retell AI webhook documentation ↗Open sandbox instructions →https://leadproof.jessesay.chatgpt.site/api/v1/leadsimport { Retell } from "retell-sdk";
app.use(
"/retell/call-analyzed",
express.raw({ type: "application/json" })
);
app.post("/retell/call-analyzed", async (req, res) => {
const rawBody = req.body.toString("utf-8");
const signature = req.headers["x-retell-signature"];
const isAuthentic = Retell.verify(
rawBody,
process.env.RETELL_API_KEY,
signature
);
if (!isAuthentic) return res.sendStatus(401);
const { event, call } = JSON.parse(rawBody);
if (event !== "call_analyzed") return res.sendStatus(204);
const lead = call.call_analysis?.custom_analysis_data;
if (!call.call_id || !lead?.name || !lead?.email) {
return res.sendStatus(204);
}
await deliveryQueue.send({
callId: call.call_id,
lead: { name: lead.name, email: lead.email, phone: lead.phone }
});
return res.sendStatus(204);
});
export async function deliverRetellLead({ callId, lead }) {
const response = await fetch(
"https://leadproof.jessesay.chatgpt.site/api/v1/leads",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.LEADPROOF_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": callId
},
body: JSON.stringify({
destination: process.env.CRM_WEBHOOK_URL,
...lead,
source: "retell_call_analyzed"
})
}
);
if (!response.ok) throw new Error("Lead delivery failed");
const delivery = await response.json();
await persistReceipt(callId, delivery.receipt?.id);
}Questions
Retell documents call_ended without call_analysis and call_analyzed with the completed call_analysis object.
The queue may receive the event again, but the same call.call_id becomes the LeadProof Idempotency-Key so the CRM handoff remains duplicate-safe.
No. It selects only named fields from custom_analysis_data and does not copy the full call object.
Get an explainable risk score, prioritized fixes, and the right LeadProof plan.