> ## Documentation Index
> Fetch the complete documentation index at: https://docs.polygon.technology/llms.txt
> Use this file to discover all available pages before exploring further.

# Customer onboarding

> How to register a customer, collect identity verification, and provision their first wallet.

<Note>
  **Before you start:** the OMS API is in early access. Every endpoint, including the ones in this guide, requires an early-access API key. [Request access](https://info.polygon.technology/get-early-access?utm_source=docs\&utm_medium=card\&utm_campaign=oms_access) before you begin.

  Authenticate by exchanging your API key and secret for a bearer token at `POST /auth/token`, then send it as `Authorization: Bearer {accessToken}` on every request. Every mutating request (`POST` and `PATCH`) also requires an `Idempotency-Key` header. See [Get started](/payments/get-started) for the full flow.
</Note>

This guide walks through registering a new end user in OMS, requesting the endorsements that unlock money movement, and provisioning a custodial wallet. Every transaction, deposit address, and virtual account in OMS belongs to a customer record, so this is the starting point for any integration.

## Architecture

<div style={{border:"1px solid #C8CFE1",borderRadius:"12px",overflow:"hidden",marginBottom:"24px"}}>
  <div style={{background:"linear-gradient(180deg,#EAE4F5 0%,#F6F3FB 100%)",borderBottom:"1px solid #D5C4F2",padding:"10px 16px",fontSize:"11px",fontWeight:"700",color:"#670DE5",letterSpacing:"0.06em",textTransform:"uppercase"}}>Customer onboarding flow</div>

  <div style={{borderBottom:"1px solid #EEF0F9",padding:"9px 16px",display:"flex",alignItems:"center",gap:"10px"}}>
    <span style={{color:"#929EBA",fontSize:"11px",fontWeight:"700",minWidth:"16px",textAlign:"right"}}>1</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>App</span>
    <span style={{color:"#670DE5",fontWeight:"700"}}>→</span>
    <span style={{background:"#EAE4F5",color:"#670DE5",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"700",whiteSpace:"nowrap"}}>OMS</span>
    <span style={{fontFamily:"'Geist Mono',ui-monospace,monospace",fontSize:"12px",color:"#141635"}}>POST /customers</span>
  </div>

  <div style={{borderBottom:"1px solid #EEF0F9",padding:"9px 16px",display:"flex",alignItems:"center",gap:"10px"}}>
    <span style={{color:"#929EBA",fontSize:"11px",fontWeight:"700",minWidth:"16px",textAlign:"right"}}>2</span>
    <span style={{background:"#EAE4F5",color:"#670DE5",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"700",whiteSpace:"nowrap"}}>OMS</span>
    <span style={{color:"#48526F",fontWeight:"700"}}>→</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>App</span>
    <span style={{fontFamily:"'Geist Mono',ui-monospace,monospace",fontSize:"12px",color:"#141635"}}>\{id: "cst\_...", endorsements: \[...]}</span>
  </div>

  <div style={{borderBottom:"1px solid #EEF0F9",padding:"9px 16px",display:"flex",alignItems:"center",gap:"10px"}}>
    <span style={{color:"#929EBA",fontSize:"11px",fontWeight:"700",minWidth:"16px",textAlign:"right"}}>3</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>App</span>
    <span style={{color:"#670DE5",fontWeight:"700"}}>→</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>User</span>
    <span style={{fontSize:"13px",color:"#141635"}}>Collect identity documents</span>
  </div>

  <div style={{borderBottom:"1px solid #EEF0F9",padding:"9px 16px",display:"flex",alignItems:"center",gap:"10px"}}>
    <span style={{color:"#929EBA",fontSize:"11px",fontWeight:"700",minWidth:"16px",textAlign:"right"}}>4</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>User</span>
    <span style={{color:"#670DE5",fontWeight:"700"}}>→</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>App</span>
    <span style={{fontSize:"13px",color:"#141635"}}>KYC data</span>
  </div>

  <div style={{borderBottom:"1px solid #EEF0F9",padding:"9px 16px",display:"flex",alignItems:"center",gap:"10px"}}>
    <span style={{color:"#929EBA",fontSize:"11px",fontWeight:"700",minWidth:"16px",textAlign:"right"}}>5</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>App</span>
    <span style={{color:"#670DE5",fontWeight:"700"}}>→</span>
    <span style={{background:"#EAE4F5",color:"#670DE5",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"700",whiteSpace:"nowrap"}}>OMS</span>
    <span style={{fontSize:"13px",color:"#141635"}}>Submit verification (endorsement review)</span>
  </div>

  <div style={{borderBottom:"1px solid #EEF0F9",padding:"9px 16px",display:"flex",alignItems:"center",gap:"10px"}}>
    <span style={{color:"#929EBA",fontSize:"11px",fontWeight:"700",minWidth:"16px",textAlign:"right"}}>6</span>
    <span style={{background:"#EAE4F5",color:"#670DE5",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"700",whiteSpace:"nowrap"}}>OMS</span>
    <span style={{color:"#670DE5",fontWeight:"700"}}>→</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>App</span>
    <span style={{fontSize:"13px",color:"#141635"}}>Endorsements active</span>
  </div>

  <div style={{borderBottom:"1px solid #EEF0F9",padding:"9px 16px",display:"flex",alignItems:"center",gap:"10px"}}>
    <span style={{color:"#929EBA",fontSize:"11px",fontWeight:"700",minWidth:"16px",textAlign:"right"}}>7</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>App</span>
    <span style={{color:"#670DE5",fontWeight:"700"}}>→</span>
    <span style={{background:"#EAE4F5",color:"#670DE5",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"700",whiteSpace:"nowrap"}}>OMS</span>
    <span style={{fontFamily:"'Geist Mono',ui-monospace,monospace",fontSize:"12px",color:"#141635"}}>POST /customers/\{id}/wallets</span>
  </div>

  <div style={{borderBottom:"1px solid #EEF0F9",padding:"9px 16px",display:"flex",alignItems:"center",gap:"10px"}}>
    <span style={{color:"#929EBA",fontSize:"11px",fontWeight:"700",minWidth:"16px",textAlign:"right"}}>8</span>
    <span style={{background:"#EAE4F5",color:"#670DE5",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"700",whiteSpace:"nowrap"}}>OMS</span>
    <span style={{color:"#670DE5",fontWeight:"700"}}>→</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>Polygon</span>
    <span style={{fontSize:"13px",color:"#141635"}}>Derive wallet address</span>
  </div>

  <div style={{borderBottom:"1px solid #EEF0F9",padding:"9px 16px",display:"flex",alignItems:"center",gap:"10px"}}>
    <span style={{color:"#929EBA",fontSize:"11px",fontWeight:"700",minWidth:"16px",textAlign:"right"}}>9</span>
    <span style={{background:"#EAE4F5",color:"#670DE5",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"700",whiteSpace:"nowrap"}}>OMS</span>
    <span style={{color:"#48526F",fontWeight:"700"}}>→</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>App</span>
    <span style={{fontFamily:"'Geist Mono',ui-monospace,monospace",fontSize:"12px",color:"#141635"}}>\{id: "wlt\_...", address: "0x..."}</span>
  </div>

  <div style={{borderBottom:"1px solid #EEF0F9",padding:"9px 16px",display:"flex",alignItems:"center",gap:"10px"}}>
    <span style={{color:"#929EBA",fontSize:"11px",fontWeight:"700",minWidth:"16px",textAlign:"right"}}>10</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>App</span>
    <span style={{color:"#670DE5",fontWeight:"700"}}>→</span>
    <span style={{background:"#EAE4F5",color:"#670DE5",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"700",whiteSpace:"nowrap"}}>OMS</span>
    <span style={{fontFamily:"'Geist Mono',ui-monospace,monospace",fontSize:"12px",color:"#141635"}}>POST /external-accounts</span>
  </div>

  <div style={{padding:"9px 16px",display:"flex",alignItems:"center",gap:"10px"}}>
    <span style={{color:"#929EBA",fontSize:"11px",fontWeight:"700",minWidth:"16px",textAlign:"right"}}>11</span>
    <span style={{background:"#EAE4F5",color:"#670DE5",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"700",whiteSpace:"nowrap"}}>OMS</span>
    <span style={{color:"#48526F",fontWeight:"700"}}>→</span>
    <span style={{background:"#EEF0F9",color:"#48526F",padding:"2px 8px",borderRadius:"4px",fontSize:"11px",fontWeight:"600",whiteSpace:"nowrap"}}>App</span>
    <span style={{fontFamily:"'Geist Mono',ui-monospace,monospace",fontSize:"12px",color:"#141635"}}>\{id: "ext\_bankUs\_...", status: "pending"}</span>
  </div>
</div>

***

## Prerequisites

* An OMS API key.
* Webhook endpoints registered with `POST /webhooks` (or in the OMS Dashboard) if you plan to react to status changes as endorsements advance and transactions settle.

***

## Step 1: Create a customer

Register the end user with a `POST /customers` call. OMS creates the record and returns an ID. To onboard a customer who can move USD or use cash services, send the full set of identifying fields, not just a name, and request the `endorsements` the customer needs.

### Request

```text theme={null}
POST /v0.10/customers
Authorization: Bearer {accessToken}
Idempotency-Key: cst-alice-001
Content-Type: application/json
```

```json theme={null}
{
  "type": "individual",
  "firstName": "Alice",
  "lastName": "Martin",
  "email": "alice@example.com",
  "phone": "+12125551234",
  "birthDate": "1990-05-15",
  "nationality": "US",
  "residentialAddress": {
    "line1": "123 Main St",
    "city": "New York",
    "state": "NY",
    "country": "US",
    "zipCode": "10001"
  },
  "identifyingInformation": [
    { "type": "ssn", "issuingCountry": "US", "number": "123-45-6789" }
  ],
  "externalId": "usr_7890",
  "signedAgreement": true,
  "endorsements": ["basic", "cryptoCustody", "usd"]
}
```

**Required field:** `type` must be `individual`. Only the individual customer type is supported today.

**`endorsements`** declares which capabilities the customer needs: `basic` (identity and geo checks), `cryptoCustody` (custodial wallets that hold stablecoins), and `usd` (USD movement over fiat rails, including bank payouts, virtual accounts, and cash services). Requesting `cryptoCustody` or `usd` auto-includes `basic`. If you omit `endorsements`, OMS defaults to `cryptoCustody` and `usd`. Request endorsements through the Customers API.

**`externalId`** is the recommended way to link the OMS customer record to your internal user ID. Pass it at creation to avoid a separate update call later.

### Response, `201 Created`

```json theme={null}
{
  "id": "cst_01H9Xa...",
  "object": "customer",
  "type": "individual",
  "firstName": "Alice",
  "lastName": "Martin",
  "email": "alice@example.com",
  "externalId": "usr_7890",
  "status": "active",
  "endorsements": [
    { "name": "basic", "status": "ACTIVE" },
    { "name": "cryptoCustody", "status": "ACTIVE" },
    { "name": "usd", "status": "PENDING" }
  ],
  "wallets": [],
  "createdAt": "2026-01-15T10:00:00Z"
}
```

The `status` field is `active` or `inactive`; all the granularity lives in the per-endorsement `status`, which uses SCREAMING\_CASE (`INACTIVE`, `PENDING`, `ISSUES`, `ACTIVE`, `REJECTED`, `REVOKED_ISSUES`, `OFFBOARDED`). An endorsement must reach `ACTIVE` before its capability unlocks. PII fields (`birthDate`, `residentialAddress`, `ipAddress`, `identifyingInformation`) are write-only: OMS accepts them but never returns them. A customer created without the identifying fields cannot be provisioned to move fiat.

***

## Step 2: Collect identity and unlock endorsements

OMS uses an endorsement model to gate access to financial operations. An endorsement tracks the KYC and compliance status behind a capability. Request the endorsements a customer needs at creation, or add them later with `PATCH /customers/{customerId}`.

| Endorsement     | Unlocks                                                                             |
| --------------- | ----------------------------------------------------------------------------------- |
| `basic`         | Identity and geo-based compliance checks. Auto-included with any other endorsement. |
| `cryptoCustody` | Custodial wallets that hold stablecoins.                                            |
| `usd`           | USD movement over fiat rails: US bank payouts, virtual accounts, and cash services. |

Your KYC flow submits identity documents to OMS for review, which advances each requested endorsement toward `ACTIVE`. Provisioning reads the identifying fields above, so include them even in sandbox, where endorsements are auto-approved.

<Warning>
  A customer created with only `type` cannot be provisioned to move fiat. For USD and cash flows, always provide a structured `residentialAddress`, a `phone` in E.164 format, a `birthDate`, and a government ID in `identifyingInformation` (for US customers, an `ssn` or `itin`).
</Warning>

<Note>
  Endorsement names on the wire are `basic`, `cryptoCustody`, and `usd`. An upcoming release adds a finer-grained set of endorsements for specific rails (for example, separate IBAN, Canadian bank, and card endorsements). Those are early access; contact us to enable them.
</Note>

***

## Step 3: Provision a wallet

Once the customer's `cryptoCustody` endorsement is `ACTIVE`, create a custodial wallet with `POST /customers/{customerId}/wallets`. The path carries the customer ID; the body names the single `asset` and `chain` the wallet will hold. OMS derives the onchain address and manages the keys, with no wallet SDK or user signing.

### Request

```text theme={null}
POST /v0.10/customers/cst_01H9Xa.../wallets
Authorization: Bearer {accessToken}
Idempotency-Key: wlt-alice-polygon-001
Content-Type: application/json
```

```json theme={null}
{
  "asset": "usdc",
  "chain": "polygon"
}
```

Each wallet holds a single `asset` (`usdc` or `usdt`) on one `chain` (`polygon`, `ethereum`, `base`, or `solana`). To provision the same customer on another chain or asset, call the endpoint again with a different `asset`/`chain` pair.

### Response, `201 Created`

```json theme={null}
{
  "id": "wlt_01H9Xb...",
  "object": "wallet",
  "customerId": "cst_01H9Xa...",
  "type": "internal",
  "status": "active",
  "asset": "usdc",
  "chain": "polygon",
  "address": "0xBEEF4a2c891D56e72b67a3f21d0cf94F1D7c5911",
  "blockchainAsset": {
    "protocol": "evm",
    "chainId": "137",
    "tokenId": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359"
  },
  "createdAt": "2026-01-15T10:01:00Z"
}
```

The `address` is the onchain address where deposits for this customer arrive, and `blockchainAsset` resolves the on-chain identity (protocol, chain ID, and token contract). `type` is `internal` for OMS-managed wallets; `status` is `active`, `suspended`, or `closed`. Store the `wlt_` ID, you will use it as the source or destination in quotes and transactions. Read the current balance with `GET /wallets/{walletId}/balance`, and list a customer's wallets with `GET /customers/{customerId}/wallets`.

***

## Step 4: Register an external bank account

If the customer will receive fiat payouts, they need a registered external bank account. External accounts are referenced everywhere in OMS by their ID, with a per-type prefix: `ext_bankUs_` for US bank accounts, `ext_bankIban_` for IBAN accounts, `ext_bankCa_` for Canadian accounts, `ext_card_` for debit cards, and `ext_wlt_` for externally-custodied wallets.

Register the account with `POST /external-accounts`. The body carries an `owner`, a `type`, and exactly one per-type object named after the type (here `bankUs`). Supplying a per-type object that does not match `type` is rejected with `422`.

### Request

```text theme={null}
POST /v0.10/external-accounts
Authorization: Bearer {accessToken}
Idempotency-Key: ext-alice-bank-001
Content-Type: application/json
```

```json theme={null}
{
  "owner": { "kind": "customer", "customerId": "cst_01H9Xa..." },
  "type": "bankUs",
  "bankUs": {
    "accountNumber": "123456789012",
    "routingNumber": "021000021",
    "accountType": "checking",
    "bankName": "Chase"
  },
  "label": "Alice checking"
}
```

For `bankUs`, `accountNumber` and `routingNumber` are required; `accountType` (`checking` or `savings`) and `bankName` are optional. IBAN accounts use a `bankIban` object (`iban` and `BIC` required) and Canadian accounts use a `bankCanada` object (`institutionNumber`, `transitNumber`, and `accountNumber` required).

### Response, `201 Created`

```json theme={null}
{
  "id": "ext_bankUs_01H9X...",
  "object": "externalAccount",
  "owner": { "kind": "customer", "customerId": "cst_01H9Xa..." },
  "type": "bankUs",
  "category": "fiatAccount",
  "status": "pending",
  "bankUs": {
    "accountNumberLast4": "9012",
    "routingNumber": "021000021",
    "accountType": "checking",
    "bankName": "Chase"
  },
  "label": "Alice checking",
  "createdAt": "2026-01-15T10:02:00Z",
  "updatedAt": "2026-01-15T10:02:00Z"
}
```

The account starts `pending` and flips to `active` once provisioning completes, or `failed` if the provider rejects it. `accountNumber` is write-only: reads expose only `bankUs.accountNumberLast4`, never the full number. List a customer's accounts with `GET /external-accounts?customerId=...` (the `customerId` query parameter is required).

Once the account is `active`, reference its ID in the `destination` of a quote to pay out to that account:

```json theme={null}
{
  "customerId": "cst_01H9Xa...",
  "source": {
    "type": "walletOms",
    "details": { "id": "wlt_01H9Xb...", "asset": "usdc", "network": "polygon" },
    "amount": "100.00"
  },
  "destination": {
    "type": "bankUs",
    "details": {
      "id": "ext_bankUs_01H9X...",
      "asset": "usd",
      "network": "ach",
      "accountHolder": "customer"
    }
  }
}
```

Bank payouts require the customer's `usd` endorsement to be `ACTIVE`. See [Send from a wallet](/payments/guides/crypto-to-fiat) for the full payout flow.

<Note>
  To pay a third-party recipient rather than the customer, create a counterparty with `POST /counterparties` (`customerId` and `name` required), then register the recipient's bank account with `owner: { "kind": "counterparty", "counterpartyId": "..." }`. Debit cards (`type: "card"`) and external wallets (`type: "walletExternal"`) register through the same `POST /external-accounts` endpoint; there is no separate cards endpoint.
</Note>

***

## What's next

With a customer, wallet, and registered external account in place, you can:

* Fund the wallet with a [cash-in](/api-reference/guide-cash-in) at a retail location.
* Set up a [virtual account](/payments/guides/virtual-accounts) to accept recurring ACH deposits.
* Create a [deposit address](/payments/guides/deposit-addresses) to receive crypto from external wallets.
* Send funds out of the wallet with the [send guide](/payments/guides/crypto-to-fiat): crypto addresses or bank payouts.
