External ID mapping Live
Visitors in message.com have three identifiers: an internal UUID, an email, and your external ID. Always send your external ID. It survives email changes, prevents merge conflicts when two visitors share an email, and gives you a stable join key across your CRM, billing, and support data.
If you only set email, you are betting that your customer's email will never change. It will. Use externalId as the primary, email as the secondary, and let our merge logic handle the rest.
Why external ID matters
Three scenarios where email-only mapping breaks:
- Customer changes email. They get a new job, new email. The old conversations go to a dead address; new conversations create a fresh visitor record. History is lost.
- Two customers share an email. Family Gmail, shared work alias. Their support history merges into a single confused thread.
- Auth provider switches. You migrate from email-password to SSO. Email casing flips, your visitor records double.
Setting externalId to your CRM primary key fixes all three. The visitor record stays stable; email is a mutable attribute.
Setting external ID
Send it on every identify call from the widget, and on every REST upsert from your backend.
// Browser, via the widget
Message.identify({
userId: "crm-4567", // your internal primary key
email: "[email protected]", // may change later
userHash: SIGNED_HMAC // computed server-side
});// Server-side, via REST
curl -X POST 'https://app.message.com/api/v1/visitors' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"externalId": "crm-4567",
"email": "[email protected]",
"name": "Alice Johnson"
}'Merge logic
When you call identify or POST to visitors, we run a deterministic merge.
// Lookup precedence (highest wins)
1. externalId (your ID) → matches exactly one visitor
2. email + userHash → matches authenticated visitor
3. email alone → matches anonymous visitor (last resort)
4. anonymousId (browser cookie) → creates a new visitor
// If externalId is set, email changes are non-destructive.If we find an existing visitor by externalId, the new fields update that record (including email). If we find by email and the new externalId does not match an existing one, we set it. If we find by email and externalId conflicts with another visitor, the call returns 409 external_id_conflict.
Changing an external ID
You can change the externalId on an existing visitor via PATCH. We log this as a workspace audit event. Avoid doing it often; it suggests your primary key is not stable, which will cause other problems.
Multi-system sync
If you push the same visitor from multiple systems (CRM, billing, analytics), pick one as the authoritative source for externalId and let the others reference it. The most common pattern is your auth provider's user ID. Stripe customer IDs work too if Stripe is the source of truth.
Format
- Up to 256 characters.
- Case-sensitive.
USER-4567anduser-4567are different visitors. - Any character allowed. Prefixes are useful when syncing from multiple systems:
stripe_cus_abc,crm-4567,auth0|abc123. - Unique per workspace. Conflicts return 409.
Looking up by external ID
The visitors API supports filtering on externalId:
curl 'https://app.message.com/api/v1/visitors?externalId=crm-4567' \
-H 'Authorization: Bearer YOUR_API_KEY'Common pitfalls
- Casing drift. Stripe IDs are case-sensitive (
cus_abc), but spreadsheet exports often lowercase them. Normalise before sending. - Using email as external ID. Defeats the purpose. The external ID needs to be stable when the email is not.
- Setting external ID on the browser-only flow. Always pair browser
identifywith a server-sideuserHash, otherwise anyone can pass any ID and impersonate your users.