Issue a contact's browser visitor id

Search Documentation

Search across all developer documentation

contacts

Issue a contact's browser visitor id

POST /v1/contacts/identify
Auth required api.contacts.manage

Returns the contact's visitor id — a random, opaque identifier you set as a first-party cookie so that SendOps can recognise the person when they visit your website.

Call this from your own server, at login and at signup. Never from a browser: the response is issued to an API key, and the cookie has to be set by a Set-Cookie header from your own domain.

Find-or-create and safe to repeat. A contact has exactly one active visitor id; the first call mints it and returns result: "created", every later call returns the same id with result: "existing". Re-calling on every login is the intended usage — it is what keeps the cookie's 400-day window rolling.

Setting the cookie

Set-Cookie: so_vid=vis_…; Max-Age=34560000; Domain=.example.com; Path=/; Secure; SameSite=Lax

Four things about that line are load-bearing:

  • Not HttpOnly. The SendOps snippet reads the cookie with JavaScript. Marking it HttpOnly makes the whole feature silently do nothing.
  • Domain is your registrable domain (.example.com), not the host your app runs on. That is what lets the snippet on www.example.com, docs.example.com and blog.example.com see it. Scoping it to app.example.com means only your app can read it, which is the one place you do not need it.
  • Re-set it on every login. Max-Age restarts from each Set-Cookie, so a returning customer's window never expires.
  • Never write it from JavaScript. Safari's Intelligent Tracking Prevention caps a script-written cookie at 7 days, which defeats the point. Set it from your server, and let the snippet only read it.

Clear the cookie on logout (Max-Age=0, same Domain and Path), so a shared browser does not attribute the next person's visits to the one who left.

Identity resolution

Supply email, external_id, or both. The rules match POST /v1/activities:

  • An email we have not seen creates a contact for it.
  • An external_id we have not seen, with no email alongside it, is refused with 422 unknown_external_id — an external id is your own key, and one we have never seen is more likely a typo than a person.
  • An email whose contact already carries a different external_id is refused with 422 external_id_conflict.

Archiving a contact revokes its visitor id: any cookie still in a browser stops resolving, and the beacon carrying it is dropped without being recorded.

Supports the Idempotency-Key header: a duplicate key within 24 h replays the original response.

Request body

Content type: application/json

email string<email> optional

The contact's email address. An address we have not seen creates a contact for it, exactly as POST /v1/activities does.

external_id string optional

Your own stable identifier for the person. On its own, one we have never seen returns 422 unknown_external_id rather than creating a contact — pair it with email if you want it created.

Responses

Errors follow the RFC 7807 problem format — see the error reference.

200 The contact's active visitor id application/json
contact_id string<uuid> required

The contact the visitor id belongs to.

visitor_id string required

The value to set as the so_vid cookie. Opaque, random, and derived from nothing about the person — treat it as a credential and do not log it.

Constraints: pattern ^vis_[0-9a-hjkmnp-tv-z]{26}$

result string enum required

created on the call that minted the id, existing on every later call for the same contact. The id itself is identical either way.

One of: created, existing

400 The body named no identity at all — neither email nor external_id. The code is validation_failed. Declared here rather than as a shared response because it is the only 400 on this API: everywhere else a validation fault is 422, and this one is the narrower "there is nothing here to process" case. application/problem+json
401 Missing, malformed, or unknown API key application/problem+json
403 Key lacks the required scope or plan limit violated application/problem+json
404 Resource not found application/problem+json
422 The identity could not be resolved. Nothing was written. - unknown_external_id — no contact carries that external_id and no email was supplied. Supply an email alongside it, or create the contact first. - external_id_conflict — that email belongs to a contact under a different external_id. Permanent until reconciled; retrying changes nothing. - validation_failed — the body was not a JSON object. application/problem+json
429 Per-org rate limit exceeded application/problem+json
500 Unexpected server-side failure. The code is internal_error. The request_id field can be quoted to SendOps support to investigate. application/problem+json