message.comDevelopers

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.

Widget identify
// 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
});
REST upsert
// 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
// 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-4567 and user-4567 are 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:

Lookup
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 identify with a server-side userHash, otherwise anyone can pass any ID and impersonate your users.