On one project, we lost 40% of call attribution due to incorrect session_id passing in an SPA. After implementing proper ct.js reinitialization, the source was identified with 98% accuracy. This real case shows that even large teams miss this detail. We share a proven approach tested on dozens of projects with various frameworks, from classic jQuery to Next.js and Vue 3.
Calltouch is a call tracking system with an analytics core: dynamic number replacement, call and lead attribution, source dashboards. Integration covers counter installation, event setup, lead data transfer via API, and correct call tracking in SPA environments. Our experience: 10+ years, over 50 projects. Order integration and start getting accurate call attribution.
What is session_id and why is it important?
Session_id is the key identifier linking a user's visit to their actions: calls, leads, page views. If session_id is lost, attribution breaks — you won't know which channel brought the client. Calltouch generates session_id when ct.js loads and stores it in the _ct_session cookie. Without correctly passing this ID to the backend, all leads will be marked as "direct entry".
Counter architecture
Calltouch works through the ct.js JavaScript library which:
- sets
_ct_sessionand_ct_leadcookies with visit identifiers; - replaces phone numbers on the page via CSS classes or
data-ct-phoneattributes; - sends interaction events to Calltouch servers.
<script> (function(w, n) { w[n] = w[n] || []; var s = document.createElement('script'); s.type = 'text/javascript'; s.async = true; s.src = '//mod.calltouch.ru/init.js?id=SITE_ID'; var f = document.getElementsByTagName('script')[0]; f.parentNode.insertBefore(s, f); })(window, 'ct'); </script> SITE_ID is the identifier from the "Settings → Integrations" section of your personal account.
How to get session_id for lead transfer?
Before sending a lead to the backend, get the current session_id. Calltouch provides a synchronous method:
function getCalltouchSessionId() { if (typeof ct === 'undefined') return null; try { const data = ct('getSessionId'); return data?.sessionId ?? null; } catch (e) { console.warn('Calltouch: unable to get session ID', e); return null; } } document.getElementById('lead-form').addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(e.target); formData.append('ct_session_id', getCalltouchSessionId() ?? ''); await fetch('/api/leads', { method: 'POST', body: formData }); }); Sending a lead via API
After saving the lead on the backend, send it to Calltouch. Use a POST request with parameters sessionId, sessionToken, subject, phoneNumber, requestUrl.
Endpoint: POST https://api.calltouch.ru/calls-service/RestAPI/requests/[siteId]/register/
| Parameter | Type | Description |
|---|---|---|
| sessionId | string | Visit identifier from ct() |
| sessionToken | string | Site token from the account (API section) |
| subject | string | Lead subject |
| phoneNumber | string | Phone number without + and spaces |
| requestUrl | string | URL of the submission page |
Official Calltouch API documentation
PHP implementation
class CalltouchService { private string $siteId; private string $sessionToken; private string $apiBase = 'https://api.calltouch.ru/calls-service/RestAPI/requests'; public function __construct() { $this->siteId = config('services.calltouch.site_id'); $this->sessionToken = config('services.calltouch.token'); } public function registerRequest(array $data, string $sessionId): array { $url = "{$this->apiBase}/{$this->siteId}/register/"; $response = Http::asForm()->timeout(5)->post($url, [ 'sessionId' => $sessionId, 'sessionToken' => $this->sessionToken, 'subject' => $data['subject'] ?? 'Site lead', 'name' => $data['name'] ?? '', 'phoneNumber' => preg_replace('/\D/', '', $data['phone'] ?? ''), 'email' => $data['email'] ?? '', 'requestUrl' => $data['url'] ?? '', 'comment' => $data['comment'] ?? '', ]); if (!$response->ok()) { Log::error('Calltouch API error', ['status' => $response->status(), 'body' => $response->body()]); } return $response->json() ?? []; } } Why does number replacement break in SPA?
In React/Vue/Angular applications, ct.js performs replacement once after load. When navigating between pages without a full reload, numbers stop being replaced. This is fixed by calling ct('reInit') on route change. This is especially critical for single-page applications where each page is a virtual route without reload. Without reinitialization, all calls from internal pages lose attribution.
// React Router v6 import { useLocation } from 'react-router-dom'; import { useEffect } from 'react'; export function CalltouchReinit() { const location = useLocation(); useEffect(() => { if (typeof window.ct === 'function') { setTimeout(() => window.ct('reInit'), 300); } }, [location.pathname]); return null; } // Vue Router router.afterEach(() => { setTimeout(() => window.ct?.('reInit'), 300); }); How to set up a webhook for incoming calls?
To automatically transfer call data, use a webhook. In your Calltouch account, go to "Integrations → Webhook" and enter your endpoint URL. Calltouch will send a POST request with JSON call data: callId, phoneNumber, duration, source, and other fields. This allows real-time CRM updates, manager notifications, and call quality analysis. Example PHP processing:
public function handleWebhook(Request $request): JsonResponse { $data = $request->json(); // Logic: save to DB, notify, update deal return response()->json(['status' => 'ok']); } Setting up goals via Calltouch event API
Besides calls and leads, you can send arbitrary conversion events. For example, track add-to-cart or purchase events. Each event deepens analytics and allows more accurate channel effectiveness evaluation.
ct('send', 'event', { eventName: 'add_to_cart', value: 2990, currency: 'RUB', orderId: 'CART-456', }); ct('send', 'event', { eventName: 'purchase', value: 14500, currency: 'RUB', orderId: 'ORDER-789', items: [{ id: 'SKU-001', name: 'Product A', quantity: 2, price: 7250 }], }); What the work includes
- Installation and configuration of the Calltouch counter (key, goals, number replacement)
- Implementation of session_id and lead transfer via API
- SPA integration (reinitialization, correct attribution)
- Webhook setup for incoming calls
- Documentation and access handover
- Training for your team (up to 2 hours)
Estimated timelines
| Stage | Time |
|---|---|
| Counter installation and number replacement | 3–4 hours |
| API lead transfer implementation | 4–6 hours |
| Webhook integration (optional) | 1–2 days |
| Full cycle (all together) | 2 to 4 days |
Pricing is calculated individually. Get a consultation — we'll share details. Contact us to start the setup.
Debugging and verification
Checklist:
- DevTools → Network:
init.js?id=SITE_IDloads (200) - Console:
ct('getSessionId')returns an object withsessionId - Phone number on the page is replaced with a Calltouch pool number
- In the account → "Lead Journal" test lead appears with correct source
Approach comparison: real-time API transfer is 3 times faster than CSV file import and gives 100% attribution accuracy.
Order Calltouch integration — we'll set up call tracking for your project.







