0800 310 1010
← All articles
CRM & integrations · 7 min read

Connect Pipedrive to NumberGroup: log inbound and outbound calls against deals

Wire NumberGroup call data into Pipedrive — Activity records, person matching by phone number, deal attribution, and the Pipedrive API quirks worth knowing.

Pipedrive is the sales-first CRM most NumberGroup Members on the SME side actually use. Its API is friendly, its data model is sane, and the integration is the smallest of the four in this series. ~80 lines of worker code.

Start with NumberGroup call webhooks explained for the mechanism.

What you’ll have at the end

  • Every inbound call creates an Activity of type
    call
    against the matching Person.
  • If the Person has open Deals, the Activity is linked to the most recently-active one.
  • Unknown callers create a new Person with
    label = "Unknown caller"
    so they’re easy to triage.
  • Duration and the dialled DDI are captured.

Step 1 — Get a Pipedrive API token

  • Pipedrive → top-right profile menu → Personal preferences → API.
  • Copy the personal API token. It scopes to that user’s permissions, so use an admin account or a dedicated “NumberGroup” service user.
  • Note your company subdomain — it’s in the URL, e.g.
    https://yourco.pipedrive.com
    . API calls go to that subdomain.

For multi-user multi-tenant scenarios use OAuth instead — but a single-tenant personal token is fine for one company.

Step 2 — Worker code

// numbergroup-pipedrive-worker.js
export default {
  async fetch(req, env) {
    if (req.method !== "POST") return new Response("ok");
    const call = await req.json();
    const phone = normaliseUK(call.caller);

    let personId = await findPerson(env, phone);
    if (!personId) personId = await createPerson(env, phone);

    const dealId = await getMostRecentDeal(env, personId);

    await createCallActivity(env, {
      personId,
      dealId,
      from: phone,
      to: call.called,
      duration: call.duration || 0,
      timestamp: call.ended_at || new Date().toISOString(),
      recordingUrl: call.recording_url || null,
    });

    return new Response("ok");
  },
};

const BASE = env => `https://${env.PD_DOMAIN}.pipedrive.com/api/v1`;
const auth = env => `?api_token=${env.PD_TOKEN}`;

function normaliseUK(n) {
  if (!n) return null;
  n = n.replace(/[^0-9+]/g, "");
  if (n.startsWith("+")) return n;
  if (n.startsWith("0")) return "+44" + n.slice(1);
  return "+" + n;
}

async function findPerson(env, phone) {
  const r = await fetch(`${BASE(env)}/persons/search${auth(env)}&term=${encodeURIComponent(phone)}&fields=phone&exact_match=false`);
  const j = await r.json();
  return j.data?.items?.[0]?.item?.id || null;
}

async function createPerson(env, phone) {
  const r = await fetch(`${BASE(env)}/persons${auth(env)}`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({
      name: `Unknown caller ${phone}`,
      phone: [{ value: phone, primary: true, label: "work" }],
      visible_to: 3, // entire company
    }),
  });
  const j = await r.json();
  return j.data?.id;
}

async function getMostRecentDeal(env, personId) {
  const r = await fetch(`${BASE(env)}/persons/${personId}/deals${auth(env)}&status=open&sort=update_time DESC&limit=1`);
  const j = await r.json();
  return j.data?.[0]?.id || null;
}

async function createCallActivity(env, c) {
  await fetch(`${BASE(env)}/activities${auth(env)}`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({
      subject: `Inbound call to ${c.to}`,
      type: "call",
      done: 1,
      duration: secsToHMS(c.duration),
      due_date: c.timestamp.slice(0, 10),
      due_time: c.timestamp.slice(11, 16),
      person_id: c.personId,
      deal_id: c.dealId,
      note: c.recordingUrl ? `Recording: ${c.recordingUrl}` : "",
    }),
  });
}

function secsToHMS(s) {
  const h = String(Math.floor(s / 3600)).padStart(2, "0");
  const m = String(Math.floor((s % 3600) / 60)).padStart(2, "0");
  return `${h}:${m}`;
}

Step 3 — Wire it up

  • Portal → Web Hook Manager → new webhook.
  • URL: your worker.
  • Attach as End of call webhook on each DDI.

Pipedrive API quirks worth knowing

  • Phone search is fuzzy — it matches partial numbers. Good for messy data, but watch for false positives if you have very short phone fragments stored.
  • Duration is HH:MM, not seconds. If you skip this you lose call-length reporting.
  • The
    done: 1
    field
    marks the activity as completed so it doesn’t clutter the rep’s “activities to do” list.
  • Rate limit is 100 requests / 2 seconds per company. Fine for SME call volume.
  • API v2 is rolling out and parameter names differ for some endpoints. Code above is v1 — stable, well-documented, supported for years.

Optional: filter by DDI to route calls to specific pipelines

If you run multiple sales pipelines (e.g. one for new business, one for renewals) and have dedicated DDIs for each, your worker can use the

called
number to pick which pipeline the auto-created Deal goes into. Useful for cleaner reporting.

Need help?

Ring 0800 310 1010. Pipedrive is one of the easier ones — we can usually talk you through it in 15 minutes if you have a worker environment ready.

Move your phones to us. We'll save you money.

Memorable numbers, unlimited UK numbers, every smart feature and a UK team — all included with Membership at £149/mo. No contract.

See what Membership includes →Browse phone numbers →
Related reading