Send calls to your CRM
A webhook receiver that verifies each delivery and files the call, the transcript and any message.
Subscribe to three events and you have everything a CRM wants: call.ended for the call itself, message.taken and call.urgent for the follow-ups, and call.transcript.ready when you want the words too.
Add an endpoint
In Settings → Webhooks, press Add an endpoint, give it your HTTPS URL and tick the events above. Copy the endpoint’s signing secret into your server’s environment as
PREPAID_WEBHOOK_SECRET.Verify, answer, then work
Read the raw body, verify the
x-signatureheader, answer200straight away and do the slow work afterwards. We wait 10 seconds for an answer.server.js (Express)const express = require('express');// verifyWebhook is the function on the Webhooks page.const app = express();app.post('/hooks/prepaid', express.raw({ type: 'application/json' }), (req, res) => { const raw = req.body.toString('utf8'); if (!verifyWebhook(raw, req.get('x-signature'), process.env.PREPAID_WEBHOOK_SECRET)) { return res.status(401).end(); } res.status(200).end(); // answer first const event = JSON.parse(raw); queue.push(event); // your own job queue — see the next step});app.listen(3000);File each event once
The same event can arrive more than once — a retry after a timeout, for instance. Its
idnever changes, so store the ids you have handled and skip repeats.worker.jsasync function handle(event) { if (await seen(event.id)) return; // already filed const d = event.data; switch (event.type) { case 'call.ended': await crm.logCall({ phone: d.from, name: d.caller_name, seconds: d.duration_seconds, outcome: d.outcome }); break; case 'message.taken': case 'call.urgent': await crm.createTask({ phone: d.caller_phone, name: d.caller_name, note: d.body, urgent: d.kind === 'urgent' }); break; case 'call.transcript.ready': { const res = await fetch(`https://app.prepaid.ai/api/v1/calls/${d.call_id}`, { headers: { Authorization: `Bearer ${process.env.PREPAID_API_KEY}` }, }); const { call, transcript } = await res.json(); await crm.attachNote({ phone: call.from, text: transcript.map((t) => `${t.speaker}: ${t.text}`).join('\n') }); break; } } await markSeen(event.id);}
from and caller_phone can be null for a withheld number. Decide what your CRM does with an anonymous caller before the first one arrives.