Skip to main content
Niadra recognizes the same customer on WhatsApp, on the phone, in your app and in your CRM. It does so without guessing: every link between two identifiers is a recorded assertion, with a method and evidence, and every link can be undone. This page covers handles, profiles, assertions and the verification levels that decide what each conversation may read.

Handles

A handle is an identifier of a subject in some channel or system. It has a type, a value and, when the type needs one, a scope: Values are classified by their format, never by the name of the field they came from, and normalized on the server. The SDKs have helpers that build each type:
Every handle belongs to a kind of subject, in subject_kind: person (the default), account or partner. org_registry_hmac and email_domain are organization handles from the start. See Accounts and partners.

Profiles and assertions

A profile is the customer as the memory sees them: the set of handles linked to one another. Each link is an assertion in an immutable log, with the method that produced it: When two handles in one event resolve to different profiles, the merge happens outside the batch answer, under a lease. Memory stays attached to the handle each fact came from, so splitting two profiles merged by mistake is a real operation: facts, orders and invoices go back to the right owner on their own.
identify() is sent right away, outside the queue, so the next context() already sees the merged profile.

Safeguards against wrong merges

  • Two profiles that already hold a handle at V3 or above are never merged automatically; only by a forced merge, with actor and reason.
  • Test values and QA placeholders are blocked per type, from a fixed list you can extend.
  • Each profile has per-type limits: one national ID per country, one system_id per namespace, a few phones. Past the limit, the weakest handle is demoted, and ingestion is never rejected.
  • A handle that links many profiles in a short time (a store tablet, a PBX number, a support WhatsApp) becomes ambiguous: usable for reads, never for new merges.
  • A phone number or BSUID rotation creates an alias, never an automatic merge.
  • A person and an organization are never merged. A link is what connects them.
An unknown, partial (a BSUID with no phone yet) or ambiguous handle gets a provisional profile and an empty context. When in doubt, the memory reveals nothing.

Governing identity

Your team corrects identity through the governance routes, with a Console person holding the security or integration role, or a source key with the admin scope. Every write requires an Idempotency-Key header, answers 202 with an operation id and is applied under the profile lease, in order: Niadra also proposes links it cannot prove, such as two profiles with the same name and a similar e-mail. It never applies them on its own: GET /v1/identity/suggestions lists them with a score and the features behind it, accept turns one into an accepted_suggestion assertion, and dismiss closes it. To find a profile from a handle, use POST /v1/profiles/search: the value goes in the body and is never logged. GET /v1/profiles/{profile_id} returns its handles, links, subject kind and pseudonym, masked for the role of whoever reads.

Verification levels

The same customer gets different context depending on how much the conversation proved who they are: Your policy sets the minimum level per category of memory. The context reports in withheld how many items were held back: the agent knows that verifying releases more, without seeing the content. no_customer sits outside the scale and is only accepted from sources with the internal_agent audience; that context never reaches the end customer.

Effective level

The level that applies is the lowest of three: what you requested, the source ceiling and what the conversation proved. The ceiling is an attribute of the source: automated agents V2, sources that run OTP V3, V4 only for human desks and systems of record. A leaked agent key cannot declare V4. The context response shows both sides in verification: requested, effective and, when effective is lower, reason (source_ceiling or not_proven).

Raising the level

The level goes up through a verify event, never by inference. It applies to that conversation or task, with an optional expiry. After it, request a new context(): the SDK has already dropped the cached context of that conversation.
The methods are otp_whatsapp, otp_sms, login, kba, network_attestation and human_agent. A verify above the source ceiling comes back in the 207 with the code verification_not_allowed. For voice, the network attestation of the call (STIR/SHAKEN and equivalents) maps A to V2, and B and C to V1.

Next steps

Context and views

what each level releases in the context.

Accounts and partners

organizations and role-based links.

List assertions

the API of the identity log.

Identity suggestions

links to review, never applied on their own.