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.







