PrepaidAIDocsv1
StatusConsole

Send calls to your CRM

A webhook receiver that verifies each delivery and files the call, the transcript and any message.

1 min read

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.

  1. 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.

  2. Verify, answer, then work

    Read the raw body, verify the x-signature header, answer 200 straight 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);
  3. File each event once

    The same event can arrive more than once — a retry after a timeout, for instance. Its id never changes, so store the ids you have handled and skip repeats.

    worker.js
    async 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.