A completed call is not a CRM record
The post-call webhook can arrive successfully while the later sales-system request times out, rejects a field, or silently disappears.
Synthflow post-call CRM delivery
Verify Synthflow's call-ID signature, select only approved collected variables, and turn each completed call into a duplicate-safe CRM delivery receipt.
Paid plan uses secure Stripe checkout · plan assigned after verified payment · activate with the checkout emailSynthflow sends a structured payload to external_webhook_url after a call completes, but webhook receipt is not proof that a downstream CRM accepted the lead. The bridge must verify HTTP_SYNTHFLOW_SIGNATURE using the call ID, avoid forwarding the complete call payload, and keep call.call_id stable through queued delivery and recovery.
Synthflow post-call webhook -> authenticated mapping handler -> durable queue -> LeadProof -> CRMThe post-call webhook can arrive successfully while the later sales-system request times out, rejects a field, or silently disappears.
Retries and webhook-log resends need call.call_id as the original identity so the CRM does not receive a duplicate.
The CRM should receive approved collected variables, not a complete call payload containing unrelated artifacts.
Implementation
Keep the tools that create and process the lead. Standardize only the fragile handoff between them.
Platform reference: Synthflow webhook documentation ↗Open sandbox instructions →https://leadproof.jessesay.chatgpt.site/api/v1/leadsimport crypto from "node:crypto";
app.post("/synthflow/post-call", express.json(), async (req, res) => {
const callId = req.body?.call?.call_id;
const signature = req.get("HTTP_SYNTHFLOW_SIGNATURE");
const secret = process.env.SYNTHFLOW_WEBHOOK_SECRET;
if (!callId || !signature || !secret) return res.sendStatus(401);
const expected = crypto
.createHmac("sha256", secret)
.update(callId)
.digest();
const supplied = Buffer.from(signature, "base64");
const authentic =
supplied.length === expected.length &&
crypto.timingSafeEqual(supplied, expected);
if (!authentic) return res.sendStatus(401);
const fields = req.body.collected_variables;
const lead = {
name: fields?.customer_name?.value ?? req.body.lead?.name,
email: fields?.email?.value,
phone: fields?.phone?.value ?? req.body.lead?.phone_number
};
if (lead.name && lead.email) {
await deliveryQueue.send({ callId, lead });
}
return res.sendStatus(204);
});
export async function deliverSynthflowLead({ 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: "synthflow_post_call"
})
}
);
if (!response.ok) throw new Error("Lead delivery failed");
const delivery = await response.json();
await persistReceipt(callId, delivery.receipt?.id);
}Questions
Set external_webhook_url on the call request. Synthflow sends a structured payload there after the call completes.
Synthflow documents an HMAC-SHA256 signature of call_id, base64 encoded in HTTP_SYNTHFLOW_SIGNATURE. Verify it with the workspace webhook secret.
Use the specific collected_variables configured for the agent, plus only the contact fields the buyer authorized. Do not copy the complete call object.
Get an explainable risk score, prioritized fixes, and the right LeadProof plan.