Identifying a user tells Gleap who is on the other side of the conversation. Without it, every visitor is anonymous, which limits what Kai can answer and what you can target. This article is for whoever adds Gleap to your product's code. It works the same on every plan and in every SDK.
Run this wherever your app knows who the user is: right after login, and on page load for a session that's already signed in.
Gleap.identify("user-123", {
name: "Franz Huber",
email: "[email protected]",
plan: "Pro",
createdAt: new Date("2024-03-01"),
company: {
id: "acme-inc",
name: "ACME Inc."
},
customData: {
seats: 12,
trialEndsAt: "2026-09-01"
}
});The first argument is your own user ID. Use the value your database uses, and keep it stable. It's how Gleap recognises someone across sessions and devices, so an ID that changes produces a new contact each time.
Call Gleap.clearIdentity() when someone logs out. On shared computers, skipping this leaves the next person signed in as the previous one.
Gleap understands these built-in fields: name, email, phone, avatar, plan, value, sla, createdAt and tags, plus the company object. They land in the right places: the email is the address you reply to, and the avatar is the picture your agents see.
Anything else goes in customData as key-value pairs, for example seats, a trial end date or a feature flag you care about. Custom data shows on the contact, works in audience filters and segments, and Kai can read it.
Values can be text, numbers, true/false, dates, or lists of text or numbers. Nested objects are dropped, so flatten them first (billingPlan, not billing: { plan }).
Up to 100 custom keys are stored per call. Send the handful you'd actually filter on: a contact with six attributes is easier to read than one with fifty.
Don't reuse a built-in name for something else. A custom value or plan is treated as the built-in field, with its type checks.
When something changes mid-session, such as an upgrade, send just the fields that changed:
Gleap.updateContact({ plan: "Enterprise" });Passing null for a field clears it.
Attribute keys are case-sensitive. trialEndsAt and trialendsat are two different attributes, and filters, audiences and segments match the exact casing. A rule built on one silently ignores data sent under the other.
When you define an attribute under Settings → Inbox → Data attributes, the editor shows a Casing mismatch warning if your recent contact data uses a different spelling. Fix the spelling in your code rather than ignoring it.
Pass a company object to group contacts under the organisation they belong to. That's what lets you see everyone from one customer together, and what company-level SLAs are based on.
The id is required. Without it the whole company is ignored. Use a stable identifier from your own system, not the display name.
Only id and name are used. Put company-level attributes such as seat count in the user's customData, or set them on the company record through the server-side Companies API. company.name only fills in a company that has no name yet and never replaces an existing one.
The nested object needs SDK 16.3.0 or later. On earlier versions, pass the flat companyId and companyName instead. Those keep working.
Attributes say what someone is. Events say what they did:
Gleap.trackEvent("invited-teammate", { count: 3 });Events show on the contact's page, and outbound messages and workflows can react to them: a checklist that advances when someone finishes setup, or a survey after a first export. Name each event for what happened, in one consistent style. "invited-teammate", "Invite Sent" and "invite_teammate" are three different events. Gleap already tracks page views on its own, so you don't need to send those.
Sign in to your own product so identify runs, and trigger one event.
In Gleap, open Contacts and find yourself.
Check your custom attributes in the details on the right of the contact page. Attributes you've defined show there (All N attributes opens the rest), and data you haven't defined as an attribute yet is listed under Custom data. Your event is under the Activity tab.

If nothing shows up, check that identify ran at all. A contact still marked as a guest is the usual explanation.
The same calls exist in the mobile and cross-platform SDKs, with the same field names. If you have both a web app and a mobile app, identify with the same user ID in both so people don't end up as two contacts.
By default, anyone who can read your client-side code could call identify with someone else's user ID and see their conversations. Identity verification closes that gap. Only admins can change it.
Go to Settings → Security → User identity and copy the Secret key under Identity verification secret. This is not your API key.
On your server, create an HMAC-SHA256 of the user ID with that secret, and pass it to your app as the third argument of identify:
// On your server
const crypto = require("crypto");
const userHash = crypto
.createHmac("sha256", process.env.GLEAP_SECRET)
.update(String(user.id))
.digest("hex");
// In your app
Gleap.identify("user-123", { name: "Franz Huber" }, userHash);Once every version of your app sends the hash, switch on Enforce identity verification. From then on, identify calls without a valid hash are rejected.

Example code for other server languages is in the developer documentation.