Developer Guide · Guides
Widget identity & merge
How Inboxi turns website and app chat sessions into contacts: what a visitorId is, what identify() stores, how chats from the same person are grouped, and how to model one contact with many support tickets.
When someone chats through your website widget or mobile SDK, Inboxi has to decide two things: which conversation (ticket) the message belongs to, and which person (contact) that conversation belongs to. Inboxi keeps these separate on purpose: conversations are matched by session, contacts are grouped by identity. The SDK calls themselves are documented in the SDK section.
The visitorId: one conversation
Each SDK session starts with POST /api/widget/sdk/init. The userId you pass there becomes the session'svisitorId (Inboxi generates one if you don't). A conversation is stored per visitor:
- Same visitorId (and same widget): the existing conversation is reused. Closing and reopening the app continues the same thread.
- New visitorId: a new conversation is created. Inboxi deliberately does not merge it into an older one, because that would collapse separate support threads into one.
identify(): who the person is
identify() (POST /api/widget/sdk/identify) attaches identity to the session's conversation. name is required; everything else is optional but strongly recommended.
| Field | Stored as | Used for |
|---|---|---|
name | Contact name | Chat header, contact card. Keep it clean: the person's name only. |
email | metadata.email | Grouping chats into one contact; contact panel. |
phone | metadata.realPhone | Grouping; order lookups by the AI agent; contact panel. |
userId | metadata.externalUserId | Your user ID. Echoed back in every webhook's contact block; filter with ?externalUserId=. |
data | metadata.customData | Any extra fields (plan, ticket number, city). Shown as custom fields; filter with ?customData.plan=pro. |
Inboxi.identify({
userId: 'u_12345',
name: 'Ahmed Ali',
email: 'ahmed@example.com',
phone: '201001234567',
data: { plan: 'pro', city: 'Riyadh' }
});Over time each conversation accumulates these identity keys in its metadata:
{
"email": "ahmed@example.com",
"realPhone": "201001234567",
"externalUserId": "u_12345",
"customData": { "plan": "pro", "city": "Riyadh" },
"knownVisitorIds": ["3f1c…-a91b_t42", "3f1c…-a91b"],
"baseVisitorId": "3f1c…-a91b",
"lastTicketId": "12345",
"lastSubject": "Booking issue",
"ticketHistory": [{ "id": "12345", "subject": "Booking issue", "at": "2026-10-10T09:00:00Z" }]
}How contacts are merged
On the Contacts page, conversations that share an email, a realPhone, a baseVisitorId or any of theknownVisitorIds are shown as one contact with a “N chats” badge. Open the contact to see every individual conversation in its Chats tab. The inbox still shows each conversation separately, so agents work ticket by ticket while seeing the whole history of the person.
knownVisitorIdscollects every visitorId that has been used for a conversation.baseVisitorIdis the device ID with any per-ticket suffix removed. Suffixes like_t535703,_ticket_1776…or_session_…are stripped, so tickets from the same device are recognised as the same person even without an email or phone.
Pattern: one contact, many tickets
If your app opens a new support ticket each time, you want separate threads in the inbox but one contact per customer. Do both things:
- 1Unique visitorId per ticketStart the session with a userId such as
`${user.id}_ticket_${ticket.id}`. Each ticket becomes its own conversation. - 2Stable identity in identify()Pass the same
email/phoneevery time, and put ticket details indata(ticket_id,ticket_subject), not in the name. - 3ResultAgents see one conversation per ticket; the Contacts page shows one person with all tickets; webhooks carry your IDs in the contact block.
// User opens ticket #369841
Inboxi.identify({
userId: `clinic_${user.clinicId}_ticket_${ticket.id}`, // unique per ticket
name: user.clinicName, // just the name
email: user.email,
phone: user.phone,
data: { ticket_id: ticket.id, ticket_subject: ticket.subject, clinic_id: user.clinicId }
});For “one conversation per customer” instead, simply use the same userId every time.
Composite name parser
Some integrations put several values in the name, like "Ahmed (Clinic) | #12345 | Booking issue". Inboxi recognises the | separator and splits it: the first part becomes the contact name, a #number part becomes metadata.lastTicketId, the next part becomes metadata.lastSubject, and each ticket is added tometadata.ticketHistory (last 20 kept). Agents see only the clean name in the chat list and header.
name and put ticket details in data so they appear as structured, searchable custom fields.Reading conversations for a visitor
GET /api/v1/conversations accepts visitorId (matches the session and merged knownVisitorIds),externalUserId, email and customData.<key> filters. If you call it from an embedded context with a widget-scoped key, always pass visitorId so a visitor only ever sees their own chats. Details in the conversations reference.