Contacts
Signals sessions are anonymous by design. A contact is the bridge: when a visitor gives you an email or logs into an account you already know, you identify them, and from then on their sessions link to one record you can look up, export, or delete.
Personal data lives on the contact only. A session carries nothing but a contact_id.
Creating a contact
Section titled “Creating a contact”A contact is created the first time you identify a person on a property. Four paths lead there:
Links the current browser session. See Identify.
POST /v2/property/contacts/identifyAuthorization: Bearer <token with contacts:write>
Works with or without a session id. See Contacts API.
The Create a contact action identifies the person who submitted a form, using the email field you choose. See Create a contact.
Paid orders identify the customer automatically once the Shopify integration is linked.
From a Laravel backend the PHP SDK can identify on login with one middleware.
Matching rules
Section titled “Matching rules”Identify is idempotent. Each call resolves to exactly one contact:
- If the payload has an
external_id, match on it. A new email on a knownexternal_idupdates the email. - Otherwise match on
email, most recently seen first. Anexternal_idsupplied later is backfilled onto the matched contact. - Otherwise create a new contact.
Traits in the payload overwrite the stored value when present and are left alone when absent. meta entries merge by key. If the call came with a session and the contact has no country or city yet, they default from the session’s geo.
At least one of external_id or email is required.
Linking sessions: set once
Section titled “Linking sessions: set once”A session is linked to the first contact that identifies it, and never re-linked. A second identify on the same session with a different person is recorded against that person’s contact, but the session keeps its original contact_id. This keeps a shared device from rewriting history, and it means a session’s contact is something you can rely on.
Each successful link increments the contact’s session_count and writes an identify event onto the session, so identification is visible in the session timeline and queryable.
What a contact stores
Section titled “What a contact stores”| Field | Notes |
|---|---|
external_id | Your own id for the person (user id, CRM id). Preferred match key. |
email | |
first_name, last_name | |
phone, company, job_title, website | |
country, city | Defaults from the session’s geo on first identify |
lifecycle_stage | Free text. SnipForm offers lead, subscriber, customer, user anywhere it gives you a picker; the API accepts whatever your CRM uses. New contacts default to user. |
state | active, unsubscribed, or deleted (see below) |
meta | A list of {key, value} pairs for anything else |
first_seen_ts | Entry time of the first linked session, or the identify time |
identified_ts | When the contact was created |
last_seen_ts | Last activity of any linked session |
session_count | Linked sessions |
Timestamps are unix seconds.
States
Section titled “States”| State | Set by | Meaning |
|---|---|---|
active | default | Normal |
unsubscribed | you, via the dashboard or API | A flag for your own outbound tools. SnipForm does not send anything to contacts, so it changes nothing here. |
deleted | the delete action only | The contact has been redacted. It cannot be set directly. |
Deleting a contact
Section titled “Deleting a contact”Deleting a contact is erasure by redaction. Sessions and events keep the contact’s id as their relational spine, so removing the row would break them. Instead, every identifying value is destroyed in place:
external_id, names, phone, company, job title, website, country, city andmetaare clearedemailbecomesdeleted_<random>@redacted.invalidstatebecomesdeleted- Identify events on the linked sessions are scrubbed of the email and external id
What remains says nothing about anyone. A deleted contact returns 410 Gone on update over the API, and its sessions stay in your analytics as anonymous sessions, which is what they were before identification.
Global Privacy Control
Section titled “Global Privacy Control”A browser that sends Sec-GPC: 1 is never identified by the tracker. signals.identify() returns quietly and no contact is created or linked. Server-side identification through the API is under your control and is not affected; honour the signal yourself if you have it.
Browsing contacts
Section titled “Browsing contacts”The Contacts page on a property lists contacts with search, state and lifecycle filters. Each contact page shows the traits, the linked sessions newest first, and the edit and delete actions. The same data is available at /v2/property/contacts.
Contacts also power the A visitor is identified and A contact is created or updated automation triggers, which is the hook for pushing identified people into a CRM.