Identify and leads
A lead is a person linked to a session – the bridge between anonymous analytics and your CRM. 3x offers two ways to create leads, and both keep personal data minimal.
identify() in the browser
import { identify, getIds } from "@3x-engine/sdk";
form.addEventListener("submit", async () => {
await identify({
email: form.email.value,
firstName: form.firstName.value,
form: "contact",
traits: { plan: "pro", employees: "11_50" },
});
});- E-mail, phone, first name, last name and external id are normalized and hashed with SHA-256 in
the browser (WebCrypto). Only hashes travel; the API never sees plaintext from
identify(). - Phone numbers: digits with country code;
00prefixes and(0)trunks are handled; national numbers get the project country's calling code, and are dropped when the project has no country. - E-mail: lowercased; an additional Google variant (dots removed for Gmail) is hashed for Google Ads.
traits: up to 20 non-personal attributes; values that look like e-mail addresses or phone numbers are dropped.- Sent only with consent unless the project sets
tracking.identify.requireConsent = false(decide with your DPO). - One lead row per session and identity: calling
identify()repeatedly updates the same row.
The session gets identified = true, goals of kind lead fire, the relevance model adds its
identified points, and conversion definitions with a lead trigger record a conversion
(subject to the web guards).
Leads from your server
When the form posts to your backend anyway, create the lead there with a secret key – this is the most robust path and the only one that can store plaintext:
import { createX3Server } from "@3x-engine/sdk/server";
const x3 = createX3Server({ apiUrl: process.env.X3_API_URL!, apiKey: process.env.X3_SECRET_KEY! });
await x3.leads.create({
sessionId, // from getIds() in the browser
form: "contact",
email, firstName, lastName, company,
message, // stored only when the project stores plaintext
answers: { "pricing-quiz": { team_size: "11_50" } },
summary: "Pro plan, 11–50 seats, wants a demo",
spam: honeypotFilled, // spam leads are kept but excluded everywhere by default
conversion: { definition: "lead", eventId },
});| Field | Notes |
|---|---|
email, phone, firstName, lastName, externalId | always hashed; plaintext e-mail, names and company kept only with settings.pii.storePlaintext |
company, message | plaintext only with storePlaintext; message is never returned by MCP |
answers | {questionnaire: {question: option}} |
score, grade, summary | your qualification |
spam | honeypot result; excluded from lists, MCP, goals, identified and lead triggers unless include_spam=true |
attribution | for leads without a session |
conversion | also record a conversion of this definition |
Requires the scope leads:write. The request is idempotent with an Idempotency-Key header.
Reading and erasing leads
- Dashboard Leads (members with the personal-data permission), CSV export.
GET /v1/projects/{project}/leadsand/leads.csv(leads:read), MCPleads_list(never phone numbers or free text).DELETE /v1/projects/{project}/leads/{id}erases a lead and the hashes on its conversions.settings.pii.leadsRetentionDaysdeletes leads automatically after N days.