> ## 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.

# Derive a blockchain address

> Derives a blockchain address for the specified network at the given BIP-44 index.
Set `enable_monitoring: true` to have the payment monitor watch this address
for incoming deposits and push webhooks.

Requires an **active** subscription. Returns `402` if the plan has expired.
Deriving additional addresses for an existing customer wallet does **not** count
against the wallet cap — only creating a new customer wallet does.

**Deposit lifecycle (when `enable_monitoring: true`):**

| Event | When | Notes |
|-------|------|-------|
| `wallet.deposit.detected` | deposit first seen on-chain | may carry `hash_pending: true` |
| `wallet.deposit.hash_resolved` | real on-chain tx hash resolved | **same `deposit_id`**, `tx_hash` now real |
| `wallet.deposit.confirmed` | block depth / settlement delay met | safe to credit |

**Idempotency — dedup on `deposit_id`, NOT `tx_hash`.** Every deposit webhook carries a stable
`deposit_id`; record it and ignore repeats. `tx_hash` may initially be a placeholder (`pending_…`)
when the real on-chain hash isn't indexable yet — then `hash_pending` is `true`, and a follow-up
`wallet.deposit.hash_resolved` (same `deposit_id`) arrives later with the real `tx_hash`. Update
the hash on your existing record; **never credit a second time.**

**Addresses** are always EIP-55 checksummed for EVM chains (ETH/BSC/POL), matching the casing
returned by `GET /balances` and `list addresses`. **The native BSC coin is `BNB`** (never `BSC`).

The `confirmations` field behaviour differs by network:

| Network | Confirmation method | `confirmations` value |
|---------|--------------------|-----------------------|
| ETH, BSC, POL | Block depth via `eth_getTransactionReceipt` | Real block count (e.g. `12`) |
| BTC | Block depth via Esplora API | Real block count (e.g. `6`) |
| TRX, SOL, LTC, BCH, XRP | Time-based settlement delay | Always `0` even when confirmed |

**TRX/SOL/LTC/BCH/XRP always emit `"confirmations": 0`** in the webhook payload — even when
`status` is `"confirmed"`. This is expected: these networks use a settlement delay instead of
block depth. Do not treat `confirmations: 0` as an error on these networks.
Use `status === "confirmed"` as the authoritative signal, not the `confirmations` count.

**Do not credit customer funds until you receive `wallet.deposit.confirmed`.**

**Webhook payload shape:**
```json
{
  "event": "wallet.deposit.detected",
  "data": {
    "deposit_id": 4821,             // STABLE idempotency key — dedup on THIS, not tx_hash
    "customer_id": "cus_abc123",
    "network": "TRX",
    "address": "TV8f...",           // EVM addresses are EIP-55 checksummed
    "amount": "8.152212",
    "currency": "USDT",             // native BSC coin is "BNB"
    "tx_hash": "pending_a1b2c3...", // may be a placeholder until resolved
    "hash_pending": true,           // true ⇒ the real hash arrives via hash_resolved
    "confirmations": 0,
    "status": "detected",
    "detected_at": "2026-07-21T04:41:30Z",
    "detected_by": "custom",
    "safe_to_credit": false
  }
}
```

Follow-up once the real on-chain hash is known (**same `deposit_id`** — update, don't re-credit):
```json
{
  "event": "wallet.deposit.hash_resolved",
  "data": { "deposit_id": 4821, "tx_hash": "9644c5c5...", "hash_pending": false, "status": "detected" }
}
```

Note: on TRX/SOL/LTC/BCH/XRP `confirmations` stays `0` even when `status` is `"confirmed"`
(settlement-delay networks) — use `status`, not the count.

**Reconciling / normalizing your recorded balances:** the authoritative source is the chain.
To (re)sync one customer, call `GET /api/v1/wallet/{customerID}/balances` (live, per-currency,
with `confirmed`/`spendable`) and set your stored balance to it; for a full sweep of every
customer use `GET /api/v1/wallet/addresses`. This corrects both any past double-count and any
previously-missed deposit in a single pass. Dedup historical deposits by `deposit_id`.

Verify webhook authenticity using `HMAC-SHA256(rawRequestBody, whsec_secret)`, hex-encoded —
the signature is in the `X-Webhook-Signature` header (we also send `X-Webhook-Event` and `X-Webhook-ID`).




## OpenAPI

````yaml /api-reference/openapi.json post /api/v1/wallet/{customerID}/derive
openapi: 3.0.3
info:
  contact:
    email: support@rachfinance.com
    name: Rach Finance Support
  description: >
    Complete REST API for the Rach Finance platform — covering authentication,
    KYC, crypto payment gateway,

    Wallet-as-a-Service (WaaS) HD wallets, remittance/FX transfers, OTC trading,
    virtual accounts,

    analytics, webhooks, push notifications, and all admin operations.


    ## Authentication

    Three authentication methods are supported depending on the endpoint group:


    | Method | Header | Used For |

    |--------|--------|----------|

    | JWT Bearer | `Authorization: Bearer <token>` | Dashboard / user-facing
    endpoints |

    | API Key | `X-API-Key: <key>` | Server-to-server integrations (remittance,
    checkout, WaaS) |

    | Admin Token | `X-Admin-Token: <token>` | Admin-only operations |


    ## API Key Environments


    Every business has two server-to-server API keys. The key **prefix is
    authoritative** —

    the environment is determined by which key you send, not a toggle in your
    dashboard:


    | Prefix | Type | Behaviour |

    |--------|------|-----------|

    | `test_sk_` | Test (sandbox) | Testnet addresses, no real funds move, no
    blockchain confirmations needed |

    | `live_sk_` | Production | Mainnet addresses, real transactions, webhooks
    fire on real confirmations |


    **Use the same code path for both environments** — swap the key, not the
    logic.

    The `is_test_mode` flag is locked onto every checkout session and wallet
    operation

    at the moment the request is authenticated, so mode cannot drift mid-flow
    even if

    you later toggle sandbox mode in the dashboard.


    Sandbox toggle (`POST /api/v1/api-keys/toggle-sandbox`) only affects legacy
    keys

    (no prefix). If you use prefixed keys it has no effect.


    ## Base URL

    `https://api.rach.finance/api/v1/`


    (Rach CaaS — Card-as-a-Service — is served separately at
    `https://api.rach.finance/caas/api/v1/`.)


    ## Official SDKs


    Client libraries covering every endpoint on this page:


    | Language | Install | Source |

    |----------|---------|--------|

    | **JavaScript / Node** | `npm install rachfinance` | `sdk/javascript/` |

    | **Python** | `pip install rachfinance` | `sdk/python/` |

    | **Go** | `go get github.com/rach-finance/rachfinance-go` | `sdk/go/` |

    | **Flutter / Dart** | add `rachfinance` to `pubspec.yaml` | `sdk/flutter/`
    |


    **JavaScript quick start:**

    ```js

    const RachFinance = require('rachfinance');

    const rach = new RachFinance({ apiKey: 'live_sk_...' });

    const session = await rach.checkout.create({ amount: 100, currency: 'USD',
      customerEmail: 'user@example.com', reference: 'ORDER-001' });
    ```


    **Python quick start:**

    ```python

    from rachfinance import RachFinance

    rach = RachFinance(api_key='live_sk_...')

    session = rach.checkout.create(amount=100, currency='USD',
        customer_email='user@example.com', reference='ORDER-001')
    ```


    **Go quick start:**

    ```go

    c, _ := rachfinance.New(rachfinance.WithAPIKey("live_sk_..."))

    session, err := c.Checkout.Create(ctx, rachfinance.CreateCheckoutRequest{
        Amount: 100, Currency: "USD",
        CustomerEmail: "user@example.com", Reference: "ORDER-001",
    })

    ```


    **Flutter quick start:**

    ```dart

    final rach = RachFinance(apiKey: 'live_sk_...');

    final session = await rach.checkout.create(
        amount: 100, currency: 'USD',
        customerEmail: 'user@example.com', reference: 'ORDER-001');
    ```


    ## Common Error Format

    ```json

    { "error": "Human-readable error message" }

    ```
  title: Rach Finance API
  version: 1.0.0
servers:
  - description: Production
    url: https://api.rach.finance
  - description: Local development
    url: http://localhost:8080
security: []
tags:
  - name: Checkout (Crypto Gateway)
  - name: WaaS (Wallet-as-a-Service)
  - description: >
      Unified token swap API for merchants. Same-chain swaps on POL/BSC are
      executed via the

      Rach FiatSwapV2 smart contract; cross-chain pairs are routed through LiFi.
      Merchants

      consume one API — routing is invisible to them.


      **Auth:** Quote is public. Execute and history require `X-API-Key`.
    name: Swap
  - description: >
      Real-time crypto market data service — included with every merchant
      account.

      Prices for 100+ coins served from Rach's edge cache with no additional
      setup required.


      **Auth:** `X-API-Key` or `Authorization: Bearer <key>`. Health check is
      public.


      **Rate limit:** 120 REST requests per merchant per minute.


      **WebSocket:** Connect to `/v1/market/ws?key=<api-key>`, send a subscribe
      message, then receive

      a snapshot immediately followed by real-time price ticks as they change.
    name: Market Data
paths:
  /api/v1/wallet/{customerID}/derive:
    post:
      tags:
        - WaaS (Wallet-as-a-Service)
      summary: Derive a blockchain address
      description: >
        Derives a blockchain address for the specified network at the given
        BIP-44 index.

        Set `enable_monitoring: true` to have the payment monitor watch this
        address

        for incoming deposits and push webhooks.


        Requires an **active** subscription. Returns `402` if the plan has
        expired.

        Deriving additional addresses for an existing customer wallet does
        **not** count

        against the wallet cap — only creating a new customer wallet does.


        **Deposit lifecycle (when `enable_monitoring: true`):**


        | Event | When | Notes |

        |-------|------|-------|

        | `wallet.deposit.detected` | deposit first seen on-chain | may carry
        `hash_pending: true` |

        | `wallet.deposit.hash_resolved` | real on-chain tx hash resolved |
        **same `deposit_id`**, `tx_hash` now real |

        | `wallet.deposit.confirmed` | block depth / settlement delay met | safe
        to credit |


        **Idempotency — dedup on `deposit_id`, NOT `tx_hash`.** Every deposit
        webhook carries a stable

        `deposit_id`; record it and ignore repeats. `tx_hash` may initially be a
        placeholder (`pending_…`)

        when the real on-chain hash isn't indexable yet — then `hash_pending` is
        `true`, and a follow-up

        `wallet.deposit.hash_resolved` (same `deposit_id`) arrives later with
        the real `tx_hash`. Update

        the hash on your existing record; **never credit a second time.**


        **Addresses** are always EIP-55 checksummed for EVM chains
        (ETH/BSC/POL), matching the casing

        returned by `GET /balances` and `list addresses`. **The native BSC coin
        is `BNB`** (never `BSC`).


        The `confirmations` field behaviour differs by network:


        | Network | Confirmation method | `confirmations` value |

        |---------|--------------------|-----------------------|

        | ETH, BSC, POL | Block depth via `eth_getTransactionReceipt` | Real
        block count (e.g. `12`) |

        | BTC | Block depth via Esplora API | Real block count (e.g. `6`) |

        | TRX, SOL, LTC, BCH, XRP | Time-based settlement delay | Always `0`
        even when confirmed |


        **TRX/SOL/LTC/BCH/XRP always emit `"confirmations": 0`** in the webhook
        payload — even when

        `status` is `"confirmed"`. This is expected: these networks use a
        settlement delay instead of

        block depth. Do not treat `confirmations: 0` as an error on these
        networks.

        Use `status === "confirmed"` as the authoritative signal, not the
        `confirmations` count.


        **Do not credit customer funds until you receive
        `wallet.deposit.confirmed`.**


        **Webhook payload shape:**

        ```json

        {
          "event": "wallet.deposit.detected",
          "data": {
            "deposit_id": 4821,             // STABLE idempotency key — dedup on THIS, not tx_hash
            "customer_id": "cus_abc123",
            "network": "TRX",
            "address": "TV8f...",           // EVM addresses are EIP-55 checksummed
            "amount": "8.152212",
            "currency": "USDT",             // native BSC coin is "BNB"
            "tx_hash": "pending_a1b2c3...", // may be a placeholder until resolved
            "hash_pending": true,           // true ⇒ the real hash arrives via hash_resolved
            "confirmations": 0,
            "status": "detected",
            "detected_at": "2026-07-21T04:41:30Z",
            "detected_by": "custom",
            "safe_to_credit": false
          }
        }

        ```


        Follow-up once the real on-chain hash is known (**same `deposit_id`** —
        update, don't re-credit):

        ```json

        {
          "event": "wallet.deposit.hash_resolved",
          "data": { "deposit_id": 4821, "tx_hash": "9644c5c5...", "hash_pending": false, "status": "detected" }
        }

        ```


        Note: on TRX/SOL/LTC/BCH/XRP `confirmations` stays `0` even when
        `status` is `"confirmed"`

        (settlement-delay networks) — use `status`, not the count.


        **Reconciling / normalizing your recorded balances:** the authoritative
        source is the chain.

        To (re)sync one customer, call `GET
        /api/v1/wallet/{customerID}/balances` (live, per-currency,

        with `confirmed`/`spendable`) and set your stored balance to it; for a
        full sweep of every

        customer use `GET /api/v1/wallet/addresses`. This corrects both any past
        double-count and any

        previously-missed deposit in a single pass. Dedup historical deposits by
        `deposit_id`.


        Verify webhook authenticity using `HMAC-SHA256(rawRequestBody,
        whsec_secret)`, hex-encoded —

        the signature is in the `X-Webhook-Signature` header (we also send
        `X-Webhook-Event` and `X-Webhook-ID`).
      parameters:
        - in: path
          name: customerID
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeriveAddressRequest'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  address:
                    example: bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh
                    type: string
                  customer_id:
                    type: string
                  derivation_path:
                    example: m/84'/0'/0'/0/0
                    type: string
                  index:
                    type: integer
                  is_testnet:
                    type: boolean
                  monitored:
                    type: boolean
                  network:
                    enum:
                      - BTC
                      - BCH
                      - LTC
                      - ETH
                      - BSC
                      - POL
                      - TRX
                      - SOL
                      - XRP
                    type: string
                type: object
          description: Derived address
        '400':
          description: Invalid network or request body
        '402':
          content:
            application/json:
              schema:
                properties:
                  error:
                    example: subscription expired — please renew your plan to continue
                    type: string
                type: object
          description: Subscription expired or cancelled
        '404':
          description: Wallet not found for customer
      security:
        - ApiKeyAuth: []
components:
  schemas:
    DeriveAddressRequest:
      properties:
        enable_monitoring:
          default: false
          description: Subscribe this address to the payment monitor for deposit detection
          type: boolean
        index:
          default: 0
          type: integer
        is_testnet:
          default: false
          type: boolean
        network:
          enum:
            - BTC
            - BCH
            - LTC
            - BSC
            - ETH
            - POL
            - TRX
            - SOL
            - XRP
          type: string
      required:
        - network
      type: object
  securitySchemes:
    ApiKeyAuth:
      description: |
        Business API key for server-to-server integrations.
        Key prefix determines the environment — no separate flag needed:
        `test_sk_*` = sandbox/testnet, `live_sk_*` = production/mainnet.
      in: header
      name: X-API-Key
      type: apiKey

````