Dynamic Number Insertion (DNI) with the NumberGroup API
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
- Reservations — seed a small pool of numbers up front.
- Translation Request — assign a number to a visitor with a 1-day TTL.
- Translation Update — optionally extend or release early.
- CDR Feed — pull completed call records and stitch them back to campaigns.
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>
&reservePersist 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-123The 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 busyStep 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_chargesJoin 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_utcis present. - A useful call is usually one with
inbound_duration ≥ 30seconds. 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=1If 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_sequenceas the unique key. Usesession_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.
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.