A completed call is not a CRM record
The conversation can finish successfully while a later CRM request times out, rejects a field, or disappears between systems.
Vapi CRM lead delivery
Map Vapi end-of-call reports into a minimal lead payload, protect the CRM write with a stable call ID, and keep a verifiable delivery receipt.
Paid plan uses secure Stripe checkout · plan assigned after verified payment · activate with the checkout emailVapi can post an end-of-call-report to a Server URL, but receiving that event only proves the report reached your handler. It does not prove a downstream CRM stored the mapped lead. A safe production bridge must select only authorized structured fields, deduplicate repeated callbacks, verify the destination response, and preserve a receipt.
Vapi end-of-call-report -> mapping handler -> LeadProof -> CRM or sales queueThe conversation can finish successfully while a later CRM request times out, rejects a field, or disappears between systems.
Provider retries and operator replays need the original Vapi call ID so recovery does not create a duplicate lead.
Transcripts, recordings, and full artifacts should not be forwarded when the destination only needs approved contact and routing fields.
Implementation
Keep the tools that create and process the lead. Standardize only the fragile handoff between them.
Platform reference: Vapi server events documentation ↗Open sandbox instructions →https://leadproof.jessesay.chatgpt.site/api/v1/leadsapp.post("/vapi/end-of-call", async (req, res) => {
const message = req.body?.message;
if (message?.type !== "end-of-call-report") {
return res.sendStatus(204);
}
const callId = message.call?.id;
const lead = message.call?.analysis?.structuredData;
if (!callId || !lead?.name || !lead?.email) {
return res.sendStatus(204);
}
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,
name: lead.name,
email: lead.email,
phone: lead.phone,
source: "vapi_end_of_call"
})
}
);
if (!response.ok) return res.sendStatus(502);
const delivery = await response.json();
await persistReceipt(callId, delivery.receipt?.id);
return res.sendStatus(204);
});Questions
Use a small mapping handler first. Vapi sends a message envelope, while LeadProof expects a normalized lead payload plus the final destination URL.
Use the structured data configured in Vapi call analysis or structured outputs. Adapt the extraction path to that configuration and do not forward the complete call artifact.
Use message.call.id as the Idempotency-Key for the original call. LeadProof recognizes later attempts with the same stable key.
Get an explainable risk score, prioritized fixes, and the right LeadProof plan.