0800 310 1010
← Developer docs
Recipe

Dynamic Number Insertion (DNI) with the NumberGroup API

A working blueprint for showing a different phone number to every paid-search visitor, mapped back to the campaign that brought them in, with calls attributed in your CRM. Built entirely on the four NumberGroup API methods — no separate tracking platform required.

What you'll end up with

  • Each paid-search visitor sees a unique tracking number on your site.
  • The number forwards to your real business line and rings as normal.
  • You know which campaign, keyword and landing page produced every call.
  • Unused numbers are released back to the pool automatically.

Pieces you'll use

Step 1 — Seed a number pool

For most sites a pool of 20–50 numbers is plenty. Choose 0800 for inbound-marketing-y feel, 03 for trust at no premium-rate cost, or a local 01 in your area code if local presence matters.

# Search — held for 10 minutes against the session_id we get back
GET https://api.numbergroup.com/reservations
    ?api_key=xxxyyy
    &ddi_type=0800
    &ddi_limit=20

# Reserve each one for 24h
GET https://api.numbergroup.com/reservations
    ?api_key=xxxyyy
    &session_id=<from-above>
    &ddi=<one-of-the-returned-ddis>
    &reserve

Persist each reserved DDI in your DB along with the session_id. Mark every number as state=free.

Step 2 — Allocate per visitor

When a paid visitor lands on your page, server-side: pick the oldest free number from the pool, flip it to busy, and assign it to the visitor with a 1-day TTL. Point it at your real business line.

GET https://api.numbergroup.com/translationrequest
    ?api_key=xxxyyy
    &ddi=<pool-number>
    &translation=441234567890
    &ttl=1
    &name=visitor-abc-123

The name parameter is your friend here — drop the visitor token (or campaign code) in so the number is labelled on selfcare in case you need to debug. Now stash the mapping:

assignments table
---------------------------------
ddi                441234567890
visitor_token      abc-123
utm_source         google
utm_campaign       0800-numbers-sept
utm_keyword        "buy 0800 number"
landing_page       /0800
assigned_at        2026-06-06T10:14:11Z
expires_at         2026-06-07T10:14:11Z
state              busy

Step 3 — Show the number on the page

Render the assigned number directly in your server response (ideal — works without JS). If your site is fully static, add a small client-side swap script that hits a JSON endpoint on your origin and replaces every element with data-dni-number.

<a href="tel:+448001230987"
   data-dni-number
   data-dni-token="abc-123">
  0800 123 0987
</a>

Two pitfalls to avoid: keep visitor → number sticky for the whole session (cookies or signed token), and don't swap the number after the page becomes visible to humans — it's jarring and people occasionally call the wrong digits.

Step 4 — Pull CDRs and attribute calls

Run a small daemon that polls the CDR feed and obeys the cooldown timer. For each call, look up your assignments table by inbound_destination_address— that's the DDI that was dialled, i.e. the number you assigned to that visitor.

GET https://api.numbergroup.com/cdrfeed
    ?api_key=xxxyyy
    &columns=session_uuid,session_started_utc,inbound_originating_address,inbound_destination_address,inbound_duration,inbound_connected_utc,session_total_charges

Join on inbound_destination_address = assignments.ddi, filter the window to assigned_at ≤ session_started_utc < expires_at, and you have a row per call mapped to campaign, keyword, landing page and visitor.

What "a real call" means

  • A call connected when inbound_connected_utc is present.
  • A useful call is usually one with inbound_duration ≥ 30 seconds. Pick the threshold that matches your sales reality.
  • Withheld numbers may have masked tails per Ofcom rules — that's fine, you're attributing to the DDI, not the caller.

Step 5 — Recycle the pool

The 1-day TTL handles most cleanup automatically — the DDI returns to the pool when it expires. Mark the local row free on a nightly sweep.

If you need to release a number early (visitor converted, lead won, refund taken), call Translation Update:

GET https://api.numbergroup.com/translationupdate
    ?api_key=xxxyyy
    &ddi=<pool-number>
    &delete=1

If you want to keep a hot lead's number sticky past 24 hours, send another Translation Update with a fresh ttl instead of releasing it.

Sizing the pool

  • Start with concurrent active visitors × 1.3 rounded up — gives you headroom for the TTL overhang.
  • For very low-traffic landing pages, even 5 numbers is enough.
  • For campaigns over a few hundred clicks/day, pool 20–50 and rotate.
  • If you ever serve the same number to two simultaneous visitors, your attribution is wrong — bias toward a bigger pool.

Common pitfalls

  • Don't poll the CDR feed in tight loops. Obey next_permitted_fetch_seconds — the API will disable the feed for your key if you don't.
  • Don't store session_sequence as the unique key. Use session_uuid.
  • Don't use the same number for organic and paid traffic at the same time. Reserve dedicated DDIs for paid pools so attribution stays clean.
  • Don't swap the number after the visitor has had time to read it. Pick once on landing and keep it sticky for the session.

What this costs as a Member

The numbers themselves are free — unlimited DDIs come with Membership, so spinning up dedicated tracking pools costs nothing. Inbound calls are passed through at the wholesale rates we pay, never marked up: 01, 02 and 03 calls delivered to your SIP endpoint or IP phones are effectively free to receive, while 0800/0808 inbound carries the BT origination charge we're billed, passed straight through to you at cost. API access and the CDR feed are free. There's no platform fee on DNI — you're using the same network and the same numbers that handle the rest of your business.

See also

Want help wiring this in?

If you'd rather not build the daemon, our team can spec the pool, set up the campaign UTMs in your ad accounts and hand back a small attribution dashboard. Members-only.

Request an API key →help@numbergroup.com