Post-call receipt is not CRM proof
A successful response to Bland confirms the mapping handler received the call data, not that the sales system accepted the lead.
Bland AI post-call CRM delivery
Verify Bland AI's signed post-call webhook, queue only approved analysis fields, and deliver each qualified lead once with the original call ID and a receipt.
Paid plan uses secure Stripe checkout · plan assigned after verified payment · activate with the checkout emailBland AI sends the configured webhook after a call ends, but receiving that payload does not prove a downstream CRM stored the lead. A production bridge should verify X-Webhook-Signature against the raw body, select only the authorized analysis fields, acknowledge the provider promptly, and preserve call_id through the queued CRM delivery.
Bland AI post-call webhook -> signed mapping handler -> durable queue -> LeadProof -> CRMA successful response to Bland confirms the mapping handler received the call data, not that the sales system accepted the lead.
Webhook recovery and operator resends must keep the original call_id so every attempt has the same business identity.
A CRM handoff should select the approved analysis fields instead of copying the complete post-call payload.
Implementation
Keep the tools that create and process the lead. Standardize only the fragile handoff between them.
Platform reference: Bland AI post-call webhook documentation ↗Open sandbox instructions →https://leadproof.jessesay.chatgpt.site/api/v1/leadsimport crypto from "node:crypto";
app.use(
"/bland/post-call",
express.raw({ type: "application/json" })
);
app.post("/bland/post-call", async (req, res) => {
const signature = req.headers["x-webhook-signature"];
const secret = process.env.BLAND_WEBHOOK_SECRET;
if (
!secret ||
typeof signature !== "string" ||
!/^[0-9a-f]{64}$/i.test(signature)
) {
return res.sendStatus(401);
}
const expected = crypto
.createHmac("sha256", secret)
.update(req.body)
.digest("hex");
const authentic = crypto.timingSafeEqual(
Buffer.from(signature, "hex"),
Buffer.from(expected, "hex")
);
if (!authentic) return res.sendStatus(401);
const event = JSON.parse(req.body.toString("utf-8"));
const lead = {
name: event.analysis?.name,
email: event.analysis?.email,
phone: event.analysis?.phone
};
if (event.call_id && lead.name && lead.email) {
await deliveryQueue.send({ callId: event.call_id, lead });
}
return res.sendStatus(204);
});
export async function deliverBlandLead({ 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: "bland_ai_post_call"
})
}
);
if (!response.ok) throw new Error("Lead delivery failed");
const delivery = await response.json();
await persistReceipt(callId, delivery.receipt?.id);
}Questions
Use the webhook setting for the post-call notification. Bland documents webhook_events separately for events streamed during the call.
Bland signs the request body with HMAC-SHA256 and sends the hex signature in X-Webhook-Signature. Keep the generated secret in server-side environment storage.
The original call_id becomes the LeadProof Idempotency-Key, so a resend or replay keeps the same delivery identity.
Get an explainable risk score, prioritized fixes, and the right LeadProof plan.