HubSpot call tracking integration

HubSpot doesn't accept arbitrary inbound webhooks on most tiers, so the standard recipe uses Zapier as a one-step bridge: our webhook triggers a Zap that creates or updates the HubSpot contact and logs a call engagement with duration and campaign.

Integrations / HubSpot

What you get

Every qualified call (passing your duration and screening rules) is delivered to HubSpot within about a minute: caller number, duration, timestamps, the campaign that generated the call, ad click IDs when present, and a recording link.

Step A — callstracking.com side

  1. Create an account and set up your campaign (2 minutes).
  2. Go to Dashboard → Integrations → Webhook / CRM.
  3. Paste the webhook URL from HubSpot (Step B) and choose a signing secret.
  4. Save. Qualified calls now POST to that URL automatically.

Step B — HubSpot side

  1. In Zapier, create a Zap with trigger "Webhooks by Zapier → Catch Hook". Copy the hook URL.
  2. Paste that URL into the callstracking.com webhook integration (Step A above) and save.
  3. Make one qualified test call so Zapier captures a sample payload.
  4. Add action "HubSpot → Find or Create Contact" matching on phone = fromE164.
  5. Add action "HubSpot → Create Engagement (Call)" — set duration from durationSec, note from campaignName, and timestamp from startedAt.
  6. Turn the Zap on.
  • Operations Hub customers can swap Zapier for HubSpot's own workflow webhooks.

The payload

Sent as JSON with content-type application/json. The recordingUrl opens in a signed-in callstracking.com session.

{
  "type": "qualified_call",
  "callId": "clx1abc...",
  "source": "DNI",
  "startedAt": "2026-08-14T17:21:09.000Z",
  "qualifiedAt": "2026-08-14T17:23:41.000Z",
  "durationSec": 152,
  "fromE164": "+15551234567",
  "toE164": "+12138321924",
  "campaignId": "cmsam...",
  "campaignName": "Google Ads — Plumbing",
  "channel": "GOOGLE_ADS",
  "recordingUrl": "https://callstracking.com/api/recordings/clx1abc...",
  "clickIds": { "gclid": "..." },
  "utm": { "utm_source": "google", "utm_campaign": "plumbing" }
}

Verifying the signature

Every delivery is signed with your secret: header x-ct-signature: sha256=<hmac> over the raw body, plus x-ct-event: call.qualified. Platforms like Zapier and GoHighLevel can skip verification (the URL itself is secret); verify when posting to your own server:

// Node.js — verify the x-ct-signature header
import { createHmac } from "node:crypto";

const expected = "sha256=" +
  createHmac("sha256", process.env.CT_WEBHOOK_SECRET)
    .update(rawRequestBody)   // the exact raw body string
    .digest("hex");

const valid = expected === req.headers["x-ct-signature"];

Troubleshooting