Phone tracking

Swap tel: links to dynamic DIDs. Every call is attributed back to the visitor’s campaign, channel and landing page.

You provision DIDs (numbers) inside Funnelion.ai, and the telephony layer handles routing, recording and the call-event webhook back into Funnelion.ai. Visitors see a per-session or per-source number on your site; calls forward to your real line transparently.

1. Dashboard setup

  1. Create a phone pool at Pools → New: channel phone, type dynamic (one number per visitor session, exclusive) or static (one number per traffic source, shared). Pick the target size - how many DIDs the pool should hold.
  2. Allocate DIDs through Phone numbers → New. Each row provisions a DID, configures forward-to + caller-ID, and attaches the number to your pool.
  3. Add a swap zone at Swap zones → New: kind phone, selector a[href^="tel:"]. Leave the pool blank if a routing rule should pick the pool per visitor; set it explicitly for a single-pool site.
  4. Add routing rules to map UTM / referrer patterns to pools (e.g. utm_source=facebook → Facebook DID pool, utm_medium=cpc → Other Paid pool). The first matching rule wins by priority.

2. Frontend path (JS snippet)

Same snippet as the other channels. The snippet binds to every tel: link on the page, swaps the visible number and the dial-href, and writes a per-visitor cookie so subsequent page loads reuse the same number.

HTML
<!-- Production (minified, recommended) -->
<script async src="https://dash.funnelion.ai/track.min.js?t=YOUR_SITE_TOKEN"></script>

<!-- Same code, unminified - handy when debugging in DevTools -->
<script async src="https://dash.funnelion.ai/track.js?t=YOUR_SITE_TOKEN"></script>

Anchors marked with data-funnelion="Phone Number" (whatever your swap zone is named) get rewritten by the snippet on page load:

HTML
<a href="tel:+37060000000" data-funnelion="Phone Number">
  +370 600 00 000
</a>

The hardcoded number is your fallback - what callers reach if the snippet is blocked. Use a real number you’re happy for visitors to see when tracking is offline.

3. Backend path (PHP SDK)

Server-side resolution survives the realistic blockers (ad blockers, CSP, most of Apple ITP). Same SDK as the email path - one resolveOrNull() call covers all your swap zones.

PHP
use Funnelion\Client;
use Funnelion\Config;
use Funnelion\Cookie\Session;
use Funnelion\Html\ZoneSwapper;
use Funnelion\Resolve\Request;

$client = new Client(new Config(
    siteToken: getenv('FUNNELION_SERVER_SIDE_TOKEN'),
    timeoutSeconds: 0.5,
));

$response = $client->resolveOrNull(new Request(
    url:       'https://yoursite.com'.($_SERVER['REQUEST_URI'] ?? '/'),
    ip:        $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0',
    referrer:  $_SERVER['HTTP_REFERER'] ?? null,
    userAgent: $_SERVER['HTTP_USER_AGENT'] ?? null,
    visitorId: Session::readFromGlobals(),
));

if ($response !== null) {
    $html = (new ZoneSwapper())->swap($html, $response);
    if ($response->visitorId !== null) {
        header('Set-Cookie: '.Session::headerValue($response->visitorId), false);
    }
}

Mark a phone anchor with the zone name from the dashboard:

HTML
<a href="tel:+37060000000" data-funnelion="Phone Number">+370 600 00 000</a>

When the anchor wraps an icon

The PHP ZoneSwapper regex requires a leaf element. If your link looks like <a><PhoneIcon />+370 …</a>, the server-side swap can’t reach the visible text. Either move the icon outside the link, or read the resolved number in JSX from window.__FUNNELION_ZONES.

4. What happens when a call comes in

  • The telephony layer receives the call on the DID, forwards it to your configured destination, and posts the call event (start / answer / hangup / duration / recording URL) to Funnelion.ai via signed webhook.
  • Funnelion.ai ingests it as a lead event of kind call, attributed to the visitor session that claimed the DID.
  • The dashboard shows the call alongside the visitor’s UTMs, landing page and any other touches on the same session.

Static pools (one DID per source) attribute calls by which pool the DID belongs to - no visitor cookie required, which lets calls from offline channels (Google Maps profiles, flyers, business cards) still attribute correctly.