Session Handoff
The tracker knows the session id. Your server does not, because there is no cookie to read. Anything you want to record from the backend - an order paid, a trial started, a user signed in - needs the id carried across first. The tracker ships four ways to do it, all idempotent and all safe to call before the session is ready.
| Method | Transport | Best for |
|---|---|---|
signals.attachToFetch() | Header on your own fetch/XHR calls | SPAs and apps that talk to their backend over JSON |
signals.attachToForm(form) | Hidden input in a form you post | Classic form posts: checkout, sign-up |
signals.bindTo(url) | One POST to an endpoint you choose | Stash the id in your server session once per visit |
signals.cookieTo() | A short-lived first-party cookie | Same as bindTo, without writing an endpoint |
Two names are fixed across the tracker, the API and the SDK:
signals.headerName; // 'X-Snipform-Session-Id'signals.formFieldName; // 'snip_session_id'The server-side session actions read the session id from the request body, then that header, then that field, so whichever bridge you use, the API finds it.
attachToFetch(options?)
Section titled “attachToFetch(options?)”Patches window.fetch and XMLHttpRequest so every same-origin request carries X-Snipform-Session-Id. Requests to other origins are untouched unless you list them.
signals.attachToFetch();signals.attachToFetch({ origins: ['https://api.example.com'] });Call it once, any time after the script tag; requests made before the session resolves simply go out without the header. Axios and other XHR-based clients are covered by the XHR patch.
attachToForm(target, fieldName?)
Section titled “attachToForm(target, fieldName?)”Injects <input type="hidden" name="snip_session_id"> into a form and keeps its value current. target is a form element or a CSS selector. Returns the form, or null if the selector does not match a <form>.
signals.attachToForm('#checkout');signals.attachToForm(document.querySelector('form.signup'), 'visitor_session');Call it per form. Forms rendered later need their own call after they exist.
bindTo(url, options?)
Section titled “bindTo(url, options?)”Sends one POST to your endpoint with { "session_id": "..." } as JSON, as soon as the session is ready. Fire-and-forget with keepalive, so it survives an immediate navigation.
signals.bindTo('/snipform/bind');| Option | Default | Notes |
|---|---|---|
headers | CSRF from <meta name="csrf-token"> as X-CSRF-TOKEN | Object or function returning one. Pass {} to send no extra headers. |
body | { session_id } | Function (payload) => object to reshape the JSON |
credentials | 'same-origin' | 'include' for a cross-site API that accepts your cookies |
skipIfMatch | none | Skip the POST when the resolved id equals this string |
// CSRF is picked up from the meta tag automaticallysignals.bindTo('/snipform/bind');signals.bindTo('/api/snipform-bind', { headers: () => ({ 'X-CSRFToken': getCookie('csrftoken') }),});signals.bindTo('https://api.example.com/bind', { headers: { Authorization: 'Bearer ' + token }, credentials: 'omit',});skipIfMatch is how you make the second page load free: render the id your server already holds into the call, and the tracker skips the POST when nothing changed.
signals.bindTo('/snipform/bind', { skipIfMatch: '{{ session("snipform_session_id") }}' });cookieTo(name?, options?)
Section titled “cookieTo(name?, options?)”Writes the session id into a first-party cookie meant to live for exactly one round trip: your backend reads it, copies the value into its own session, and clears the cookie on the response.
signals.cookieTo(); // cookie 'site_initializer'signals.cookieTo('snip_sid', { maxAge: 120 });signals.cookieTo({ name: 'snip_sid', sameSite: 'Strict' });| Option | Default |
|---|---|
name | 'site_initializer' |
maxAge | 300 seconds |
path | '/' |
sameSite | 'Lax' |
secure | true on HTTPS, false on HTTP |
domain | not set |
skipIfMatch | none |
This is a transport cookie set by your page for your server, not a tracking cookie: it carries an id the server could already have received in a header, and it is gone after the server reads it. Clear it on first read so that stays true.
What the backend does with it
Section titled “What the backend does with it”Once the id is in your server session (or on the order, or on the user), the server-side writes are one call each:
use SnipForm\Laravel\Facades\Snipform;
Snipform::event('trial_started', meta: ['plan' => 'pro']);Snipform::revenue(12900, 'EUR');Snipform::identify($user);The SDK’s hydrate loader does the bindTo/cookieTo call and the stashing for you. See Laravel.
POST https://api.snipform.io/v2/property/session/eventAuthorization: Bearer <token with signals:write>Content-Type: application/json
{ "session_id": "...", "name": "trial_started", "meta": { "plan": "pro" } }Reference: Session Actions.