MenuIdentify users

Track

Identify users

Link a visitor to your own user id after signup and login, so later payments are credited to the channel that brought the person.

identify connects the anonymous visitor to a user id from your own database. Without it, ProductLense can count visitors but cannot tell which channel a paying customer came from.

JavaScript
window.productlense('identify', 'user_123', { email: 'ada@example.com', plan: 'starter' });

When to call it

  • After signup, once the account exists.
  • After every login.
  • On page loads for a signed-in user, if that is simpler. Repeated calls with the same id are harmless.

Each call sends a $identify event and attaches the id as uid to every later event in the same tab. The id is kept in sessionStorage, so it survives reloads but not a new tab; call identify again when a signed-in user opens a new tab.

Which id to use

Use the stable id of the user in your database: a UUID, a numeric id passed as a string, or an auth provider id such as auth0|abc. The id may be any non-empty string of at most 128 UTF-8 bytes without control characters, so an email address also works. Calls with any other value are ignored.

The second argument is optional traits, a JSON object stored as props on the $identify event. Send the user's email there: ProductLense stores it, lower-cased, with the identity, so a payment whose billing customer has the same email is matched to this visitor even when the customer carries no user id.

Show names and avatars

Send name and an image (or avatar) URL in traits and ProductLense shows the person instead of an anonymous visitor: on the live globe and its activity feed, in Visitors, Customers and the Events page, and on each visitor's page.

JavaScript
window.productlense('identify', 'user_123', {
  email: 'ada@example.com',
  name: 'Ada Lovelace',
  image: 'https://lh3.googleusercontent.com/a/abc=s96-c',
});
  • Name. Up to 80 characters; markup characters are removed. A later call without a name keeps the stored one, a new name replaces it.
  • Image. Only https URLs on these avatar hosts are kept: gravatar.com (and secure./www.), lh3.googleusercontent.com, avatars.githubusercontent.com, pbs.twimg.com, cdn.discordapp.com, avatar.vercel.sh, img.clerk.com, images.clerk.dev and *.supabase.co. Anything else is dropped and the person gets initials. This keeps the dashboard from loading images from arbitrary hosts.
  • Anonymous visitors get a stable two-word name for their session, such as "Cyan Chipmunk", so the live feed is readable without identifying anyone.
  • Public dashboards show identified names masked ("Ada** L**"), never the avatar or the visitor id.

Identify from your server

When the user logs in on your backend, or from a mobile app's API, call POST /api/v1/identify with an API key that has the events scope (Scale plan; Settings → API keys):

Shell
curl -X POST https://api.productlense.app/api/v1/identify \
  -H "Authorization: Bearer mk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"user_id":"user_123","visitor_id":"3f9a1c0b7d2e4a51","traits":{"name":"Ada Lovelace","email":"ada@example.com","image":"https://secure.gravatar.com/avatar/..."}}'
  • user_id is required (at most 128 characters).
  • visitor_id is optional: the 16-character id from window.productlense.visitorId or the /api/vid endpoint. With it, the call links that visitor to the user, as a browser identify does, including the email for payment matching. Without it, only the user's traits are updated, for every visitor already linked to the user.
  • traits.email must be an email address; name and image follow the rules above.
  • The response is { "user_id": "user_123", "linked": true }. Errors: 400 invalid body, 401 missing or revoked key, 403 key without the events scope.

Put the same id on the billing customer

A payment is matched to a visitor through the customer in your billing provider. The most reliable link is the same user id stored on that customer:

ProviderWhere to put the user id
StripeCustomer metadata pl_user_id
PaddleCustomer custom data pl_user_id
RazorpayCustomer or payment notes pl_user_id
PolarCustomer external id
ChargebeeCustom field cf_pl_user_id or cf_user_id
Payments APIcustomer.user_id

ProductLense reads the metadata keys pl_user_id, user_id, userId and uid, in that order. Each provider page under Connect billing says where its metadata lives.

Framework examples

app/productlense-identify.tsx

'use client';
import { useEffect } from 'react';

export function ProductLenseIdentify({ userId }: { userId?: string }) {
  useEffect(() => {
    if (userId) window.productlense?.('identify', userId);
  }, [userId]);
  return null;
}

src/useProductLenseIdentify.ts

import { useEffect } from 'react';

export function useProductLenseIdentify(user: { id: string } | null) {
  useEffect(() => {
    if (user) window.productlense?.('identify', user.id);
  }, [user]);
}

src/routes/+layout.svelte

<script lang="ts">
  let { data, children } = $props();

  $effect(() => {
    if (data.user) window.productlense?.('identify', data.user.id);
  });
</script>

{@render children()}

plugins/productlense.client.ts

export default defineNuxtPlugin(() => {
  const user = useState<{ id: string } | null>('user');
  watch(
    user,
    (u) => {
      if (u) window.productlense?.('identify', u.id);
    },
    { immediate: true },
  );
});

functions.php

<?php
add_action('wp_footer', function () {
    if (!is_user_logged_in()) return;
    printf(
        '<script>window.productlense=window.productlense||function(){(window.productlense.q=window.productlense.q||[]).push(arguments)};window.productlense("identify",%s);</script>',
        wp_json_encode((string) get_current_user_id())
    );
});

For TypeScript, declare window.productlense once as shown in Custom events.

How it affects attribution

A visitor who identifies joins every visitor that used the same id. When that user pays, the earliest first touch among those visitors wins. Payments made before the identify call arrives are retried every night for 90 days. See How payments are attributed.

Privacy

identify links pseudonymous visits to a person in your system, so the data linked to that id is personal data you control. Do not send special category data (health, religion and so on) as traits. See GDPR and India DPDP.

    ↑ ↓ to move, Enter to open