Dynamic number insertion (DNI) is a call tracking technique where a JavaScript snippet swaps the phone number displayed on your website based on how each visitor arrived. A visitor from a Google Ads click sees one tracking number, a visitor from Facebook sees another, an organic visitor sees a third. When the phone rings, the number that was dialed tells you which channel — or which individual session — produced the call. Underneath the script, DNI runs on two pieces of carrier infrastructure: a pool of tracking phone numbers and per-number call records that connect each dialed number back to its source.
DNI exists because phone calls are the attribution blind spot of web analytics. A form submission carries its UTM parameters with it; a phone call does not. If every page of your site shows one static number, every inbound call looks identical — you know the phone rang, but not whether the billboard, the ad campaign, or the organic ranking made it ring. DNI closes that gap by making the displayed number itself the tracking parameter.
What DNI is — and what a static tracking number is
The simpler ancestor of DNI is the static tracking number: print one dedicated number on the billboard, a different one in the direct-mail piece, a third on the Google Ads landing page. Each number maps permanently to one channel. Static tracking works, requires no JavaScript, and is still the right tool for offline media — a billboard cannot swap its number per viewer.
DNI is the on-site, per-visitor version. The number in your site header is no longer hardcoded; a script decides at page load which number this particular visitor should see, based on referrer, UTM parameters, ad click IDs, or paid-search keyword. Static tracking attributes at the medium level (one number per placement). DNI attributes at the source, campaign, or even session level — and it only applies where the script runs: your own website.
Most real deployments use both. Offline placements get static tracking numbers; the website gets DNI. The two feed the same reporting because both resolve to the same primitive — a dialed number that means something.
How DNI works: swap, pool, attribution
Mechanically, DNI is three steps repeated on every page view.
The swap. A JavaScript snippet loads with the page, inspects how the visitor arrived — the referrer header, utm_source/utm_medium parameters, a gclid from Google Ads — and rewrites every displayed instance of your phone number (and its tel: links) to the tracking number assigned to that source. Direct visitors with no attribution signal typically see the default number.
The pool. The script draws from a pool of tracking numbers provisioned in advance. Channel-level DNI needs one number per source you distinguish. Session-level DNI — where each concurrent visitor gets a temporarily dedicated number so a call can be tied to one specific session and its full click path — needs a rotating pool sized to your concurrent traffic. The script holds the number for that visitor for a set window (commonly the session plus a buffer), then releases it back to the pool.
The attribution. When a call comes in, the dialed number is looked up: which source owns this number right now? Channel-level lookups are a fixed mapping. Session-level lookups join the call’s timestamp against which session held that number at that moment. Either way, the call inherits the visitor’s source data — that is the entire trick.
What DNI requires underneath
Strip away the JavaScript and DNI is a numbering and data problem. It requires two things from the carrier layer.
A pool of tracking DIDs. Every number the script can display must be a real, provisioned phone number — a DID — that routes to the same destination as your main line. These should be numbers you actually control, registered to your account and signed with A-level STIR/SHAKEN attestation, so that callbacks you place from them display as verified rather than flagged. SIPNEX provisions tracking DID pools at carrier-direct pricing — standard DID rates, no call-tracking premium, no platform fee on the numbers themselves.
Per-number CDRs. Attribution is only as good as the call records. Each inbound call must generate a record showing the timestamp, the calling number, the called tracking DID, the duration, and the disposition. SIPNEX delivers real-time CDRs for every call on the trunk, which is the raw material the attribution join runs on.
The DNI script itself is a separate layer. Platforms like CallRail and Invoca bundle the script, dashboards, and integrations; operators with their own analytics stack write or integrate the swap script and run it against carrier numbers directly. SIPNEX supplies the carrier layer — the DIDs, routing, recording, and CDRs — for either approach.
Pool sizing logic
Channel-level pools are easy: count the sources you want to separate. Google Ads, Facebook, organic, direct, plus one static number per offline placement — a typical small deployment runs five to ten numbers.
Session-level pools are a queueing problem. You need at least as many numbers as you have concurrent tracked sessions during peak traffic, plus headroom, because a number handed to one visitor cannot be shown to another until its hold window expires. Undersized pools degrade quietly: the script runs out of free numbers and falls back to a shared or default number, and those sessions lose session-level attribution. Typical session-level deployments run pools of 10 to 50 numbers, scaling with concurrent website traffic.
Two levers control the math: peak concurrent sessions and the hold window. A longer hold window catches the visitor who browses, thinks, and calls twenty minutes later — but it keeps each number out of circulation longer, which demands a bigger pool. Watch the fallback rate in your reporting; a rising fallback rate is the signal to add numbers or shorten the window.
DNI and local SEO: keep NAP consistent while swapping
The classic objection to DNI is that swapping numbers will shred your NAP consistency — the name-address-phone signal that local search relies on. Handled correctly, it does not, because the swap happens in the rendered page for tracked visitors, not in your canonical business identity.
The working rules: keep one canonical business number, and keep it authoritative everywhere your identity is published — your LocalBusiness structured data, your Google Business Profile, and your citations and directories. Configure the DNI script so it never rewrites the number inside schema markup; crawlers and data aggregators should always encounter the canonical number there. Direct, untracked visitors seeing the default number should see the canonical one.
Google Business Profile has its own documented pattern: Google permits a call-tracking number as the primary phone number on a profile provided the canonical business number is kept on the profile as an additional number, which preserves the match against your other citations. That is a deliberate, static assignment — one dedicated tracking number for GBP-driven calls — not on-profile DNI; the profile is Google’s page, and your script does not run there.
Reading the attribution: CDRs are the source of truth
However the front end is built, the answer to “which campaign made the phone ring” ultimately comes out of call detail records. Each CDR row carries the called tracking DID; joining that against your number-to-source map turns the raw call log into a per-campaign call report.
The CDR fields do more than count calls. Duration separates real conversations from misdials and voicemail drops — a channel producing thirty-second calls is buying you hang-ups, not leads. Timestamps let session-level deployments join a call to the exact session that held the number, inheriting its keyword and click path. The calling number lets you de-duplicate repeat callers and match calls to CRM records. Add carrier-level call recording on the tracking DIDs and you can audit lead quality per channel, not just volume.
This is why per-number, real-time CDR access matters when choosing where the numbers live. If your carrier’s records are delayed, aggregated, or dashboard-filtered, your attribution inherits those limits no matter how good the script is.
Frequently asked questions
How does DNI decide which number a visitor sees?
The DNI script inspects the visitor’s arrival context at page load: the HTTP referrer, UTM parameters such as utm_source and utm_medium, and ad click identifiers like Google’s gclid. It matches that context against its configured rules — one number per channel, or a temporarily dedicated number per session — and rewrites the displayed phone number and tel: links accordingly. Visitors with no attribution signal, such as direct traffic, see the default number. The assignment usually persists for the visitor’s session via a cookie or local storage, so the same person sees the same number as they browse.
Does swapping numbers on my website break NAP consistency?
Not if the canonical number stays authoritative where it counts. Keep one canonical business number in your LocalBusiness structured data, your Google Business Profile, and your citations, and configure the DNI script so it never rewrites schema markup. The swap then only affects what tracked human visitors see in the rendered page. For Google Business Profile specifically, Google’s documented pattern is a static tracking number as the primary phone with the canonical number retained as an additional number, which preserves the citation match. What breaks NAP is publishing different canonical numbers across directories — not per-session on-site swapping.
What happens to DNI when JavaScript is blocked?
The swap never runs, and the visitor sees whatever number is hardcoded in the page — which is why the hardcoded fallback should always be your canonical business number, never a tracking number from the pool. Calls from script-blocking visitors arrive on the main line and attribute as untracked, the same as direct traffic. This is a small but real measurement gap: DNI undercounts rather than miscounts, since blocked sessions fall back to the default rather than being assigned to the wrong source. Search engine crawlers behave similarly for attribution purposes, which conveniently keeps the canonical number in what they index.
Do I need a call tracking platform to run DNI?
No — you need three layers, and the platform is only one way to get two of them. The required layers are: a pool of tracking DIDs, per-number call records, and a swap script with reporting. Platforms such as CallRail or Invoca bundle the script and dashboards on top of carrier numbers. Operators with their own analytics stack instead provision tracking DIDs directly from SIPNEX at carrier rates, consume the real-time CDRs, and run their own or an integrated swap script — skipping the platform fee. Which is right depends on whether you already have an analytics stack to join CDRs against.
Can a DNI tracking number be reassigned to a different campaign?
Yes, but only after a quarantine period. A tracking number that appeared in ads, cached pages, or a visitor’s call history keeps generating calls attributed to its old source after you remap it — someone redials the number they called last month, and your report credits the new campaign. Before reassigning, let the number rest until its inbound volume from the old assignment decays, and check its CDR history to confirm. Session-level pools avoid this at the session scale by design: the hold window plus a release buffer exists precisely so late calls still land on the right session’s number.
DNI is a script on top of carrier plumbing, and the plumbing is the part SIPNEX sells: tracking DID pools with real-time CDRs and call recording at standard DID pricing — no platform markup, A-level STIR/SHAKEN attestation on every number. Bring your own script or your preferred platform. Set up call tracking, see rates, or call (833) 665-2220.
Keep reading.
The carrier built by operators, for operators.
FCC-licensed carrier with its own STIR/SHAKEN SP certificate. Operator-owned. SIP trunks built for operators who dial at volume.