Visitors Live
Visitors are the customer records of message.com. One visitor can have many conversations across chat, ticket, and call. Identifying a visitor links anonymous sessions, surfaces CRM data inside the inbox, and feeds AI with the customer's real history.
For the conceptual distinction between visitors, contacts, and users (different vendors use these words differently), see Concept: visitor vs contact vs user.
List / search visitors
Cursor-paginated list. Filters: email, externalId, phone, tag, q (free text). Use this to look up a known customer before opening a conversation.
Code samples
curl 'https://app.message.com/api/v1/[email protected]' \
-H 'Authorization: Bearer YOUR_WORKSPACE_JWT'const res = await fetch('https://app.message.com/api/v1/[email protected]', {
headers: { Authorization: 'Bearer ' + token }
});
const { visitors } = await res.json();import requests
r = requests.get(
"https://app.message.com/api/v1/visitors",
headers={"Authorization": f"Bearer {token}"},
params={"email": "[email protected]"},
)
visitors = r.json()["visitors"]require "net/http"
require "json"
uri = URI("https://app.message.com/api/v1/[email protected]")
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{token}"
http.request(req)
end
visitors = JSON.parse(res.body)["visitors"]<?php
$ctx = stream_context_create([
"http" => ["method" => "GET", "header" => "Authorization: Bearer $token"]
]);
$visitors = json_decode(file_get_contents("https://app.message.com/api/v1/[email protected]", false, $ctx), true)["visitors"];Get one visitor
{
"id": "uuid",
"email": "[email protected]",
"name": "Alice Johnson",
"externalId": "crm-4567",
"phone": "+14155550100",
"attributes": {
"plan": "pro",
"mrr": 99,
"signupAt": "2024-09-04T12:00:00Z"
},
"tags": ["vip", "early-adopter"],
"firstSeenAt": "2024-09-04T12:00:00Z",
"lastSeenAt": "2026-05-13T15:30:00Z",
"conversationCount": 12,
"createdAt": "2024-09-04T12:00:00Z"
}Upsert visitor
Idempotent upsert by email or externalId. If a visitor with the matching key exists, it is updated; otherwise a new visitor is created. This is the right endpoint for syncing customers from a CRM.
| Field | Type | Description |
|---|---|---|
| emailoptional | string | Primary key. Required if no externalId. |
| externalIdoptional | string | Your stable customer ID. Used as a secondary key. |
| nameoptional | string | Display name. |
| phoneoptional | string | E.164 phone number. |
| attributesoptional | object | Arbitrary string / number / boolean fields. Indexed for filtering. |
| tagsoptional | string[] | Tags to apply. Existing tags are not replaced; pass null to clear. |
{
"email": "[email protected]",
"name": "Alice Johnson",
"externalId": "crm-4567",
"attributes": {
"plan": "pro",
"mrr": 99
}
}Update a visitor
Partial update. Same fields as upsert.
Merge visitors
Merges another visitor (by ID in the body) into this one. Conversations, messages, notes, and tags are unioned; externalId and attributes from the primary win on conflict. Useful when one customer interacts anonymously then later identifies, creating two records.
Delete a visitor
Hard-deletes the visitor and all their conversations. Used to honour right-to-be-forgotten requests under GDPR. Admin-only; logged in the audit log.
Deletion is irreversible. Export the conversation history first if you need it for records.
Visitor conversations
Convenience endpoint: cursor-paginated list of this visitor's conversations across all channels. Equivalent to filtering Conversations by visitorId.
Errors
| Code | When |
|---|---|
400 invalid_body | Validation failed. Email or externalId is required. |
404 not_found | Visitor not in this workspace. |
409 conflict | Email collision with another visitor on upsert without externalId. |
Common pitfalls
- Upserting by email when emails are not stable. If customers change emails, lean on
externalIdfrom your CRM instead. Email is the fallback key. - Storing PII in
attributes. Attributes are indexed and visible to all agents in scope. Do not put credit-card numbers, SSNs, or health data there. - Forgetting that the widget identifies client-side by default. Without HMAC, anyone can call
Message.identify({email})from the browser console. See Identifying visitors.