Webhooks

Getting conversations out of the platform

When a conversation ends, bitpull posts it to one HTTPS URL you control. That single hop is what every CRM, helpdesk and automation recipe on this site is built on.

Beta · account flag Enabled per account · Actions tab appears once the flag is on

What actually exists

One outbound webhook per agent. You set an https URL and an active flag in the agent's Actions tab; after every completed conversation bitpull posts the conversation data there as JSON - transcript, summary, outcome, sentiment, channel and timings. A Test button sends a sample delivery immediately, and the active toggle pauses delivery without losing the URL.

That is the whole mechanism. It is not an event bus, there is no subscription model and there are no topics. Understanding that is worth more than any amount of clever consumer code.

  1. Conversation endsphone · web · chat
  2. bitpull posts JSONone URL, per agent
  3. Your endpointanswer 200 fast
  4. Your systemsCRM · ticket · Slack · sheet
One trigger, one destination. Fan-out is your endpoint's job.

The payload

Do not write your mapping from this page

The exact envelope - key names, nesting, which fields are present on a web chat versus a phone call - is not part of any published contract, and this site will not invent one. Point the webhook at the tester on this site, press Test in the dashboard, and read the delivery you actually get. Then write the mapping against that.

What the platform holds about a conversation is knowable, because the same data drives the dashboard's conversation view. Expect the delivery to carry some arrangement of these:

ConceptValues it takesWhat it is good for
Session identitysessionId, roomNameIdempotency. Store it and drop duplicates.
Channel & directionchannel, INBOUND / OUTBOUNDRouting. A cold outbound call rarely deserves the same treatment as an inbound one.
TimingstartedAt, answeredAt, endedAtDuration, and whether the call was ever picked up.
TelephonysipFrom, sipTo, sipStatusCodeThe caller number - in provider-specific formats. Normalise before matching.
OutcomeRESOLVED · PARTIALLY_RESOLVED · UNRESOLVED · INFORMATIONAL · UNKNOWNThe single most useful field. Gate ticket and task creation on it.
SentimentPOSITIVE · NEUTRAL · NEGATIVE · MIXED · UNKNOWNEscalation and priority.
Summarytitle, key points, follow-up required + reasonWhat a human reads instead of the transcript.
Transcriptturns of USER / ASSISTANT with timestampsThe record. Usually belongs behind access control, not in a chat channel.
Language & voicelanguage, voiceIdRouting to a language-specific team.
Failurestatus, failureReasonDistinguishing a bad conversation from a call that never connected.

Consuming it without losing data

Four rules, in the order they will bite you.

1. Answer immediately, work afterwards

Return 200 before you touch a CRM. A webhook consumer that waits for three third-party APIs is a webhook consumer that times out.

webhook consumer - the shape that survives
export default async function handler(req, res) {
  // 1. Persist the raw body first. Everything after this is replayable.
  const id = await store.insertRaw({
    receivedAt: new Date().toISOString(),
    headers: req.headers,
    body: req.body
  })

  // 2. Acknowledge. Nothing downstream is allowed to delay this.
  res.status(200).json({ ok: true })

  // 3. Do the real work out of band, where a failure is retryable.
  await queue.publish('conversation.received', { id })
}

2. Assume there is no retry

No retry schedule, replay API or delivery log is documented on the sending side. Treat a delivery as fire-and-forget: persist the raw body as the very first thing your handler does, and drive everything else from your own store. That one habit turns a lost integration into a replayable one.

3. The URL is the credential

No request signature is documented, so there is nothing to verify a delivery against. In practice that means the path itself has to be unguessable, and your endpoint has to be prepared for anyone who finds it.

what an unguessable path looks like
# Good: the path itself is the secret, and it is long.
https://hooks.example.com/bitpull/9f3c1a7b4e2d8065

# Also fine: a token your handler checks before doing anything.
https://hooks.example.com/bitpull?t=9f3c1a7b4e2d8065

# Bad: guessable, and now anyone can inject conversations into your CRM.
https://example.com/webhook

4. Be idempotent

Key on the session id. Even without documented retries, a proxy, a redeploy or a replay of your own will eventually deliver the same conversation twice - and a duplicate CRM task is how people lose trust in an integration.

The event model this does not have

It is worth being explicit, because the names below are what people search for and expect. bitpull has one outbound trigger: the conversation finished. It does not emit call.started, call.answered, appointment.created or anything else, and it has no subscription model in which you could select them.

Event people expectReality todayWhat to do instead
conversation.completedBeta · account flagThis is the one that exists. Everything below is a variation you have to build.
call.startedPlannedNot emitted. If you need live call state, poll the conversation list with a user token - or reconsider whether you do.
call.completedBeta · account flagCovered by the completion delivery: channel and the SIP fields tell you it was a call.
appointment.createdPlannedCalendly is the system of record for bookings - subscribe to its webhooks, not to bitpull.
lead.createdVia webhookDerive it: outcome plus the follow-up flag in the summary is your lead signal.
callback.requestedVia webhookSame derivation. Write the prompt so the summary states it plainly.
escalation.createdVia webhookDerive from sentiment NEGATIVE together with an UNRESOLVED outcome.

If you need something during the conversation rather than after it, the mechanism is not a webhook at all - it is a declared agent tool, which the model calls mid-conversation and waits for.

The lower-effort alternative

Every agent can also send a conversation summary to an email address after each conversation, configured in its Notifications tab. No endpoint, no deployment, no maintenance. For a small team that just wants to see what the agent handled, it is often the right answer, and the webhook can come later.