# Choosing an identityReference

Every Banxa Native transaction is tied to an `identityReference` — your stable identifier for the user. Banxa accumulates KYC state, verification history, and compliance tier against it, so a user who verifies once doesn't need to verify again.

Banxa's underlying record key is **email**. When Banxa receives an email on any `identityReference` — via identity creation, OTP verification, or a PATCH — it links the two. If that email already exists on another identity, the records merge automatically: the newer identityReference inherits the older account's KYC history. Getting the user's email into Banxa early is what determines whether a returning user is recognised correctly.

## Requirements

An `identityReference` must be:

- **Unique per user.** Two users must never share an `identityReference`.
- **Opaque.** Do not include PII (name, email, phone number) in the value itself.


The format is up to you — use whatever you can derive consistently and uniquely for the same person.

## When to create the identity

Create the identity as early as possible — ideally before pricing. Pricing doesn't strictly require it, but Banxa distinguishes between new and returning customers, so pricing is most accurate when Banxa already knows the user. Eligibility requires knowledge of the user, so either create the identity before calling eligibility, or call eligibility with the user's email and country directly.

## Which approach applies to you

The key question is whether your system already stores a unique identifier per user.

|  | Approach 1 | Approach 2 |
|  --- | --- | --- |
| **When to use** | You store a unique ID per user | You have no stored per-user ID |
| **Typical integration** | Exchanges, custodial wallets | Non-custodial wallets |
| **Works across multiple wallets** | Yes | Yes, with OTP |
| **KYC history persists if wallet changes** | Yes | Yes, with OTP |
| **Merging required** | No | Yes, via email + OTP |


## Approach 1 — You have a stored per-user ID

If your system already has a stable identifier for each user — a database ID, a UUID, a hashed account reference — use that as your `identityReference`. The choice of what to store is yours; a common pattern is to hash the internal ID to keep the value opaque.

This is the simplest approach: your identifier is already stable and independent of wallet addresses or email changes. KYC history accumulates against one record across all the user's devices and wallets.

## Approach 2 — You have no stored per-user ID

If your app has no concept of a persistent user — common for non-custodial wallets, apps using device fingerprints, or session-based flows — you need to derive an `identityReference` at transaction time. A common pattern is to hash the wallet address, but any value that is unique per person works.

**Email is required before a transaction at all times.** Without it, Banxa cannot recognise a returning user who comes back with a different `identityReference` — eligibility returns results as if they are a new user, causing transaction failures or unnecessary KYC friction.

### Linking email to the identity

Email can be provided to Banxa at any of these points — choose what fits your flow:

- **At identity creation** — include `email` in `POST /eapi/v0/identities/basic`
- **Via OTP verification** — create a sparse identity first, then bind the email via `POST /eapi/v1/verifications/otp` (recommended — see below)
- **At Sumsub token sharing** — include `email` in `POST /eapi/v0/identities/share/token`; this also lets you discover upfront whether KYC token sharing or just a basic identity is needed


### Handling new and returning users

There is no single required flow — the right approach depends on your app's architecture. The variants below cover every meaningful combination. Pick the one that fits.

#### New users

**Create identity with email, then eligibility**

The most common pattern for apps with their own authentication. Include email at creation, then proceed to eligibility. If a Banxa record already exists for that email, creation returns `422` with code `81` — retrieve the existing record via GET and continue as a returning user.


```mermaid
flowchart TD
    A([User initiates transaction]) --> B["POST /identities/basic\nidentityReference + email"]
    B --> C{Response}
    C -->|201 — new record| D[POST /eligibility\nwith identityReference]
    C -->|"422 code 81 — email exists"| E["GET /identities/any-ref?email=email\nReturns real identityReference"]
    E --> D
    D --> F([Proceed to payment])
```

**Eligibility first, then create identity**

Call eligibility with email and country before creating the identity. The response tells you upfront whether KYC token sharing (SELFIE/DOCUMENT requirements) or just basic structured fields are needed — avoiding an extra round-trip after creation.


```mermaid
flowchart TD
    A([User initiates transaction]) --> B["POST /eligibility\nemail + countryOfResidence only"]
    B --> C[Note requirements returned]
    C --> D["POST /identities/basic\nidentityReference + email"]
    D --> E[Handle requirements from step 1\ntoken sharing and/or PATCH]
    E --> F["POST /eligibility\nwith identityReference"]
    F --> G([Proceed to payment])
```

Eligibility called with email + country returns `paymentReady` and `requirements` only — no `identityReference` is returned. The `identityReference` is yours to define.

**Sparse identity + OTP**

Required if your app has no authentication mechanism of its own. Create the identity without an email first, then bind the email via OTP. OTP verifies the user owns the address and triggers merging if that email already exists on another Banxa identity.


```mermaid
flowchart TD
    A([User initiates transaction]) --> B["POST /identities/basic\nidentityReference only"]
    B --> C[Collect email from user in your UX]
    C --> D["POST /verifications/otp\nidentityReference + email → OTP sent"]
    D --> E[User enters OTP code]
    E --> F["POST /verifications/otp/verify\nEmail bound — merge triggered if email already on another record"]
    F --> G[POST /eligibility]
    G --> H([Proceed to payment])
```

See [OTP Email Verification](/products/native-api/docs/how-it-works/otp-verification) for the endpoint reference.

**Sumsub token sharing at onboarding**

If your platform runs Sumsub KYC when users sign up, share the verification token with Banxa at that point — not at transaction time. The user arrives at their first transaction already verified.


```mermaid
flowchart TD
    A([User completes KYC\nat onboarding]) --> B["POST /identities/share/token\nidentityReference + email + Sumsub token"]
    B --> C["Poll GET /identities/{ref}\nor receive KYC webhook\nuntil kyc.status = VERIFIED"]
    C --> D(["Transaction time:\nPOST /eligibility → likely paymentReady: true"])
```

See [KYC Token Sharing](/products/native-api/docs/how-it-works/kyc-token-sharing) for the full flow.

#### Returning users

**identityReference stored**

If you saved the `identityReference` from a previous session, call eligibility directly with the stored value. Transaction context may have changed since the last visit — re-check eligibility each time.


```http
POST /eapi/v0/identities/transactions/eligibility
{ "identityReference": "stored-ref", ... }
```

**identityReference not stored — GET lookup by email**

The user returns on a different device or with a different wallet address and you have no stored reference. Pass any value in the path and include the user's email as a query parameter — Banxa resolves to the `identityReference` linked to that email.


```http
GET /eapi/v0/identities/any-ref?email=user@example.com
```


```json
{
  "identityReference": "abc123",
  "account": {
    "exists": true,
    "blocked": false,
    "createdAt": "2026-05-25T00:34:03Z"
  },
  "kyc": {
    "status": "VERIFIED"
  }
}
```

Use the `identityReference` returned here for all subsequent calls. Email is an optional query parameter — if omitted, Banxa uses the path value as-is.

**Can't distinguish first-time vs returning users upfront**

If you can't tell whether a user is new or returning before calling the API, attempt creation and branch on the response.


```mermaid
flowchart TD
    A([User initiates transaction]) --> B["POST /identities/basic\nnew identityReference + email"]
    B --> C{Response}
    C -->|201 — genuinely new| D[Proceed as new user\nPOST /eligibility]
    C -->|"422 code 81 — record exists"| E["GET /identities/any-ref?email=email\nRetrieve real identityReference"]
    E --> D
    D --> F([Proceed to payment])
```

**No stored reference and no email available**

Design to avoid this state
Always collect email before checking eligibility. Without it, Banxa treats the user as new, which causes two problems:

- **Unnecessary re-KYC** — the user faces requirements they have already satisfied on a previous account.
- **Wrong eligibility tier** — prior transaction history qualifies users for more favourable compliance tiers. Without the email, Banxa has no way to know. Once email is eventually linked, the requirements change — creating friction mid-flow.


### Providing email and record merging

Merging occurs whenever Banxa receives an email — at identity creation, via OTP, via Sumsub token sharing, or via a PATCH. The `identityReference` the email arrives on inherits all prior KYC history, and this chains: a user can return with a different `identityReference` multiple times without losing verification history.

**OTP and authentication:**

| Your app has its own authentication | OTP |
|  --- | --- |
| Yes | Optional — your login acts as the email verification step |
| No | Required — the only way to verify the user owns the address |


OTP is configured before launch — contact your Banxa integration manager to confirm the setup. See [OTP Email Verification](/products/native-api/docs/how-it-works/otp-verification) for the endpoint reference and flow.