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

# Confirm Wallet Link with OTP (Consent Step 2)

> Completes linking by submitting the one-time code the customer received. On success your business becomes an operator of the wallet — you can then send and withdraw on the customer's behalf. All existing members are notified via the `user.linked` webhook.



## OpenAPI

````yaml /api-reference/caas-openapi.json post /v1/users/link/confirm
openapi: 3.0.0
info:
  description: >-
    Rach CaaS is the stablecoin infrastructure layer for African fintechs and
    financial institutions. It enables any business to offer their customers
    non-custodial USDC and USDT wallets, instant on-chain transfers, and fiat ↔
    stablecoin conversion in any supported currency (XOF, XAF, NGN, GHS, ...) —
    without building blockchain infrastructure.


    **Stablecoins:** USDC and USDT are supported equally everywhere. On any
    endpoint that moves value — `POST /v1/users/fund`, `POST
    /v1/transfers/send`, `POST /v1/users/withdraw` — set the token
    (`target_token` / `token`, or the ticker inside a
    `DIRECT_CRYPTO:{USDC|USDT}:{amount}` quote). `GET /v1/users/balance` returns
    both `balance_usdc` and `balance_usdt`.


    **System-wide phone addressing:** wallets are keyed by phone number and
    belong to the system, not to any one business. Money moves freely across
    businesses — and to phone numbers that have never been provisioned: Rach
    creates the wallet on the fly and notifies the recipient by SMS with USSD
    access instructions.


    **Network:** Polygon PoS (Chain ID 137). All smart contract wallets,
    transfers, and withdrawals settle on Polygon mainnet. Test environments use
    Sepolia (Chain ID 11155111).


    ## Authentication

    **B2B API endpoints** (`/v1/users/*`, `/v1/transfers/*`, `/v1/fx/*`) use API
    Key authentication. Pass your key in the `X-API-Key` header. Keys are
    prefixed `rach_sk_live_` (mainnet) or `rach_sk_test_` (sandbox — no on-chain
    execution).


    **Dashboard endpoints** (`/v1/dashboard/*`) use JWT Bearer authentication.
    Your JWT must include `business_id` and the `waas` permission scope.


    ## Sandbox Mode

    All B2B endpoints honour test-mode API keys (`rach_sk_test_*`). In sandbox
    mode every operation returns an immediate success response
    (`SANDBOX_SIMULATED`) without touching the blockchain or debiting your
    treasury balance. Use sandbox keys for integration testing and CI.


    ## Typical Integration Flow

    1. `POST /v1/users/provision` — create a non-custodial SCW for each end-user
    (offline, no gas)

    2. Collect fiat from customer → deposit to Rach bank account → `POST
    /v1/dashboard/treasury/topup`

    3. Rach confirms XOF receipt → `POST
    /v1/dashboard/treasury/topup/{id}/confirm` (credits your USDC balance)

    4. `POST /v1/users/fund` — transfer USDC from your treasury balance to the
    customer SCW on-chain

    5. `GET /v1/users/balance` — read live on-chain USDC balance via Polygon RPC

    6. `POST /v1/transfers/send` — instant peer-to-peer USDC transfer between
    customer SCWs (ERC-4337 UserOp)

    7. `POST /v1/users/withdraw` — off-ramp: sweep USDC from SCW back to Rach
    treasury; Rach pays out XOF via mobile money


    ## System-Wide Wallets & Multi-Membership

    A wallet is keyed by phone number and belongs to the **system**, not to any
    one business. Several businesses can operate the same wallet — but only with
    the customer's consent.


    **Provisioning outcomes** (`POST /v1/users/provision`):

    - `PROVISIONED` — new wallet created; you are its first member.

    - `ADOPTED` — the phone had an unaffiliated wallet (created on the fly by an
    inbound transfer); you are now its primary member.

    - `ALREADY_EXISTS` — the wallet exists and you already operate it.

    - `LINK_REQUIRED` — the wallet belongs to another business. You get the same
    address back but **no operating rights**. Start the consent flow to gain
    them.


    **Consent-based linking** (to operate a wallet another business created):

    1. `POST /v1/users/link/request` — a one-time code is SMSed to the customer.

    2. `POST /v1/users/link/confirm` — submit the code the customer gives you;
    you become a member. Existing members receive a `user.linked` webhook.


    **Money-in is open, money-out is gated.** Any business can fund a wallet or
    send money to it (paid from the sender's own ledger). Only a **member** may
    originate transfers, withdraw, or change the phone number for a wallet.


    **Consent-gated phone change** (`POST /v1/users/phone-change/request` →
    `/confirm`): a one-time code is SMSed to the NEW number (proving SIM
    control). The SCW address, balance and history are preserved; every member
    receives a `user.phone_changed` webhook.


    ## Webhook Events

    Signed with HMAC-SHA256 over the body in the `X-Rach-Signature: sha256=...`
    header (key = your webhook secret).

    - `transfer.received` — a wallet you operate received an inbound transfer.

    - `user.linked` — a new business was granted operating rights on a wallet
    you operate.

    - `user.phone_changed` — the phone number of a wallet you operate was
    re-keyed (address unchanged).


    ## Multi-Currency

    Rach moves any fiat; USDC is the settlement rail. Rates are per-currency
    (XOF, XAF, NGN, GHS, ...). See `GET /v1/dashboard/rates`. Topup quotes and
    withdrawals carry a `currency_code` / `payout_currency`.


    ## Status Lifecycles

    **Deposit:** `QUEUED` → `SETTLED`

    **Transfer:** `QUEUED` → `SUBMITTED` → `SETTLED`

    **Withdrawal:** `PENDING` → `SUBMITTED` → `CRYPTO_RECEIVED` → `COMPLETED`
  title: Rach CaaS — Crypto-as-a-Service API
  contact:
    name: Rach Finance Support
    url: https://rach.finance
  version: 1.0.1
servers:
  - url: https://api.rach.finance/caas/api
security: []
paths:
  /v1/users/link/confirm:
    post:
      tags:
        - B2B - Users
      summary: Confirm Wallet Link with OTP (Consent Step 2)
      description: >-
        Completes linking by submitting the one-time code the customer received.
        On success your business becomes an operator of the wallet — you can
        then send and withdraw on the customer's behalf. All existing members
        are notified via the `user.linked` webhook.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/cmd_api.LinkConfirmBody'
        description: Phone number and OTP
        required: true
      responses:
        '200':
          description: Linked — you now operate this wallet
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/cmd_api.ProvisionUserResponse'
        '400':
          description: Invalid or expired OTP
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '404':
          description: No pending link request
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
      security:
        - ApiKeyAuth: []
components:
  schemas:
    cmd_api.LinkConfirmBody:
      type: object
      required:
        - otp
        - phone_number
      properties:
        otp:
          type: string
          example: '123456'
        phone_number:
          type: string
          example: '+2250700000001'
    cmd_api.ProvisionUserResponse:
      type: object
      properties:
        blind_index:
          type: string
        created_at:
          type: string
        status:
          description: >-
            Status is one of: PROVISIONED (new wallet), ADOPTED (took over an

            unaffiliated wallet), ALREADY_EXISTS (you are already a member), or

            LINK_REQUIRED (wallet belongs to another merchant — start the
            consent

            link flow at POST /v1/users/link/request to gain operating rights).
          type: string
          example: PROVISIONED
        wallet_address:
          type: string
  securitySchemes:
    ApiKeyAuth:
      description: >-
        Your Rach B2B API key. Use `rach_sk_live_*` for production (on-chain) or
        `rach_sk_test_*` for sandbox (no-chain simulation).
      type: apiKey
      name: X-API-Key
      in: header

````