message.comDevelopers

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

GET/api/v1/visitorsAuth: Bearer

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
curl 'https://app.message.com/api/v1/[email protected]' \
  -H 'Authorization: Bearer YOUR_WORKSPACE_JWT'
JavaScript
const res = await fetch('https://app.message.com/api/v1/[email protected]', {
  headers: { Authorization: 'Bearer ' + token }
});
const { visitors } = await res.json();
Python
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"]
Ruby
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
<?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

GET/api/v1/visitors/:idAuth: Bearer
200 OK
{
  "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

POST/api/v1/visitorsAuth: Bearer

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.

FieldTypeDescription
emailoptionalstringPrimary key. Required if no externalId.
externalIdoptionalstringYour stable customer ID. Used as a secondary key.
nameoptionalstringDisplay name.
phoneoptionalstringE.164 phone number.
attributesoptionalobjectArbitrary string / number / boolean fields. Indexed for filtering.
tagsoptionalstring[]Tags to apply. Existing tags are not replaced; pass null to clear.
Body
{
  "email": "[email protected]",
  "name": "Alice Johnson",
  "externalId": "crm-4567",
  "attributes": {
    "plan": "pro",
    "mrr": 99
  }
}

Update a visitor

PATCH/api/v1/visitors/:idAuth: Bearer

Partial update. Same fields as upsert.

Merge visitors

POST/api/v1/visitors/:id/mergeAuth: Bearer

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

DELETE/api/v1/visitors/:idAuth: Bearer

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

GET/api/v1/visitors/:id/conversationsAuth: Bearer

Convenience endpoint: cursor-paginated list of this visitor's conversations across all channels. Equivalent to filtering Conversations by visitorId.

Errors

CodeWhen
400 invalid_bodyValidation failed. Email or externalId is required.
404 not_foundVisitor not in this workspace.
409 conflictEmail collision with another visitor on upsert without externalId.

Common pitfalls

  • Upserting by email when emails are not stable. If customers change emails, lean on externalId from 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.