Dynamic number insertion (DNI) is a call-tracking technique that swaps a website's visible phone number for a unique tracking number tied to the visitor's traffic source or session. A JavaScript snippet assigns a number from a pool and replaces it in the DOM and tel: links, so every inbound call is attributed to the channel that drove it.

Key Takeaways

  • DNI swaps the visible number client side after page load, keeping the real NAP number in the page markup so local SEO is unaffected.
  • Session-level DNI needs a number pool sized to peak concurrent sessions, not total daily calls.
  • Pass GCLID, UTM, and GA4 client_id with the call so it closes the loop as an offline conversion in Google Ads and an event in GA4.
  • Source-level DNI is cheaper and simpler; session-level DNI is only worth it when you need keyword or ad-level granularity.
  • Most "number not swapping" bugs come from CDN caching, SPA route changes, or consent mode blocking the script.

How Does the Number Swap Actually Work?

The mechanism is deceptively simple. Your page ships with the real business number already in the HTML markup. After the document loads, a DNI script runs and, depending on the visitor's attribution, replaces the displayed digits with a tracking number pulled from a pool. The swap happens in three steps: detect source, assign number, replace in DOM.

Detection reads the document referrer, query string UTM parameters, or a cookie left by an earlier visit. Assignment picks a number that is free for the current session. Replacement walks the DOM and swaps every occurrence of the real number, including plain text and anchor tags.

DOM Replacement and Tel: Links

A naive replace on innerHTML can destroy event listeners and break layouts. A robust implementation targets specific elements by a data attribute such as data-phone and rewrites textContent plus the href of any tel: link:

document.querySelectorAll('[data-phone]').forEach(function(el){
  el.textContent = trackingNumber;
  if (el.tagName === 'A') { el.href = 'tel:' + trackingNumber.replace(/[^0-9]/g,''); }
});

This keeps click-to-call working on mobile and avoids re-parsing the whole tree. Without rewriting tel: links, mobile users tapping the number still dial the real number and the call is unattributed.

Session-Scoped Assignment and Storage

Once a visitor is assigned a number, it must stay consistent for that session. Store the assignment in a first-party cookie or sessionStorage keyed by a session id. On every subsequent page view, the script reads the stored number instead of requesting a new one. A typical cookie looks like dni_num=5550199; path=/; max-age=1800, where max-age matches the session timeout. If the cookie is missing or expired, the script requests a fresh number and writes a new cookie.

What Is a Number Pool and How Big Should It Be?

A number pool is the set of tracking numbers reserved for dynamic assignment. For session-level DNI, the pool must cover your peak concurrent active sessions, not your daily call volume. The standard sizing formula is:

pool size = (peak concurrent sessions) x (safety factor 1.1 to 1.3)

If you average 50 simultaneous visitors at peak and want a 20 percent buffer, provision roughly 60 numbers. Pool size scales with concurrency, so a high-traffic site with low call intent may need more numbers than a low-traffic lead-gen site.

What Happens When the Pool Exhausts?

When every number is checked out, the script has two options: fall back to the real business number (losing attribution for that session) or queue the request. Falling back is the safe default. To avoid exhaustion, monitor concurrent assignment and alert before the pool hits 90 percent utilization. Oversizing wastes money on unused numbers, while undersizing silently drops attribution.

Session Timeout Tuning

The timeout controls how long a number stays checked out after the last activity. Too short and a returning visitor gets a new number (fragmented attribution); too long and numbers sit idle and exhaust the pool. A 30-minute timeout matches typical web session windows. For sites with long consideration cycles, extend to 60 minutes but increase pool size proportionally.

Source-Level vs Keyword-Level vs Session-Level DNI?

These three modes trade cost and complexity for attribution granularity. Source-level swaps the number by channel (organic, paid, direct). Keyword-level requires the visitor to arrive from a tracked ad and assigns per keyword or ad group. Session-level assigns a unique number per browsing session regardless of source, enabling call attribution even for returning visitors and direct traffic.

Source-level is the cheapest and covers most SMB needs. Keyword-level pays off only when ad groups have meaningfully different conversion rates and you optimize bids on call outcomes. Session-level is worth it when a large share of calls come from direct or returning traffic that source tagging misses.

MethodAttribution AccuracyRelative CostPool NeedsBest Fit
Static tracking numberChannel only, no sessionLow (1-2 numbers)MinimalSingle landing page, one campaign
Source-level DNIPer referring sourceMedium (one per source)Small (5-15)Most SMBs splitting paid vs organic
Session-level DNIPer visitor sessionHighest (concurrent scale)Large (peak concurrency)High-traffic or multi-touch funnels

How Do You Pass GCLID and Utms with the Call?

A swapped number only tells you which session called. To make the call a measurable conversion, you must attach the visitor's GCLID, UTM tags, and GA4 client_id to the call record. The DNI script should capture these at assignment time and submit them to the call-tracking provider's API when the call connects.

For offline conversion tracking, store the GCLID in a cookie at the ad click and forward it with the call. In Google Ads, upload the call as an offline conversion keyed on GCLID so the ad that drove the click gets credit. In GA4, send the call as an event carrying client_id and session_id so it joins the user's session stream. Without this handoff, DNI tells you the channel but not the downstream revenue.

Does DNI Hurt Local SEO or NAP Consistency?

This is the most common objection, and it is mostly unfounded when implemented correctly. Search engines crawl the static HTML, which contains the real, consistent NAP number. The swap occurs only in the rendered DOM after JavaScript executes, so crawlers and citation sources see the canonical number. The rules that keep NAP safe:

  • Keep the real number in the server-rendered markup; never ship the tracking number as the default.
  • Run the swap client side so cached and crawled copies stay consistent.
  • Maintain identical citations across directories, GBP, and the site footer.

Where DNI does risk SEO is when the tracking number is hard-coded into cached page snapshots or CDN edge HTML. That produces inconsistent crawled numbers and is the failure mode to avoid, not the technique itself.

What Are the Common DNI Failure Modes?

  • Cached pages and CDN: a full-page cache can serve a previously swapped number to the wrong visitor. Bust the cache for the phone element or swap after cache with edge includes.
  • Single-page apps: route changes re-render without re-running the script, so the number reverts. Re-init the swap on every route change event.
  • Consent mode: if the script is blocked until consent, numbers never swap for denied users. Load a lightweight swap that respects consent categories.
  • Mobile tel: mismatch: text shows the tracking number but the tel: href still points to the real one. Always rewrite both.
  • Spam calls: bots and spam burn pool numbers by triggering assignments. Add bot filtering and shorter timeouts for low-trust traffic.

How Do You Diagnose "The Number Is Not Swapping"?

Use this ordered checklist before touching pool config:

  1. Open the page in a private window and confirm the real number is in the initial HTML source, not the tracking number.
  2. Check the browser console for script load errors or a blocked request to the DNI endpoint.
  3. Inspect the DNI cookie and sessionStorage to see whether an assignment was returned.
  4. Verify the referrer or UTM actually matches a configured source rule; a missing rule means no swap.
  5. Test from a CDN-cached URL to rule out edge caching of a swapped number.
  6. On an SPA, navigate between routes and confirm the number persists or re-swaps.
  7. Check consent mode state; a denied marketing category will suppress the script.
  8. Confirm pool utilization is below 100 percent in the provider dashboard.

What Is the Implementation Sequence?

A practical rollout order for a home services or lead-gen site:

  1. Audit current NAP placement and ensure the real number is in server-rendered markup everywhere.
  2. Choose the DNI mode (source-level first) and size the number pool from peak concurrency.
  3. Add the detection and DOM-replacement script, rewriting both text and tel: links.
  4. Persist the assignment in a cookie with a tuned session timeout.
  5. Capture GCLID, UTM, and GA4 client_id at assignment and forward them on call connect.
  6. Wire the call event into GA4 and the GCLID into Google Ads offline conversions.
  7. Add pool-utilization monitoring and a diagnostics checklist to your runbook.

Frequently Asked Questions

What Is Dynamic Number Insertion?

Dynamic number insertion is a call-tracking method that replaces a website's displayed phone number with a unique tracking number based on the visitor's source or session. A script assigns the number after page load and rewrites visible text and tel: links, letting each inbound call be tied back to the exact marketing channel that generated it.

Does Dynamic Number Insertion Hurt Local SEO Rankings?

No, not when implemented correctly. Search engines and citation crawlers read the static HTML, which keeps the real NAP number. The swap happens only in the rendered DOM after JavaScript runs. SEO risk appears only if a tracking number is cached into edge HTML or hard-coded as the default, producing inconsistent crawled numbers across pages.

How Many Numbers Do I Need in a DNI Pool?

Size the pool to peak concurrent active sessions, not total daily calls, then add a 10 to 30 percent safety buffer. A site with 50 simultaneous visitors at peak needs roughly 55 to 65 numbers. Extend session timeouts only if you also grow the pool, or numbers will exhaust during traffic spikes and fall back to the real number.

Why Is My Dynamic Number Insertion Script Not Swapping on Mobile?

The usual cause is rewriting only the visible text and not the tel: href, so taps still dial the real number. Other culprits are consent mode blocking the script before swap, a CDN serving a cached swapped number, or an SPA route change that re-renders without re-initializing the script. Run the diagnostics checklist to isolate which applies.