Home/Blog/Call Tracking APIs and Webhooks: How to Wire Phone Calls Into Your CRM Without Losing Data

Call Tracking APIs and Webhooks: How to Wire Phone Calls Into Your CRM Without Losing Data

CallFlux Team September 11, 2026 12 min read
A bright minimal desk with a closed laptop and an open notebook showing a hand-drawn boxes-and-arrows flow diagram

Native integrations are genuinely good now. If you run Google Ads and HubSpot, you will click two buttons and be done, and this article is not for you.

It becomes relevant at a specific moment: when your CRM is custom, your attribution model is your own, you are pushing call data into a warehouse alongside everything else, or you are an agency building one dashboard across thirty client accounts. At that point you are writing against a webhook, and the interesting part is not the happy path — it is what happens on the retry, the timeout, and the deploy.

The patterns below apply to any call tracking platform. Where the specifics matter, check your vendor's current documentation rather than trusting an example in a blog post, including this one.

Push versus pull, and when each is wrong

Webhooks push. Something happens; the platform sends an HTTP POST to a URL you own. Near-real-time, no polling, and you receive each event once — in theory.

APIs pull. You ask for data over a time range and get a consistent snapshot.

The failure mode in each direction is the one people hit:

Using polling for real-time reactions. If a rep needs a screen pop when the phone rings, a one-minute poll is not a solution. Use the webhook.

Using webhooks as your system of record. Webhooks are an event stream, not a database. They get missed — your endpoint is down for a deploy, a retry budget exhausts, a payload fails schema validation and your handler throws. If the only copy of a call lives in whatever your webhook handler wrote, you will silently lose calls and not discover it for weeks.

The durable pattern is both: webhooks for immediacy, and a nightly API reconciliation job that pulls the last 48 hours and upserts anything your webhook handler missed. That job is twenty lines and it is the difference between an integration you trust and one you re-audit every quarter.

The events that actually matter

Most platforms emit more events than you need. A minimal, well-chosen subscription:

call.completed — the workhorse. Fires when the call ends and carries duration, direction, answered status, the tracking number dialled, the caller's number, attribution (source, campaign, keyword where available), and a reference to the recording. If you subscribe to exactly one event, this is it.

call.started — only if you need a screen pop or live routing. It cannot tell you anything about outcome, because nothing has happened yet.

transcription.completed / analysis.completed — separate because they are separate. Transcription and AI scoring run after the call ends. Expect them seconds to minutes later.

That last point causes more integration bugs than anything else on this list, so it is worth stating directly: a transcript is not available at call.completed time. Code that reads payload.transcript in the completed handler will work in testing on a 20-second test call and return null intermittently in production on a 12-minute one. Handle it as a second event.

An illustrative payload shape

Every vendor's schema differs. The shape is broadly consistent, and it is worth knowing what to look for:

{
  "event": "call.completed",
  "call_id": "c_8f2a91e4c7",
  "occurred_at": "2026-09-11T14:32:07Z",
  "direction": "inbound",
  "answered": true,
  "duration_seconds": 214,
  "tracking_number": "+18175550142",
  "caller_number": "+12145550198",
  "destination_number": "+18175550100",
  "attribution": {
    "source": "google",
    "medium": "cpc",
    "campaign": "emergency-service-dfw",
    "keyword": "emergency locksmith near me",
    "landing_page": "/emergency",
    "gclid": "Cj0KCQ..."
  },
  "recording_url": "https://…/recordings/c_8f2a91e4c7",
  "first_time_caller": true
}

This is illustrative, not a spec for any particular platform. Use it to plan the fields you need, then map against real documentation.

Two fields deserve attention. call_id is your idempotency key — everything below depends on it. And gclid, where present, is what lets you close the loop back into Google Ads with revenue attached rather than just a conversion count; that flow is covered in offline conversion import.

Making the handler safe

Four rules. They are unglamorous and they are the entire difference between an integration that holds and one that quietly corrupts your CRM.

1. Verify the signature before you trust anything

Your webhook URL is a public HTTP endpoint. Anyone who discovers it can post to it. Most platforms sign payloads with an HMAC over the raw request body using a shared secret, delivered in a header.

Two details people get wrong: verify against the raw body bytes, not the re-serialised JSON — any difference in key order or whitespace changes the hash — and use a constant-time comparison rather than ==, because a naive string compare leaks the correct signature through timing. Reject anything that fails, and check the timestamp to reject replays of old-but-valid payloads.

2. Be idempotent, because delivery is at-least-once

Every webhook sender retries on timeout or a non-2xx response. It has no way to know whether your handler succeeded before the connection dropped, so the safe behaviour on its side is to send again. Duplicates are not an edge case; they are guaranteed over a long enough window.

Store call_id with a unique constraint and upsert against it. Not "check if it exists, then insert" — that has a race under concurrent delivery. Let the database enforce it.

This is the single most common cause of duplicate CRM contacts traced back to a call integration, and it is entirely preventable.

3. Return 200 fast; do the work elsewhere

Your handler should validate, enqueue, and return. Most senders time out in a handful of seconds, and a slow handler produces exactly the retry storm that then tests your idempotency under load.

If your handler enriches the record, calls three internal services, and writes to two databases inline, it will eventually exceed the timeout, the platform will retry, and you will process the same event repeatedly while getting slower. Accept the payload, put it on a queue, return 200, process asynchronously.

This is the same class of bug as blocking an event loop on a synchronous network call — the request that hangs is rarely the one that suffers.

4. Log the raw payload before you parse it

Store the body as received, with headers, before any parsing. When a call goes missing three weeks later, the raw log is what lets you answer "did it arrive and we mishandled it, or did it never arrive?" Without it you are guessing, and the two causes have completely different fixes.

Keep the log short-lived — it contains phone numbers, and possibly more depending on your vertical. If you are in healthcare, treat webhook logs as in-scope for the same governance as recordings; see HIPAA and call tracking.

Common integration patterns

Create or update a CRM contact and log the call. The default. Match on the caller's phone number, upsert the contact, attach a call activity with attribution and duration. Pitfall: match on normalised numbers — E.164 everywhere — or you will create a second contact for the same person because one system stored (214) 555-0198 and the other stored +12145550198.

Push conversions to ad platforms with revenue. Rather than counting every call as a conversion, wait for the deal to close and send the actual value back. This is where call tracking pays for itself, because it lets Smart Bidding optimise toward revenue rather than call volume. Same pattern applies on the Meta side — see Facebook and Meta ads call tracking.

Route by attribution in real time. Use call.started to look up the campaign and route high-intent traffic to your senior reps.

Warehouse everything for blended reporting. Land the raw events in your warehouse so calls join to sessions, deals, and spend in one model. This is where the reconciliation job matters most, because gaps in a warehouse are invisible until someone questions a number.

Trigger follow-up on missed calls. Subscribe to call.completed where answered is false and fire an SMS or a task. Worth noting: most platforms including ours have this built in as an automation rule, so check before you build it. Our take on why it matters is in missed call recovery.

The failure modes that stay quiet

These are the ones that do not page anyone.

Silent endpoint death after a deploy. Your URL changes or the route stops matching. The platform retries, exhausts its budget, and disables the subscription. Nothing in your system knows. Mitigation: alert on absence — if no call events arrive in a window when calls exist, page someone. A dashboard showing zero is not an alert.

Schema drift. A new field appears, your strict parser rejects the payload, and every delivery 400s. Mitigation: parse leniently, validate only the fields you use, ignore unknown keys.

Timezone confusion. Timestamps arrive UTC; your reporting is local. A call at 7:30 PM Central lands on the wrong day in a daily report, and the daily numbers are subtly wrong forever. Mitigation: store UTC, convert at render, and never let a naive local timestamp cross a system boundary — a wall-clock time carrying a UTC label is one of the harder bugs to spot after the fact.

Recording URLs that expire. Many platforms issue signed, time-limited URLs. Storing one in your CRM gives you a link that works during testing and 403s in three weeks. Mitigation: store the call_id and fetch a fresh URL on demand.

Retry storms during an outage. Your service is down for twenty minutes; the platform queues and retries. You come back up and receive a flood. Mitigation: the queue in rule 3, plus idempotency in rule 2. Together they make this a non-event.

Testing webhooks without waiting for real calls

Webhook development has an awkward property: the events you need are produced by real phone calls, which is a slow and expensive way to test a retry path.

Use a request inspector first. Point the subscription at a throwaway inspection endpoint and place one real test call. That gives you the exact payload your vendor sends — field names, types, nesting, header format — which is the ground truth no documentation fully captures. Save that payload.

Then replay it locally. With a real captured payload you can curl it at your handler as often as you like, including deliberately malformed versions. Send the same payload twice to prove your idempotency works. Send it with a bad signature to prove verification rejects it. Send one with an unexpected extra field to prove lenient parsing.

Test the slow path explicitly. Add a sleep to your handler that pushes it past the vendor's timeout, and watch what happens. You want to see the retry arrive and your idempotency absorb it. If instead you get a duplicate record, you have found the bug in development rather than in your CRM.

Use a separate subscription for staging. Sending production call events to a development endpoint means test code touching real customer data, and it means a broken staging deploy can exhaust retry budgets on live events.

The whole exercise takes an afternoon and it is the difference between an integration that works and one that works until the first bad day.

When you do not need any of this

Worth saying plainly, because engineering time is expensive: if your stack is Google Ads plus a mainstream CRM, use the native integration. It is maintained by someone else, it handles the retry semantics above, and it will not break when you deploy.

Build against the API when you have a genuine reason — a custom system, proprietary attribution, warehouse reporting, or multi-client dashboards. "We might want it later" is not one; you can add it later.

CallFlux includes API access in the Pro tier at $499/mo, alongside advanced automation, audit logs, and the power dialer. Because pricing is flat-rate with unlimited calls and no per-minute fees, integration volume does not change what you pay — which matters when you are pushing every call into a warehouse rather than sampling. The Google Ads integration covers the native path if that is all you need, and automation rules handle a surprising amount of what teams otherwise build in code.

For current endpoints, authentication, and event schemas, talk to us — API surfaces change, and a page written in September is the wrong place to learn them.

Ready to track every call?

Start your free trial and see exactly which marketing channels drive phone calls.

Get Started Free