← Documentation

Connect a customer journey with consent

Connect consented browsers to an application account using a short-lived assertion signed by your own server.

Start with basic tracking

Installing the tracker records cookieless daily estimates. It does not identify an account or connect anonymous history across days. You can use traffic and revenue reports without enabling customer identity.

For customer identity, enable consent-gated attribution for the selected site in Settings and integrate your existing visitor-consent flow. The setting makes the capability available; it does not grant consent. Never infer consent from a signup, login, email address, or purchase.

Keep the signing key on your server

In the installation guide, a workspace owner explicitly creates or reveals the website identity key. Store it in your merchant server as VISITLIFT_IDENTITY_KEY. It is separate from your Stripe signing secret. Never use a NEXT_PUBLIC environment variable, commit it, or return it to a browser.

Implement an authenticated identity endpoint in your own application. Obtain the customer ID from your existing server session, never a request-body customer ID. Use a stable opaque internal ID, not a name or email. The helper hashes that account reference with a site-specific key before it leaves your server.

The portable Node.js example below is a signing helper, not a replacement for authentication or CSRF protection. Wire it into your existing authenticated route and return only its short-lived assertion. Replace YOUR_SITE_ID with your actual website ID.

// SERVER ONLY: implement /api/visitlift-identity in your website.
// Require your existing authenticated session AND same-origin/CSRF protection.
// Read authenticatedUser from that session, never from the request body.
import { createHmac } from 'node:crypto';
const SITE_ID = "YOUR_SITE_ID";
export function signVisitLiftCustomer(authenticatedUser, challenge) {
  const secret = process.env.VISITLIFT_IDENTITY_KEY;
  if (!/^vl_identity_[a-f0-9]{64}$/.test(secret || '')) throw new Error('Configure the server identity key');
  if (!/^[a-zA-Z0-9_-]{8,128}$/.test(authenticatedUser.id)) throw new Error('Use an opaque internal customer ID');
  if (!/^[a-f0-9]{64}$/.test(challenge)) throw new Error('Invalid challenge');
  const key = Buffer.from(secret.slice('vl_identity_'.length), 'hex');
  const iat = Math.floor(Date.now() / 1000);
  const customerKey = createHmac('sha256', key).update(`customer:${SITE_ID}:${authenticatedUser.id}`).digest('hex');
  const body = Buffer.from(JSON.stringify({ v: 1, aud: 'visitlift_customer_identity', siteId: SITE_ID, customerKey, challenge, iat, exp: iat + 300 })).toString('base64url');
  const assertion = body + '.' + createHmac('sha256', key).update(body).digest('base64url');
  return { assertion }; // Return ONLY this short-lived assertion to the browser.
}
// Never send the signing key or raw customer ID to VisitLift or browser code.
// Email addresses and names are not valid customer IDs.

Connect an authenticated, consented browser

After explicit visitor consent and authentication, request a browser challenge, send only that challenge to your authenticated server, and pass its signed assertion to identify(). A challenge expires after five minutes and can be consumed only by its original consented browser.

The tracker waits for accepted collection before requesting identity. It returns no challenge without consent; a blocked or unavailable tracker must not block login, signup, or Checkout. Check the identify result before reporting success.

A signed reference may connect consented browsers that your application identifies as the same account. It is not verification of a real person, and shared email, IP, or device characteristics do not merge accounts.

// Run only AFTER your existing consent manager has granted permission
// and the customer is authenticated. This code never grants consent.
const challenge = await window.visitlift?.getIdentityChallenge();
if (challenge) {
  const response = await fetch('/api/visitlift-identity', {
    method: 'POST', credentials: 'same-origin',
    headers: { 'Content-Type': 'application/json' },
    // Include your application's normal CSRF protection too.
    body: JSON.stringify({ challenge: challenge.challenge })
  });
  if (response.ok) {
    const { assertion } = await response.json();
    const identified = await window.visitlift?.identify(assertion);
    // Check identified; do not report success without an accepted link.
  }
}
// On logout or account change, before identifying another account:
function onCustomerLogoutOrAccountChange() {
  window.visitlift?.reset();
}
// Wire this function into your existing logout/account-change handler.
// Reset clears consent and browser identity. A fresh grant is required.

Reset on logout and account changes

Call reset() from your real logout and account-change handlers before identifying another account. Reset removes browser continuity and clears consent; a new explicit grant is required. A browser already linked to a different account cannot silently be reassigned.

Reset and consent revocation invalidate pending identity work. Previously permitted historical activity may remain within retention, while subsequent daily anonymous estimates are not joined to the old account.

Verify the complete path

Open the installation guide and refresh verification. Tracking received requires an actual accepted event. Customer identity linked requires a valid signed account link and consented event. Payment linked requires a positive signed Stripe Checkout connected to that same site, browser, and customer profile.

Choose Test or Live evidence explicitly. A test purchase can verify the test path but never becomes live revenue. A generic webhook delivery or a visible script alone does not verify a linked purchase.

On Visitor journeys, open a linked browser and choose View customer journey. The profile combines only signed, consented browser history, with owner-only payer email permissions preserved. Unmatched payments stay explicitly unattributed.

Measure return visits to payment

In Funnels, choose Purchase journeys. Define two to five ordered steps ending in verified paid Checkout and choose a 7-day or 30-day conversion window. Within one visit retains the original visit-based funnels.

Customer funnels join signed account references or consented browser continuity. Daily anonymous estimates are excluded from their denominator. Incomplete entrants whose window is still open are Pending; Expired means the selected observation window closed without a measured completion. Neither proves what happened outside tracking.

These funnels use collection order, a selected payment currency, and separate test/live data. Elapsed time ends when the payment notification is received, which can differ from Stripe payment time. Legacy payments without a recorded receipt timestamp are shown as exclusions.