Users & identity

Tie feedback to real people by userId and email: why it matters, how it works, and the API endpoints.

Why identify

#

Anonymous feedback tells you what is wrong. Identified feedback tells you who it's hurting and how much it's worth. Once feedback is tied to a user, Sentriment can:

  • segment themes by plan or company (“what do enterprise accounts complain about?”)
  • score each user's health and flag churn risk early
  • show the customer value behind every theme, in your currency
  • honour GDPR access and erasure requests for that person (the endpoints below)

userId, email, or both

#

You can identify a user by userId (your own stable ID), by email, or both. Sentriment resolves them in that priority order and treats them as the same person:

Attach identity + traits
curl -X POST https://app.sentriment.com/api/v1/users/identify \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "usr_123",
    "traits": {
      "email": "dana@acme.com",
      "plan": "pro",
      "mrr": 499,
      "company": "Acme"
    }
  }'

You can also pass userId and email directly on any POST /feedback call, handy for survey tools or forms where you don't call identify separately.

Email-first if you'll sync support tools

#

The single most valuable setup decision

If you plan to connect Intercom or Zendesk, or import support history by CSV, always send an email with your users. Email is the join key that stitches a person's widget feedback, API feedback, and support tickets into one profile and one health score.

Here's why it matters. Without email, the same human fragments across sources:

Without email

  • · usr_123 (your app)
  • · zd_991 (a Zendesk ticket)
  • · ic_abc (an Intercom chat)

Three “users”, three health scores, MRR counted three times or not at all.

With email

  • · one user: usr_123
  • · also known as dana@acme.com, zd_991, ic_abc

One profile, one health trajectory, MRR counted once.

It works in any order. If a Zendesk ticket from dana@acme.com arrives before you've identified her, Sentriment creates a shadow user keyed by that email, already scored and alertable. The moment you call identify with the same email, the shadow folds into her real profile, carrying its history along.

Traits

#

Traits are arbitrary key/values describing the user: plan, company, mrr, signup_date. They shallow-merge, so you can enrich a user over time, and they power segment filters (max 32 keys / 8 KB). Send authoritative traits from your server; treat traits sent from a browser (widget keys) as untrusted.

Traits vs metadata

Traits describe the person and persist across their feedback. Metadata describes a single item (which page, which experiment). Rule of thumb: “plan” is a trait; “page: /checkout” is metadata. More in Best practices.

Customer value

#

The trait you name in Customer value (for example mrr) is money, and only your server can set it:

  • Send it with a secret key, in major units, for the configured period: "mrr": 99 is €99 a month when the setting is EUR per month. A JSON number or a plain decimal string such as "99.50" is accepted.
  • Anything else on the configured trait (a negative, "1,200", "€99", true) is refused with a 422 naming the trait. Nothing from the call is written.
  • Send 0 when a customer stops paying, and null to remove the value entirely. Only a secret key can do either.
  • A public key (the widget, or identify from a browser) can never create, change or remove it. Those traits are dropped and the response lists them as warnings.

Every numeric trait your server sends is recorded this way, even before customer value is configured, so the value is ready the day you turn it on.

Shadow users & merging

#

A shadow user is someone Sentriment knows only by email or a support handle, with no userId yet. They show up in Users with a badge, are fully scored, and can trigger at-risk alerts. When you later identify them, an exact email match merges automatically; anything ambiguous (a shared support@ mailbox, two accounts claiming one email) becomes a “possibly the same person” suggestion you confirm with one click.

You control this under Settings → Identity: link by email automatically, suggest-only, or off, plus an ignore list for staff domains and shared inboxes.

GDPR erasure works by any identifier

An erasure request keyed on a user's email erases the whole merged profile (feedback, traits, health history, and every alias) even if their canonical ID is a userId. Both endpoints below accept a userId or a known email.

Endpoints

POST/api/v1/users/identifysecret or widget key

Identify: attach traits

Mixpanel-style. Traits (plan, company, …) shallow-merge and power segment filtering and per-segment cluster breakdowns in the dashboard. The trait you name as your customer value is the exception: it shows as money, never as a segment. Values are strings, numbers or booleans; a secret key can also send null to remove a trait. Max 32 keys / 8 KB. An email trait doubles as an identity: feedback arriving from Zendesk, Intercom, CSV imports, or the API with that email lands on this user's profile, and any matching shadow user merges in automatically.

Secret key
curl -X POST https://app.sentriment.com/api/v1/users/identify \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "userId": "usr_123", "traits": { "plan": "pro", "mrr": 99 } }'

# → 200
{ "userId": "usr_123", "traits": { "plan": "pro", "mrr": 99 } }

With a secret key, the response echoes the user's merged traits. A trait sent as null is removed, along with any value recorded for it. An invalid amount on the configured customer-value trait rejects the whole call:

Secret key, invalid amount
# "traits": { "mrr": "€99" }
# → 422 application/problem+json
{
  "type": "https://sentriment.com/problems/invalid-customer-value",
  "title": "Customer value is not a valid amount",
  "status": 422,
  "detail": "The \"mrr\" trait holds this project's customer value, so it must be a number from 0 to 999,999,999,999 in major units (whole currency units, not cents), or a string of digits such as \"290\" or \"99.50\". Send 0 when a customer stops paying, or null to remove the value. Nothing was written.",
  "key": "mrr"
}

With a public key, the response never echoes traits, so a browser cannot read a customer's value back. Traits a public key is not allowed to write are dropped, the rest are saved, and each dropped key comes back as a warning. The widget prints warnings to the browser console. A trait your server has already sent as a number also keeps the server's value, without a warning: a warning per user would let anyone holding your public key learn which users your server has sent values for.

Public key
# "traits": { "plan": "pro", "mrr": 99 }
# → 200
{
  "userId": "usr_123",
  "warnings": [
    { "code": "value_requires_secret_key", "key": "mrr" }
  ]
}
ParameterInDescription
value_requires_secret_keypublic keyThe key is the configured customer-value trait. Send it from your server with a secret key.
unset_requires_secret_keypublic keyThe trait was sent as null. Only a secret key can remove a trait.
GET/api/v1/users/{userId}secret key

Get a user: right of access (Art. 15)

Everything Sentriment holds about one of your users: traits, the recorded values behind them, first/last seen, and their feedback items (PII-redacted, up to 200). Accepts a userId or a known email alias; forward it directly in response to a data-access request.

Request
curl "https://app.sentriment.com/api/v1/users/usr_123" \
  -H "Authorization: Bearer sk_live_…"

# → 200
{
  "userId": "usr_123",
  "traits": { "plan": "free", "mrr": 0 },
  "valueMarks": {
    "mrr": {
      "current": 0,
      "lastPositive": 29,
      "lastPositiveAt": "2026-08-02T09:14:00.000Z",
      "payingSince": null,
      "lostAt": "2026-09-12T16:40:21.512Z"
    }
  },
  "firstSeenAt": "2026-03-04T11:02:00.000Z",
  "lastSeenAt": "2026-09-12T16:40:21.512Z",
  "feedbackCount": 7,
  "feedback": [ … ]
}

valueMarks holds one entry per numeric trait your server sent: the current amount, the last positive amount and when it was recorded, when the latest paying stretch began, and when the user stopped paying (null while they pay). payingSince is set only when the user was seen at 0 before paying; null means they were already paying when first seen. A lastPositiveAt of null means the value predates this tracking. Erasure deletes the marks with the profile.

DELETE/api/v1/users/{userId}secret key

Delete a user: right to erasure (Art. 17)

Queues a hard delete of the user's entire footprint: every feedback item and the identity profile with its traits and aliases. Accepts a userId or a known email. Cluster counts recompute. Returns 202 with a request id; completes within minutes.

bash
curl -X DELETE https://app.sentriment.com/api/v1/users/usr_123 \
  -H "Authorization: Bearer sk_live_…"

# → { "requestId": "01KX…", "status": "pending" }
Was this page helpful?