# Add Bank Account Source: https://guides.saber.money/api-reference/bank-account-operations/add-bank-account openapi-v2.json POST /api/v2/user/bank_account Add a bank account for a user. Supported for all jurisdictions. | **Country** | **Required Fields** | | --- | --- | | India (`IND`) | `account_number`, `bank_code` (IFSC format) | | UK (`GBR`) | `account_holder_name`, `account_number` (max 8 digits), `bank_code` (sort code), `account_type`, `bank_name` | | UAE (`ARE`) | `account_holder_name`, `account_type`, `bank_name`, `bank_iban` (23 chars, starts with `AE`) | | Europe (EUR countries) | `account_holder_name`, `bank_iban`, `account_type`, `bank_name` | | Philippines (`PHL`) | `account_number` (no spaces), `account_holder_name`, `bank_code` | For India, bank accounts go to `PENDING` and are validated asynchronously via penny-drop (a ₹1 deposit to confirm the account and match the account holder name against the user's KYC `legal_name`). For the Philippines, accounts are approved instantly with no penny-drop step. Use [Get Bank Codes for Philippines](/api-reference/bank-account-operations/get-bank-codes-for-country) to look up valid `bank_code` values before calling this endpoint. **Response field values** | Field | Possible Values | | --- | --- | | `account_type` | `SAVINGS`, `CURRENT`, `JOINT`, `CHECKING` | | `validation_status` | `UNINITIATED`, `PENDING`, `APPROVED`, `DELETED`, `DELETED_BY_USER`, `REJECTED`, `NAME_REJECTED`, `VERIFICATION_REQUIRED` | | `routing_type` | `IFSC`, `ROUTING_NUMBER`, `SORT_CODE`, `BANK_CODE`, `IBAN`, `null` | | `is_nre` | `true` if this is an Indian NRE (Non-Resident External) account — set automatically during penny-drop validation, not client-settable. See [INR Payouts to NRE Bank Accounts](/guides/offramp/06-inr-payouts-nre-bank-accounts/). | | `country` | ISO alpha-3 country code. Omitted if not resolved. | A user can have a limited number of `APPROVED` bank accounts at a time (error `304012` when exceeded) — the exact limit is configured per client/country; contact Saber to confirm yours. # Approve Bank Account [Sandbox Only] Source: https://guides.saber.money/api-reference/bank-account-operations/approve-bank-account-sandbox-only openapi-v2.json PUT /api/v2/user/bank_account/approve Approve a pending bank account for a user. # Delete Bank Account Source: https://guides.saber.money/api-reference/bank-account-operations/delete-bank-account openapi-v2.json DELETE /api/v2/user/bank_account Delete a bank account associated with a user. # Get Bank Account by ID Source: https://guides.saber.money/api-reference/bank-account-operations/get-bank-account-by-id openapi-v2.json GET /api/v2/user/bank_account/{bank_uuid} Retrieve details of a specific bank account by its UUID. **Response field values** | Field | Possible Values | | --- | --- | | `account_type` | `SAVINGS`, `CURRENT`, `JOINT`, `CHECKING` | | `validation_status` | `UNINITIATED`, `PENDING`, `APPROVED`, `DELETED`, `DELETED_BY_USER`, `REJECTED`, `NAME_REJECTED`, `VERIFICATION_REQUIRED` | | `routing_type` | `IFSC`, `ROUTING_NUMBER`, `SORT_CODE`, `BANK_CODE`, `IBAN`, `null` | | `is_nre` | `true` if this is an Indian NRE (Non-Resident External) account — set automatically during penny-drop validation, not client-settable. See [INR Payouts to NRE Bank Accounts](/guides/offramp/06-inr-payouts-nre-bank-accounts/). | | `country` | ISO alpha-3 country code. Omitted if not resolved. | # Get Bank Accounts Source: https://guides.saber.money/api-reference/bank-account-operations/get-bank-accounts openapi-v2.json GET /api/v2/user/bank_account Retrieve a list of bank accounts for a user. Optionally filter by validation status. **Response field values** | Field | Possible Values | | --- | --- | | `account_type` | `SAVINGS`, `CURRENT`, `JOINT`, `CHECKING` | | `validation_status` | `PENDING`, `APPROVED`, `DELETED`, `DELETED_BY_USER`, `REJECTED`, `NAME_REJECTED`, `VERIFICATION_REQUIRED` | | `routing_type` | `IFSC`, `ROUTING_NUMBER`, `SORT_CODE`, `BANK_CODE`, `IBAN`, `null` | | `is_nre` | `true` if this is an Indian NRE (Non-Resident External) account — set automatically during penny-drop validation, not client-settable. See [INR Payouts to NRE Bank Accounts](/guides/offramp/06-inr-payouts-nre-bank-accounts/). | | `country` | ISO alpha-3 country code. Omitted if not resolved. | # Get Bank Codes for Country Source: https://guides.saber.money/api-reference/bank-account-operations/get-bank-codes-for-country openapi-v2.json GET /api/v2/user/bank_account/bank-codes/{countryIso} Look up the supported bank names and their `bank_code` values for a country, for use in [Add Bank Account](/api-reference/bank-account-operations/add-bank-account). Currently only `PHL` (Philippines) is supported. Response `data` is a flat object mapping bank name to `bank_code` — pass the `bank_code` value as-is into `add-bank-account`'s `bank_code` field. # Mark Bank Account as NRE Source: https://guides.saber.money/api-reference/bank-account-operations/mark-bank-account-as-nre openapi-v2.json PUT /api/v2/user/bank_account/{bank_account_id}/nre Mark a bank account as an NRE (Non-Resident External) account. Returns the full, updated bank account object. # Get EDD Request Source: https://guides.saber.money/api-reference/kyc-and-edd-operations/edd/get-edd-request openapi-v2.json GET /api/v2/wallet/limits/edd/requests/{request_uuid} Retrieve details of a specific EDD (Enhanced Due Diligence) request by its UUID. # List EDD Requests Source: https://guides.saber.money/api-reference/kyc-and-edd-operations/edd/list-edd-requests openapi-v2.json GET /api/v2/wallet/limits/edd/requests Retrieve a list of all EDD (Enhanced Due Diligence) requests along with their statuses. # Initiate KYC (Generate KYC URL) Source: https://guides.saber.money/api-reference/kyc-and-edd-operations/initiate-kyc-generate-kyc-url openapi-v2.json POST /api/v2/user/kyc/initiate Initiate KYC flow for the user. For some countries (e.g. USA), this generates a KYC partner redirect URL dynamically via this API call. Scope: USD onramp flows only. For India, use the KYC Widget instead: `https://app.saber.money/kyc?client_id=...&user_id=...×tamp=...&secret=...`, with `secret` = HMAC-SHA256 of `client_id + timestamp + "sdk" + user_id`. See the [KYC Widget guide](/guides/kyc-and-bank-accounts/user-kyc/01-kyc-widget/), or [KYC Sharing](/api-reference/kyc-and-edd-operations/kyc-sharing/send-user-kyc-data) as the non-widget option. For `country: "USA"`, the response includes a `status` (`ACTIVE` or `PENDING`) and a redirect `url` for the KYC flow. # Send User KYC Data Source: https://guides.saber.money/api-reference/kyc-and-edd-operations/kyc-sharing/send-user-kyc-data openapi-v2.json POST /api/v2/user/kyc Share User KYC PII details for a user you have already verified. 1. Share Data (including legal name, document number etc.) 2. Share Supporting Documents # Send User KYC Documents Source: https://guides.saber.money/api-reference/kyc-and-edd-operations/kyc-sharing/send-user-kyc-documents openapi-v2.json POST /api/v2/user/kyc/upload_document Share User KYC PII supporting documents (PDF, image, or Aadhar XML) Request must be sent as `multipart/form-data` with the `file` field containing the document. # Update User KYC Data Source: https://guides.saber.money/api-reference/kyc-and-edd-operations/kyc-sharing/update-user-kyc-data openapi-v2.json PUT /api/v2/user/kyc Update previously shared User KYC PII details. # Reset KYC [Sandbox Only] Source: https://guides.saber.money/api-reference/kyc-and-edd-operations/reset-kyc-sandbox-only openapi-v2.json DELETE /api/v2/user/kyc Reset a user's KYC data. Returns `204 No Content` on success with an empty response body. # Get User Limits Source: https://guides.saber.money/api-reference/limits/get-user-limits openapi-v2.json GET /api/v2/wallet/limits Retrieve the transaction limits for a user, broken down by limit period (daily, weekly, monthly, lifetime, per-transaction) and payment method. # Validate Transaction Source: https://guides.saber.money/api-reference/limits/validate-transaction openapi-v2.json POST /api/v2/wallet/transaction/validate Validate whether a transaction is within the user's configured limits before executing it. # Confirm External Sell On-chain Funding Source: https://guides.saber.money/api-reference/offramp/from-external-wallet/confirm-external-sell-on-chain-funding openapi-v2.json POST /api/v2/wallet/transaction/crypto/sell/{id}/confirm-onchain-funding Confirm the on-chain funding transaction for an external sell. This endpoint returns `202 Accepted`, not `200 OK`. Some service branches still return a service-specific `400` error body such as `transaction not found`. # Create External Sell Source: https://guides.saber.money/api-reference/offramp/from-external-wallet/create-external-sell openapi-v2.json POST /api/v2/wallet/transaction/crypto/sell Create an external crypto sell transaction that settles to fiat. **Response field values** | Field | Possible Values | | --- | --- | | `status` | `CREATED`, `PAYMENT_INITIATED`, `FUNDS_RECEIVED`, `COMPLETED`, `FAILED`, `REFUND_INITIATED`, `REFUND_COMPLETED`, `REFUND_FAILED`. Typically `CREATED` immediately after creation. Check status via [Get External Sell Transaction Details](/api-reference/offramp/from-external-wallet/get-external-sell-transaction-details) using `transaction_type=EXTERNAL_SELL`. | | `failure_code`, `failure_desc` | Empty string until the transaction fails | | `refund_wallet_address` | Defaults to `sender_wallet_address` when not provided | | `refund_network` | Defaults to `network` when not provided | # Get External Sell Quote Source: https://guides.saber.money/api-reference/offramp/from-external-wallet/get-external-sell-quote openapi-v2.json GET /api/v2/wallet/w/quote Generate a quote for buying or selling crypto to/from an external wallet — see the specific quote page for which params to pass. Sell-direction variant of the shared quote endpoint - generates a quote for selling crypto from an external wallet. # Get External Sell Transaction Details Source: https://guides.saber.money/api-reference/offramp/from-external-wallet/get-external-sell-transaction-details openapi-v2.json GET /api/v2/wallet/transaction Fetch details and status for a transaction. Pass `transaction_type` and `transaction_id` to select which record to fetch — see the specific transaction-details page for the value to use. Pass `transaction_type=EXTERNAL_SELL` to fetch details for a post-funded, quote-locked offramp transaction. **Possible `status` values:** `CREATED`, `PAYMENT_INITIATED`, `FUNDS_RECEIVED`, `COMPLETED`, `FAILED`, `REFUND_INITIATED`, `REFUND_COMPLETED`, `REFUND_FAILED` **Error codes:** `4004`: Transaction not found # Create Pool Sell Transaction Source: https://guides.saber.money/api-reference/offramp/from-pool-wallet/create-pool-sell-transaction openapi-v2.json POST /api/v2/wallet/transaction/pool/sell Create a pool sell transaction. **Response field values** | Field | Possible Values | | --- | --- | | `transaction_type` | `POOL_SELL` | | `status` | `CREATED`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`, `APPROVAL_REQUIRED`, `SOURCE_INVALID`, `VERIFICATION_PENDING`, `REFUND_INITIATED`, `REFUND_COMPLETED`. Typically `PROCESSING` immediately after creation. | # Get List of Pool Sell Transactions Source: https://guides.saber.money/api-reference/offramp/from-pool-wallet/get-list-of-pool-sell-transactions openapi-v2.json GET /api/v2/wallet/transactions Fetch a paginated list of transactions. Pass `transaction_type` to select which list to fetch — see the specific transaction-list page for the value to use. Pass `transaction_type=POOL_SELL` to list pool sell transactions. # Get Pool Sell Transaction Details Source: https://guides.saber.money/api-reference/offramp/from-pool-wallet/get-pool-sell-transaction-details openapi-v2.json GET /api/v2/wallet/transaction Fetch details and status for a transaction. Pass `transaction_type` and `transaction_id` to select which record to fetch — see the specific transaction-details page for the value to use. Pass `transaction_type=POOL_SELL` to fetch details for a pool sell transaction. **Possible `status` values:** `CREATED`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`, `APPROVAL_REQUIRED`, `SOURCE_INVALID`, `VERIFICATION_PENDING`, `REFUND_INITIATED`, `REFUND_COMPLETED` **Error codes:** `4004`: Transaction not found # Create Sell Transaction Source: https://guides.saber.money/api-reference/offramp/from-user-wallet/create-sell-transaction openapi-v2.json POST /api/v2/wallet/transaction/sell Create a sell transaction from the user's wallet. **Response field values** | Field | Possible Values | | --- | --- | | `transaction_type` | `SELL` | | `status` | Typically `CREATED` immediately after creation | | `failure_code`, `failure_desc` | `null` until the transaction fails | # Get List of Sell Transactions Source: https://guides.saber.money/api-reference/offramp/from-user-wallet/get-list-of-sell-transactions openapi-v2.json GET /api/v2/wallet/transactions Fetch a paginated list of transactions. Pass `transaction_type` to select which list to fetch — see the specific transaction-list page for the value to use. Pass `transaction_type=SELL` to list sell transactions for a user. # Get Sell Transaction Details Source: https://guides.saber.money/api-reference/offramp/from-user-wallet/get-sell-transaction-details openapi-v2.json GET /api/v2/wallet/transaction Fetch details and status for a transaction. Pass `transaction_type` and `transaction_id` to select which record to fetch — see the specific transaction-details page for the value to use. Pass `transaction_type=SELL` to fetch details for a sell transaction. # Get Sell Quote Source: https://guides.saber.money/api-reference/offramp/get-sell-quote openapi-v2.json GET /api/v2/wallet/s/quote Get a sell quote for converting crypto to fiat. **Behavior** At least one of `from_amount` or `to_amount` must be provided with a non-zero value. **Response field values** | Field | Possible Values | | --- | --- | | `fee_breakup` keys | `platform_fee`, `network_fee`, `client_fee`, `discount`, `tax_on_fee`, `tds` | # Get Beneficiary Details Source: https://guides.saber.money/api-reference/onramp/get-beneficiary-details openapi-v2.json GET /api/v1/wallet/conversion/fiat/methods/bank_transfer/beneficiary API allows you to retrieve beneficiary information required for processing bank transfer payments. This endpoint returns the beneficiary bank details for the selected onramp flow. # Get External Wallet Quote Source: https://guides.saber.money/api-reference/onramp/get-external-wallet-quote openapi-v2.json GET /api/v2/wallet/w/quote Generate a quote for buying or selling crypto to/from an external wallet — see the specific quote page for which params to pass. Buy-direction variant of the shared quote endpoint - generates a quote for buying crypto into an external wallet. **Error codes:** `400001`: missing or invalid parameters; `402001`: unsupported currency pair; `5013`: amount less than min withdraw amount; `402009`: price not available; `402010`: service temporarily unavailable; `901011`: service configuration missing. please contact support; `499999`: Something went wrong! # Get Payment Methods Source: https://guides.saber.money/api-reference/onramp/get-payment-methods openapi-v2.json GET /api/v1/wallet/conversion/fiat/methods Get Payment Methods # Buy Quote Source: https://guides.saber.money/api-reference/onramp/price-lock/buy-quote openapi-v2.json POST /api/v2/wallet/s/quote Generate a price-locked quote for a Buy onramp transaction. Pass the returned quote id as quote_id when creating the transaction to lock the price. **Onramp Price Lock** Onramp Buy, Pool Buy, and External Buy transactions are price-locked only when they are created from a quote. Call the matching quote endpoint first, then pass the returned quote `id` as `quote_id` when creating the transaction. Authentication uses the same HMAC client auth as the other client endpoints. **Request rules** - Provide exactly one of `from_amount` or `to_amount` — not both, not neither. **Response fields** | Field | Meaning | | --- | --- | | `id` | Quote id. Pass this as `quote_id` when creating the transaction. | | `expiry` | Price-lock expiry. The quote must be used to create a transaction before this time. | | `settlement_expiry` | Deadline by which the transaction must be completed (deposit settled). | | `final_price` | Locked price used for the transaction. | | `fee_breakup` keys | `platform_fee`, `network_fee`, `client_fee`, `discount`, `tax_on_fee`, `tds` | A quote is single-use and tied to the client that created it. # External Buy Quote Source: https://guides.saber.money/api-reference/onramp/price-lock/external-buy-quote openapi-v2.json POST /api/v2/wallet/w/quote Generate a price-locked quote for an External Buy onramp transaction (crypto withdrawn to an external wallet). Pass the returned quote id as quote_id when creating the transaction to lock the price. **Onramp Price Lock** An External Buy transaction is price-locked only when it is created from a quote. Call this endpoint first, then pass the returned quote `id` as `quote_id` when creating the External Buy transaction. Authentication uses the same HMAC client auth as the other client endpoints. **Request rules** - Provide exactly one of `from_amount` or `to_amount` — not both, not neither. - `network` is required because the crypto withdrawal leg sends funds to an external wallet. **Response fields** | Field | Meaning | | --- | --- | | `id` | Quote id. Pass this as `quote_id` when creating the transaction. | | `network` | Network the locked quote applies to. | | `expiry` | Price-lock expiry. The quote must be used to create a transaction before this time. | | `settlement_expiry` | Deadline by which the transaction must be completed (deposit settled). | | `final_price` | Locked price used for the transaction. | | `min_crypto_amount` | Minimum crypto amount that can be withdrawn on this network. | | `fee_breakup` keys | `platform_fee`, `network_fee`, `client_fee`, `discount`, `tax_on_fee`, `tds` | A quote is single-use and tied to the client that created it. # Pool Buy Quote Source: https://guides.saber.money/api-reference/onramp/price-lock/pool-buy-quote openapi-v2.json POST /api/v2/wallet/p/quote Generate a price-locked quote for a Pool Buy onramp transaction. Pass the returned quote id as quote_id when creating the transaction to lock the price. **Onramp Price Lock** A Pool Buy transaction is price-locked only when it is created from a quote. Call this endpoint first, then pass the returned quote `id` as `quote_id` when creating the Pool Buy transaction. Authentication uses the same HMAC client auth as the other client endpoints. **Request rules** - Provide exactly one of `from_amount` or `to_amount` — not both, not neither. **Response fields** | Field | Meaning | | --- | --- | | `id` | Quote id. Pass this as `quote_id` when creating the transaction. | | `expiry` | Price-lock expiry. The quote must be used to create a transaction before this time. | | `settlement_expiry` | Deadline by which the transaction must be completed (deposit settled). | | `final_price` | Locked price used for the transaction. | | `fee_breakup` keys | `platform_fee`, `network_fee`, `client_fee`, `discount`, `tax_on_fee`, `tds` | A quote is single-use and tied to the client that created it. # Create External Buy Source: https://guides.saber.money/api-reference/onramp/to-external-wallet/create-external-buy openapi-v2.json POST /api/v2/wallet/transaction/crypto/buy Create an external-wallet crypto buy transaction. **Price lock (`quote_id`)** Pass `quote_id` to create this transaction against a price-locked quote previously issued by the External Buy Quote endpoint (`POST /api/v2/wallet/w/quote`). When `quote_id` is supplied, the locked currency pair, payment method, amounts, network, and fees are resolved from the stored quote; the matching request fields become optional and any values sent for them are ignored. `crypto_wallet_address` is always required. When `quote_id` is absent the endpoint behaves as before and the original fields remain required. ```json { "quote_id": "421b9fb7-6800-4d9d-ba17-083ad662733b", "crypto_wallet_address": "0xf82010290b2c3169ffaB5354d7Bbd09Eb2822724", "source_id": "94b00193-301d-4750-b310-207393d987c9", "bank_transaction_id": "501610337776" } ``` **Response field values** | Field | Possible Values | | --- | --- | | `status` | Typically `PROCESSING` immediately after creation | | `failure_code`, `failure_desc` | Empty string until the transaction fails | If `crypto_wallet_address` is `POOL_TRANSFER`, the handler may also surface broker or deposit-address service errors directly with their underlying code and message. # Get External Buy Detail Source: https://guides.saber.money/api-reference/onramp/to-external-wallet/get-external-buy-detail openapi-v2.json GET /api/v2/wallet/w/transaction/{transaction_type}/{transaction_id} Retrieve the details of an external buy transaction. This route currently supports only `transaction_type=BUY`. **Response field values** | Field | Possible Values | | --- | --- | | `transaction_type` (path) | `BUY` | | `transaction_type` (response) | `CRYPTO_BUY` | | `status` | Typically `PROCESSING` for an in-flight transaction | # Get List of Buy Transactions Source: https://guides.saber.money/api-reference/onramp/to-external-wallet/get-list-of-buy-transactions openapi-v2.json GET /api/v2/wallet/transactions Fetch a paginated list of transactions. Pass `transaction_type` to select which list to fetch — see the specific transaction-list page for the value to use. Pass `transaction_type=EXTERNAL_BUY` to list external buy transactions. `ONRAMP_AND_WITHDRAW` is a deprecated alias for this value - use `EXTERNAL_BUY` instead. Plain `BUY` and `POOL_BUY` are also valid values for other buy flows. # Create Pool Buy Transaction Source: https://guides.saber.money/api-reference/onramp/to-pool-wallet/create-pool-buy-transaction openapi-v2.json POST /api/v2/wallet/transaction/pool/buy Create a pool buy transaction. **Price lock (`quote_id`)** Pass `quote_id` to create this transaction against a price-locked quote previously issued by the Pool Buy Quote endpoint (`POST /api/v2/wallet/p/quote`). When `quote_id` is supplied, the locked currency pair, payment method, amounts, and fees are resolved from the stored quote; the matching request fields become optional and any values sent for them are ignored. When `quote_id` is absent the endpoint behaves as before and the original fields remain required. ```json { "quote_id": "421b9fb7-6800-4d9d-ba17-083ad662733b", "source_id": "94b00193-301d-4750-b310-207393d987c9", "bank_transaction_id": "501610337776" } ``` **Response field values** | Field | Possible Values | | --- | --- | | `transaction_type` | `POOL_BUY` | | `status` | Typically `PROCESSING` immediately after creation | # Get List of Pool Buy Transactions Source: https://guides.saber.money/api-reference/onramp/to-pool-wallet/get-list-of-pool-buy-transactions openapi-v2.json GET /api/v2/wallet/transactions Fetch a paginated list of transactions. Pass `transaction_type` to select which list to fetch — see the specific transaction-list page for the value to use. Pass `transaction_type=POOL_BUY` to list pool buy transactions. # Get Pool Buy Transaction Details Source: https://guides.saber.money/api-reference/onramp/to-pool-wallet/get-pool-buy-transaction-details openapi-v2.json GET /api/v2/wallet/transaction Fetch details and status for a transaction. Pass `transaction_type` and `transaction_id` to select which record to fetch — see the specific transaction-details page for the value to use. Pass `transaction_type=POOL_BUY` to fetch details for a pool buy transaction. **Possible `status` values:** `CREATED`, `PROCESSING`, `PENDING`, `COMPLETED`, `FAILED`, `CANCELLED`, `REVERTED`, `REJECTED`, `EXPIRED`, `SOURCE_INVALID`, `APPROVAL_REQUIRED`, `SUCCESSFUL`, `REFUND_INITIATED`, `REFUND_COMPLETED`, `REFUND_FAILED` **Error codes:** `4004`: Transaction not found # Create User Source: https://guides.saber.money/api-reference/user-operations/create-user openapi-v2.json POST /api/v2/user Registers a new user # Get User Details Source: https://guides.saber.money/api-reference/user-operations/get-user-details openapi-v2.json GET /api/v2/user Fetch user's details, including KYC and Bank Account status **Query parameters** Exactly one of `user_uuid`, `client_user_id`, `email`, or `phone` must be provided. Passing multiple will return an error. **Response field values** | Field | Possible Values | | --- | --- | | `kyc_status` | `Verified`, `Unverified` | # Reset Phone and Email [Sandbox Only] Source: https://guides.saber.money/api-reference/user-operations/reset-phone-and-email-sandbox-only openapi-v2.json PUT /api/v2/user/reset_phone_email Reset the phone number and email of a user. # Reset User [Sandbox Only] Source: https://guides.saber.money/api-reference/user-operations/reset-user-sandbox-only openapi-v2.json DELETE /api/v2/user Reset a user, removing their associated data. Returns `204 No Content` on success with an empty response body. # Update User Source: https://guides.saber.money/api-reference/user-operations/update-user openapi-v2.json PUT /api/v2/user Update a user's profile data. # Get Crypto Deposit Transaction Details Source: https://guides.saber.money/api-reference/wallet-operations/crypto-deposit/get-crypto-deposit-transaction-details openapi-v2.json GET /api/v2/wallet/transaction Fetch details and status for a transaction. Pass `transaction_type` and `transaction_id` to select which record to fetch — see the specific transaction-details page for the value to use. Pass `transaction_type=DEPOSIT` to fetch details for a crypto deposit. # Get List of Crypto Deposit Transactions Source: https://guides.saber.money/api-reference/wallet-operations/crypto-deposit/get-list-of-crypto-deposit-transactions openapi-v2.json GET /api/v2/wallet/transactions Fetch a paginated list of transactions. Pass `transaction_type` to select which list to fetch — see the specific transaction-list page for the value to use. Pass `transaction_type=DEPOSIT` to list crypto deposit transactions. **Possible `status` values:** `CREATED`, `PROCESSING`, `PENDING`, `COMPLETED`, `FAILED`, `CANCELLED`, `REVERTED`, `REJECTED`, `EXPIRED`, `SOURCE_INVALID`, `APPROVAL_REQUIRED`, `SUCCESSFUL`, `REFUND_INITIATED`, `REFUND_COMPLETED`, `REFUND_FAILED` # Get All Deposit Transactions Source: https://guides.saber.money/api-reference/wallet-operations/get-all-deposit-transactions openapi-v2.json GET /api/v1/wallet/deposit_history Get all deposit transactions # Get Wallet Address Source: https://guides.saber.money/api-reference/wallet-operations/get-wallet-address openapi-v2.json GET /api/v1/wallet/user_deposit_address Get Wallet Address **Response field values** | Field | Possible Values | | --- | --- | | `symbol` (query) | Product-supported symbols, e.g. `USDT`, `BTC` | | `network` (query) | Product-supported networks, e.g. `BSC`, `TRX`, `ETH` | | `batchReleaseLimit` | Number or string; treat as opaque | # Get Wallet Balance Source: https://guides.saber.money/api-reference/wallet-operations/get-wallet-balance openapi-v2.json GET /api/v1/wallet/balance Fetch the wallet balance for a user, broken down by coin. **`account` query param:** `SPOT` (optional filter). `coin_info..withdraw_enable` / `deposit_enable` are booleans. # Developer Section Source: https://guides.saber.money/client/dashboard/developers Managing and rotating API keys, and configuring your webhook URL and webhook keys, from the Saber dashboard. The Developer Section of the dashboard is where you manage the credentials and configuration your integration depends on. ## API Keys Your API Key (Client ID) and API Secret (Client Secret) are issued when you're onboarded as a merchant, and are used to generate the HMAC signature required on every API request. From the Developer Section, you can: * View your current active API Key. * **Rotate** your API Secret if it may have been compromised or as part of routine credential hygiene. Rotating generates a new secret — update it in your systems immediately, since requests signed with the old secret will stop being accepted once the rotation takes effect. ## Webhook Configuration Configure the URL Saber sends event notifications to: * **Webhook URL** — the endpoint on your server that receives event pushes for transaction status changes, KYC status changes, and other milestones. * **Webhook Key** — used to verify the authenticity of incoming webhook payloads, similar in spirit to how your API Secret signs outbound requests. Use this to confirm a webhook actually originated from Saber before acting on it. You can update the Webhook URL and rotate the Webhook Key from this section as needed. # Overview Source: https://guides.saber.money/client/dashboard/overview What you can do in the Saber merchant dashboard — stats, user and transaction details, reports, status checks, balance withdrawals, and access to the developer and team management sections. The Saber dashboard is the web-based portal for merchants to monitor and manage their account without needing to call the API directly. Saber offers two dashboards: | Dashboard | Description | Link | | :- | :- | :- | | **Sandbox Testing Dashboard** | View and act on testing data. | [Sandbox Dashboard](https://dashboard.sandbox.saber.money) | | **Production Dashboard** | Manage and view production data. | [Production Dashboard](https://dashboard.saber.money) | ## Getting Access Once you've registered as a merchant, your relationship manager will send you a dashboard invite. Further team members can be directly added from the dashboard or can be requested to be added by your Saber POC. ## Logging in Dashboard login is via OTP — enter your registered email or phone number, and verify with the one-time passcode sent to you to access the dashboard. ## What you can do * **View stats** — a summary view of transaction volumes, success rates, and other account-level metrics. * **View user details** — look up individual users on your account, including their KYC status and linked bank accounts. * **View transaction details** — inspect individual onramp/offramp transactions, including status, amounts, and timestamps. * **Download reports** — export transaction and user data for reconciliation or internal reporting. * **Check transaction status** — search for a specific transaction and see its current lifecycle state without calling the status API. * **Withdraw balances** — initiate a withdrawal of available Pool Account balance directly from the dashboard. ## Developer Section A dedicated area for managing your API integration — API keys, webhook URLs, and webhook keys. See [Developer Section](/client/dashboard/developers) for details. ## Team Management Add teammates to your Saber account and control what they can access based on role. See [Team Management](/client/dashboard/team) for details. # Team Management Source: https://guides.saber.money/client/dashboard/team Adding and viewing team members on a Saber account, along with their role-based access. The Team Management section lets you control who on your team has access to the Saber dashboard, and what they're allowed to see or do once they're in. ## Adding team members Invite a teammate by email — they'll receive an invite to set up access and log in via OTP, the same way you do. ## Viewing team members See a list of everyone currently added to your account, along with their assigned role. ## Role-based access Each team member is assigned a role that determines what they can view and act on within the dashboard — for example, whether they can view transactions and stats only, or also manage API keys, webhooks, and initiate withdrawals. Assign roles based on what a teammate actually needs, especially for anything touching the Developer Section or balance withdrawals. # Webhook Configuration Source: https://guides.saber.money/client/dashboard/webhooks Configuring Saber Money webhook URLs via the Developer Console or your POC, with Webhook Key/Secret signature authentication and sandbox testing. Webhooks let Saber send your system real-time notifications about transaction updates and other status changes. ## Setting up Webhook URL To configure webhooks: ### Through the Dashboard Saber dashboard allows you to configure webhooks directly from the [Developer Console](https://dashboard.saber.money/developer-console) → webhook tab. Setting up webhook credentials is mandatory for enabling webhooks ## Authentication Webhooks are signed with a Webhook Key/Secret pair, generated through the Developer Console. Merchants onboarded before August 2025 can use their API Keys as Webhook Keys. We recommend switching to separate API and Webhook keys via the dashboard. # Available Transaction Flows Source: https://guides.saber.money/client/pool-account/available-flows The transaction models available for sell (pre-funded / post-funded) and buy (pool wallet / external wallet), and how they link through the central Pool Account for liquidity management. Saber supports a few different transaction models depending on whether you're selling (offramp) or buying (onramp) crypto, and how you want liquidity to move. ## Sell models (Offramp) The user's (or merchant's) crypto balance is already available in Saber's system ahead of time. When an offramp is triggered, the required stablecoin amount is deducted from that available balance immediately, and the equivalent fiat is paid out to the user's bank account in the same step. This is the simpler, faster flow since funding and payout aren't sequenced — the funds are already there. The payout is initiated first, against a locked quote, before any crypto has been received. The client then funds the deposit address on-chain, and Saber confirms the deposit before completing the fiat settlement. Because settlement only happens after the crypto deposit is confirmed on-chain, this model is used when funds aren't pre-staged in Saber's system. ## Buy models (Onramp) Fiat collected from the user is converted to stablecoin and credited to your merchant Pool Account, rather than to any individual wallet. Useful when you want converted stablecoin to accumulate as reusable liquidity rather than being sent out immediately. Fiat collected from the user is converted to stablecoin and sent directly to a wallet address and network you specify in the request, rather than staying within Saber's system. ## Linking buy and sell through the Pool Account For merchants running both onramp and offramp on the same corridor, Pool Buy and Pool Sell can be linked through a single Pool Account to manage liquidity as one aggregate flow rather than two disconnected ones: Stablecoin credited to the Pool Account from Pool Buy transactions (onramp proceeds) becomes available balance that Pool Sell transactions (offramp payouts) can draw down. Fiat collected on the onramp side can directly fund fiat paid out on the offramp side, without needing to externally source or manage crypto liquidity for each side separately. Any surplus or shortfall in the Pool Account can then be topped up or withdrawn externally as needed. ## Related pages # Overview Source: https://guides.saber.money/client/pool-account/overview What a Saber Pool Account is, its purpose, and how to fetch pool wallet details and balance. A Pool Account is a merchant-level stablecoin wallet held with Saber, separate from any individual user's wallet. It acts as a shared liquidity source (or destination) that your transactions can draw from or settle into, instead of every transaction being tied to a single user's own wallet. ```mermaid theme={null} flowchart LR U1[User A pay-in] --> P((Pool Wallet)) U2[User B pay-in] --> P U3[User C pay-in] --> P P --> D1[Payout to User X] P --> D2[Payout to User Y] P --> D3[Payout to User Z] ``` ## Purpose Pool Accounts exist to simplify liquidity management at scale: Instead of pre-funding each individual user's wallet with crypto before every payout, a **Pool Sell** transaction deducts stablecoin directly from your Pool Account balance and pays the user out in fiat. Instead of sending every converted stablecoin amount to an external wallet, a **Pool Buy** transaction deposits the converted stablecoin into your Pool Account, where it accumulates as usable liquidity. A single Pool Account can fund offramp payouts and receive onramp proceeds, letting you net fiat collected on one side against fiat paid out on the other, settling the difference in stablecoin rather than moving fiat directly. A Pool Account is provisioned per merchant on request — reach out to your Saber account contact to have one set up. ## Fetching wallet details and balance To check what's available in your Pool Account, fetch the pool wallet's balance and deposit address: ```http theme={null} GET /api/v1/wallet/balance?account=SPOT&symbol=USDT ``` ```json theme={null} { "success": true, "data": { "coin_info": { "USDT": { "coin": "USDT", "name": "TetherUS", "available_balance": 76.7, "available_balance_usd": 76.7, "total_balance": 76.7, "total_balance_usd": 76.7, "withdraw_enable": true, "deposit_enable": true } }, "available_balance": 76.7, "total_balance": 76.7 } } ``` ```http theme={null} GET /api/v1/wallet/user_deposit_address?symbol=USDT&network=BSC ``` ```json theme={null} { "success": true, "data": [ { "coin": "USDT", "network": "BSC", "name": "Binance Smart Chain (BEP20)", "address": "0xc37e9cbe6e1d481d9372585e1b91cb387853d352", "tag": "", "url": "https://bscscan.com/address/0xc37e9cbe6e1d481d9372585e1b91cb387853d352", "batchReleaseLimit": 0 } ] } ``` The deposit address is where you send stablecoin to top up the Pool Account manually (outside of onramp proceeds). ## Related pages # Supported Networks Source: https://guides.saber.money/client/pool-account/supported-networks Lists the blockchain networks accepted for USDT and USDC deposits and withdrawals, with the network codes passed to deposit-address APIs. Networks supported for USDT and USDC deposits and withdrawals. Pass the value in the **Network** column to APIs that fetch deposit wallet addresses. This list is indicative and may change over time. You can also use the API to fetch the current list of available networks for each token, along with their min/max amount limits. | Name | Network | | - | - | | Aptos | `APTOS` | | Arbitrum One | `ARBI` | | BNB Smart Chain | `BSC` | | CAVAX | `CAVAX` | | CELO | `CELO` | | Ethereum | `ETH` | | KAVAEVM | `KAVAEVM` | | Mantle Network | `MANTLE` | | Polygon PoS | `MATIC` | | OP Mainnet | `OP` | | SOL | `SOL` | | TON | `TON` | | TRC20 | `TRX` | | Name | Network | | - | - | | Arbitrum One | `ARBI` | | Base Mainnet | `BASE` | | BNB Smart Chain | `BSC` | | CAVAX | `CAVAX` | | Ethereum | `ETH` | | Mantle Network | `MANTLE` | | Polygon PoS | `MATIC` | | Polygon (bridged) | `MATIC.E` | | OP Mainnet | `OP` | | SOL | `SOL` | ## Related pages # API Authentication Source: https://guides.saber.money/getting-started/auth Generating the Saber X-Signature HMAC-SHA256 authentication token from client_id, timestamp, and client_secret for admin, user, and SDK requests. Before using Saber's APIs or SDK, generate an authentication signature — the process differs slightly depending on the type of request. ## Receiving keys Saber provides two key sets — sandbox and production — each with an **API Key** (`client_id`) and **API Secret** (`client_secret`). Use these to generate an authentication token (API Signature) for your requests. Contact your Saber representative to get access to Saber dashboard. API Keys are generated from the dashboard. ## Generating the authentication token ```javascript theme={null} var timestamp = Math.floor(Date.now() / 1000).toString(); ``` The signature string differs by request type: For merchant-level requests not tied to a specific user (e.g. create a user): ```javascript theme={null} var sigString = clientId + timestamp; ``` For requests tied to a specific user (e.g. create an OFFRAMP transaction): ```javascript theme={null} var sigString = clientId + timestamp + user_id; ``` For generating a token to access the hosted Web Widget: ```javascript theme={null} var sigString = clientId + timestamp + 'sdk' + user_id; ``` ```javascript theme={null} var signature = CryptoJS.HmacSHA256(sigString, clientSecret).toString().toUpperCase(); ``` The resulting `signature` can now be used to authenticate API requests. ### Full example ```javascript theme={null} // Variables (these should be securely stored and handled) var clientId = 'YOUR_API_KEY'; // Replace with your actual API Key var clientSecret = 'YOUR_API_SECRET'; // Replace with your actual client secret // Step 1: Generate timestamp var timestamp = Math.floor(Date.now() / 1000).toString(); // Step 2: Create the signature string var sigString = clientId + timestamp; // Admin operation (e.g. create a user) // ------- OR ------- // var sigString = clientId + timestamp + user_id; // User operation (e.g. create OFFRAMP transaction) // ------- OR ------- // var sigString = clientId + timestamp + 'sdk' + user_id; // Web Widget / SDK access // Step 3: Generate the HMAC-SHA256 signature var signature = CryptoJS.HmacSHA256(sigString, clientSecret).toString().toUpperCase(); ``` ## Request headers The earlier header names `X-CLIENT-ID` (client ID) and `X-CLIENT-SECRET` (generated signature) have been replaced: | Old header | New header | | - | - | | `X-CLIENT-ID` | `X-API-KEY` | | `X-CLIENT-SECRET` | `X-SIGNATURE` | You may still find the older names referenced in some documentation. We suggest using the new headers, but both work — they're backwards compatible. ## Related pages # MCP Source: https://guides.saber.money/getting-started/mcp Connect Saber's documentation directly to your AI tools and IDE to speed up integration. The Saber MCP (Model Context Protocol) server connects your AI assistant or IDE directly to the Saber Money documentation, so you can ask integration questions as you build and get answers grounded in the actual, current docs — instead of relying on the model's own prior knowledge, which can be outdated or simply wrong about Saber's API. MCP is an open protocol for connecting AI assistants to external tools and data sources. If your AI tool or IDE already supports MCP, connecting it to Saber takes just a server URL. ## Connect Add this URL to your MCP-compatible AI assistant or IDE: ``` https://mudrex-inc.mintlify.site/mcp ``` 1. Go to [claude.ai/settings/connectors](https://claude.ai/settings/connectors). 2. Click **Add custom connector**. 3. Paste in the server URL above. 4. Click **Add**, then start a new chat and ask a Saber integration question. Run: ```bash theme={null} claude mcp add --transport http saber https://mudrex-inc.mintlify.site/mcp ``` Verify it's connected: ```bash theme={null} claude mcp list ``` 1. Open **Cursor Settings → MCP**. 2. Click **Add new global MCP server**. 3. Add this to `mcp.json`: ```json theme={null} { "mcpServers": { "saber": { "url": "https://mudrex-inc.mintlify.site/mcp" } } } ``` Create (or edit) `.vscode/mcp.json` in your project: ```json theme={null} { "servers": { "saber": { "url": "https://mudrex-inc.mintlify.site/mcp" } } } ``` 1. Go to **Settings → Connectors**. 2. Click **Add custom connector**. 3. Paste in the server URL above. 4. Save, then enable it for a chat. ## What it does * **Search and retrieval** — searches and retrieves content from the published Saber Money documentation to answer your questions as you integrate. * **Feedback reporting** — if it finds a page that's incorrect, outdated, confusing, or incomplete while helping you, it can report that back to the Saber docs team directly. ## What it doesn't do The server is read-only and scoped strictly to the published Saber Money documentation: * It does not perform actions or mutate any state in your Saber account — it can't create users, initiate transactions, or make any changes. * It does not access private or authenticated account data unless your MCP session is specifically authenticated for that. * Aside from reporting documentation feedback, it doesn't do anything beyond retrieving and answering from public site content. ## Why use it * **Answers grounded in the current docs** — responses are based on what's actually published, not on the model's training data, which may be stale or incorrect for Saber specifically. * **Cited, verifiable answers** — results reference the relevant documentation pages, so you can jump straight to the source. * **Faster integration** — ask questions about a flow, field, or edge case directly inside your IDE or AI assistant while you're writing integration code, without switching tabs to search the docs manually. # Understanding the Platform Source: https://guides.saber.money/getting-started/platform-intro Introduces Saber Money's base URLs, HMAC authentication, core entities (users, pool accounts), fiat/crypto transactions, operations, integration interfaces, and typical flow. The Saber Money platform enables businesses to offer fiat ↔ crypto conversion services through APIs, SDKs, and a merchant dashboard. This page covers the key entities, operations, and concepts you need before working with the APIs. ## Base URL ```text theme={null} https://api.sandbox.saber.money ``` ```text theme={null} https://api.saber.money ``` To ensure secure data exchange, merchants should whitelist Saber Money's IP addresses within their systems. This restricts access to trusted sources, helping to prevent unauthorized access. ## Authentication Every API request must be securely authenticated. Saber uses **HMAC signature-based authentication**. * **API Key (Client ID)** and **API Secret (Client Secret)** are issued to each merchant. * To make a request, the client generates an **API Signature** by signing the payload with the secret. * The API Signature, along with the Client ID, is passed in the request headers. * This ensures requests are validated, secure, and cannot be tampered with. This mechanism is consistent across all endpoints and must be implemented before accessing any functionality. See [API Authentication](/getting-started/auth) for the exact signature-generation steps. ## Core entities The central entity of the platform — almost every operation, fiat or crypto, requires a user context. * **KYC**: must be completed and **approved** before a user can transact. * **Bank Account**: a user can link one or more accounts, mandatory for fiat operations (payins and payouts). * **Wallets**: each user is automatically assigned blockchain wallets across supported networks, used for deposits, withdrawals, and transfers. A special account type mapped to the merchant's business — the treasury account for internal operations: * Liquidity management * Handling **Pool Buy** or **Pool Sell** operations * Acting as the settlement layer for certain transaction types While end-users transact through their personal user accounts, the Pool Account lets merchants interact with the Saber ecosystem at the business level. See [Pool Account Overview](/client/pool-account/overview). ## Transactions All transactions on Saber are initiated and recorded at the **user level**, falling into two categories: * **Deposit**: a user deposits crypto into their assigned wallet. * **Withdrawal**: a user withdraws crypto from their wallet to an external address. * **Internal Transfer**: transfers crypto between two users within the Saber system. * **Payins (Onramp)**: a user deposits fiat via a linked bank account, converted into stablecoins or other supported assets. * **Payouts (Offramp)**: a user withdraws fiat to a linked bank account, funded by stablecoins or crypto balances. Each transaction has a defined lifecycle with states (e.g. `initiated`, `pending`, `completed`, `failed`), and developers can monitor these via **webhooks** or API fetch calls. ## Operations Performed at the **merchant level**, not tied to any single user. Examples: * Creating new users * Internal transfers between users Tied to a specific user account. Examples: * Submitting and approving KYC * Adding bank accounts * Initiating transactions (fiat or crypto) Depending on Admin vs. User operation, signature generation varies slightly — see [API Authentication](/getting-started/auth). ## Integration interfaces and tools The primary integration point for all client and user operations — programmatic control over user creation, transactions, balances, and more. A hosted user-facing component provided by Saber, used to collect KYC and bank account information without merchants building their own UI. Merchants generate a Widget URL and redirect the user, who completes the process on Saber's secure environment. Event notifications sent from Saber to the merchant's server, providing real-time updates on transaction states, KYC status changes, and other events — critical for keeping merchant systems in sync with Saber's backend. A web-based portal for merchants to manage and monitor activity: track transactions and users, generate or rotate API/webhook keys, configure webhook URLs, and perform developer operations like initiating crypto withdrawals or checking balances. ## Typical flow The user is assigned wallets across blockchains. The user is verified and eligible for financial transactions. Via API or Web Widget. Crypto or fiat (payin/payout/transfer). Via webhooks or status fetch endpoints. This sequence underpins most integration scenarios and ensures compliance and traceability across fiat and crypto operations. ## Related pages # Using LLMs Source: https://guides.saber.money/getting-started/using-llms Feed Saber's documentation to your AI assistant as plain text. Saber's documentation is available in two plain-text formats built for LLMs, so you can paste them into a chat or point a tool at them for context grounded in the actual, current docs instead of stale training data. For a live connection instead of a static file, see [MCP](/getting-started/mcp). A concise index of every page on the site, formatted for LLMs. Good for pasting into a chat as lightweight context. The full content of every page on the site in one file. Good for tools that can ingest large context windows. # Welcome to Saber Money Source: https://guides.saber.money/getting-started/welcome Cross-border payment infrastructure powered by stablecoins — onramp, offramp, and user operations for platforms building global money movement. Saber Money is cross-border payment infrastructure powered by stablecoins. We help platforms move money in and out of local currencies around the world — fast, compliant, and without needing to build banking or settlement rails themselves. Under the hood, funds settle through stablecoins. From your side, and your users' side, it looks and feels like payments: a user pays in, a user gets paid out, and your business can hold and move balances across corridors without separately managing crypto infrastructure. Both **USDT** and **USDC** are supported across all Saber operations. ## What you can do with Saber Convert a user's local currency into stablecoin — credited to your Pool Account or sent directly to an external wallet. Convert stablecoin into local currency, paid out directly to your user's bank account. Create users, run KYC, link bank accounts, and manage user-level wallets and limits. Hold and manage stablecoin liquidity at the merchant level to power onramp and offramp at scale. ## Where Saber operates Saber supports payment corridors across multiple regions, with new corridors added regularly. Every corridor comes with its own supported payment rails, currency-specific requirements, and settlement timing — each documented on its own dedicated flow page. **Offramp corridors** **Onramp corridors** Don't see a corridor you need? Reach out to your Saber account contact — new corridors are added on an ongoing basis. ## How Saber fits into your product Every transaction follows the same four-step sequence: Create a user record in Saber's system, giving you a user ID that every subsequent step is tied to. Complete identity verification for the user before any bank linkage or transaction can proceed. Link the user's bank account, required for offramp payouts and for certain onramp payment methods. Trigger the onramp or offramp transaction itself — settling to your Pool Account or directly to an external wallet. Alongside that flow, two things operate independently rather than as steps in it — you'll use both continuously, not just once per transaction: Your merchant-level stablecoin liquidity — held, topped up, and withdrawn independently of any single transaction. Where you monitor transactions and users, manage API keys and webhooks, and manage team access, at any time. ## Who Saber is for Saber is built for platforms that want to offer their users global payment functionality — fintechs, remittance apps, wallets, exchanges, and marketplaces — without becoming a licensed money-transmitter themselves or building settlement infrastructure from scratch. ## Where to go next Understand offramp flows, supported corridors, and transaction models. Understand onramp flows, supported corridors, and transaction models. Understand the user, KYC, and wallet layer underneath every flow. Set up signed requests and get your first API call working. # Available Flows Source: https://guides.saber.money/offramp/available-flows Compares Saber's three offramp flows — Sell, Pool Sell, and External Sell — covering fund flow, liquidity, refunds, fees, and quote handling. Saber supports three offramp flows, each trading off liquidity management, settlement timing, and operational complexity differently. Both USDT and USDC are supported across all three flows. Pre-funded from the **user's own wallet**. This is the simplest flow — the available balance in the user's wallet is used directly. Once the offramp is triggered, the crypto balance in the user's wallet is deducted, and equivalent fiat is deposited into the registered bank account. Crypto is deposited to the user's wallet on the preferred network. The offramp is initiated for the user. Stablecoin is deducted from the user's wallet, and fiat is delivered to the user's bank account. ```mermaid theme={null} sequenceDiagram participant M as Merchant participant U as User Wallet participant S as Saber participant B as User's Bank Account M->>U: Deposit crypto M->>S: Initiate Sell S->>U: Deduct stablecoin S->>B: Transfer fiat ``` Pre-funded from the **merchant's pool wallet**. Instead of maintaining individual user wallets and per-user crypto deposits, a single central wallet funds every user's offramp. Once triggered, the specified amount is deducted from the pool wallet and equivalent fiat is deposited into the user's bank account. Crypto is deposited to the Pool Wallet on the preferred network — this can be done in bulk, ahead of any individual transaction. The offramp is initiated for the user. Stablecoin is deducted from the pool wallet, and fiat is delivered to the user's bank account. ```mermaid theme={null} sequenceDiagram participant M as Merchant participant P as Pool Wallet participant S as Saber participant B as User's Bank Account M->>P: Bulk-deposit crypto M->>S: Initiate Pool Sell S->>P: Deduct stablecoin S->>B: Transfer fiat ``` This flow is functionally similar to Sell, but optimizes liquidity management at the merchant level through a centralized wallet — see [Pool Account Overview](/client/pool-account/overview). Ensure sufficient balance is available in the pool wallet before initiating the offramp transaction. Post-funded, settled after a quote-locked crypto deposit. An offramp quote is generated first — if it's favorable, it must be accepted within the quote's expiry window (generally \~30-60s). Once accepted, the transaction is initiated, and the merchant deposits the required stablecoin within the settlement expiry window (generally 1 hour). Once the deposit is confirmed by Saber, fiat is delivered to the user's bank account. Request a locked quote and accept it within the expiry window. The offramp transaction is initiated against the accepted quote, returning a deposit address. Deposit the required stablecoin to the given address within the settlement expiry, and confirm the on-chain funding. Once Saber confirms the deposit, fiat is delivered to the user's bank account. ```mermaid theme={null} sequenceDiagram participant M as Merchant participant S as Saber participant B as User's Bank Account M->>S: Request locked quote S-->>M: Quote (expires ~30-60s) M->>S: Accept quote / initiate transaction S-->>M: Deposit address (settlement expiry ~1hr) M->>S: Deposit crypto & confirm on-chain funding S->>B: Transfer fiat (after confirmation) ``` ## Comparison | Factor | Sell | Pool Sell (Recommended) | External Sell | | - | - | - | - | | Liquidity management | Required on a per-transaction basis to the user's wallet | Easy to manage from a centrally funded pool | Required on a per-transaction basis to the given wallet | | Crypto deposit | Required for each transaction | Pool wallet can be pre-funded to avoid deposits on each transaction | Required for each transaction | | Refund (on failure) | Refunded to the user's wallet | Refunded to the pool wallet | Refunded to the provided refund address, after network fee deduction | | Crypto settlement time | Depends on network transfer time | Instant, if the pool wallet is pre-funded | Depends on network transfer time | | Fee | Network fee applies on each deposit to the user's wallet | Network fee only applies on (bulk) deposits to the pool wallet | Network fee applies on each deposit | | Quote validity | Executed at market rate | Executed at market rate | Locked quote, if accepted, is valid for its expiry window | Locked-quote functionality will be extended to Sell and Pool Sell as well in a future release. Quote validity duration can be adjusted per business agreement — changing it does affect quote behavior, so confirm with your Saber account contact before relying on a non-default expiry. ## Related pages # BDT Offramp Source: https://guides.saber.money/offramp/currencies/bdt Sell stablecoin for Bangladeshi Taka, paid out directly to a Bangladeshi bank account. BDT offramp lets your users convert their stablecoin balance into Bangladeshi Taka, settled to a bank account in Bangladesh. Every offramp flow follows the same four steps: **Create User → KYC → Bank Account Linkage → Transaction.** Both USDT and USDC are supported for BDT offramp. ```http theme={null} POST /api/v2/user ``` ```json theme={null} { "email": "user@example.com", "phone": "+8801712345678" } ``` Response: ```json theme={null} { "success": true, "data": { "user_id": "550e8400-e29b-41d4-a716-446655440000" } } ``` Save `user_id` — every subsequent call needs it as the `X-User-Id` header. ```http theme={null} POST /api/v2/user/kyc ``` Headers: `X-User-Id: ` | Field | Description | Required | | - | - | - | | `legal_name` | Full legal name, as printed on the ID document | Yes | | `dob` | Date of birth (`YYYY-MM-DD`) | Yes | | `gender` | Gender (e.g. `M`, `F`) | No | | `nationality` | Nationality, ISO 3166-1 alpha-3 | Yes | | `birth_country` | Country of birth, ISO 3166-1 alpha-3 | No | | `country_iso3` | Country of residence, ISO 3166-1 alpha-3 | Yes | | `occupation` | Occupation (e.g. `SALARIED_EMPLOYEE`) | No | | `income` | Income amount, as a string | No | | `source_of_funds` | Source of funds (e.g. `SALARY`) | No | | `address` | Full address | Yes | | `line1` | Address line 1 | Yes | | `city` | City | Yes | | `state` | State or province | No | | `postal_code` | Postal code | No | | `poi` | Proof-of-identity document ID | Yes | | `poa` | Proof-of-address document ID | Yes | | `proof_of_identity` | Proof-of-identity document details — see below | Yes | | `proof_of_address` | Proof-of-address document details — see below | Yes | `proof_of_identity` and `proof_of_address` share the same shape: | Field | Description | Required | | - | - | - | | `document_type` | Type of document (e.g. `PASSPORT`, `NATIONAL_ID`) | Yes | | `issue_date` | Document issue date (`YYYY-MM-DD`) | No | | `expiry_date` | Document expiry date (`YYYY-MM-DD`) | Yes | | `issuing_country` | Country that issued the document, ISO 3166-1 alpha-3 | Yes | ```json theme={null} { "legal_name": "Rahim Uddin", "poa": "POA-123e4e334222323223", "poi": "POI-1233e3422223423141", "country_iso3": "BGD", "dob": "1990-05-01", "gender": "M", "occupation": "SALARIED_EMPLOYEE", "income": "100000", "address": "House 12, Road 5, Dhanmondi", "line1": "House 12, Road 5, Dhanmondi", "state": "Dhaka Division", "city": "Dhaka", "postal_code": "1209", "source_of_funds": "SALARY", "birth_country": "BGD", "nationality": "BGD", "proof_of_identity": { "document_type": "PASSPORT", "issue_date": "2019-01-02", "expiry_date": "2035-12-31", "issuing_country": "BGD" }, "proof_of_address": { "document_type": "NATIONAL_ID", "issue_date": "2018-03-04", "expiry_date": "2035-06-07", "issuing_country": "BGD" } } ``` Response: ```json theme={null} { "success": true, "data": {} } ``` Fields marked **Yes** are mandatory. Document `issue_date` is optional; `expiry_date` is mandatory for both `proof_of_identity` and `proof_of_address`. The example includes optional fields; all personal data is illustrative. `poa` and `poi` may reference the same document. `address` and `line1` should match. Saber may request supporting KYC documents at any time — these must be shared within 72 hours of the request. ```http theme={null} POST /api/v2/user/bank_account ``` Headers: `X-User-Id: ` ```json theme={null} { "account_number": "1234567890123", "account_holder_name": "Rahim Uddin", "bank_code": "095261733", "routing_type": "routing_number", "country": "BGD" } ``` | Field | Description | | - | - | | `account_number` | Bangladeshi bank account number | | `bank_code` | 9-digit bank routing code identifying the branch | Both fields are mandatory. Response: ```json theme={null} { "success": true, "data": { "bank_id": "550e8400-e29b-41d4-a716-446655440000", "account_number": "", "account_holder_name": "", "name": "", "validation_status": "PENDING", "created_at": "", "updated_at": "" } } ``` Save `bank_id` for the sell step. Two variants, both requiring a quote first — a **Live Quote** (informational, executes at market rate regardless) or a **Locked Quote** (rate held \~30-60s, supplied as `quote_id` in the sell request): Get an informational quote: ```http theme={null} GET /api/v2/wallet/s/quote?from_currency=USDT&to_currency=BDT&from_amount=100 ``` Then create the sell: ```http theme={null} POST /api/v2/wallet/transaction/pool/sell ``` ```json theme={null} { "source_id": "", "fiat_symbol": "BDT", "crypto_symbol": "USDT", "crypto_amount": 25, "payment_method": "bank_transfer", "is_first_party": true } ``` Generate a locked quote (expires in \~30-60s): ```http theme={null} GET /api/v2/wallet/w/quote?from_currency=USDT&to_currency=BDT&network=BSC&from_amount=10 ``` Save `id` from the response as `quote_id`, then create the sell: ```http theme={null} POST /api/v2/wallet/transaction/crypto/sell ``` ```json theme={null} { "payment_method": "bank_transfer", "network": "BSC", "destination_instrument_id": "", "quote_id": "", "sender_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8", "refund_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8" } ``` Then fund `deposit_crypto_address` on-chain within the settlement expiry, and confirm it: ```http theme={null} POST /api/v2/wallet/transaction/crypto/sell/{id}/confirm-onchain-funding ``` ```json theme={null} { "transaction_hash": "0x1234567890abcdef" } ``` This returns **202 Accepted**, not 200 — Saber verifies the deposit asynchronously. **Payment rail:** `bank_transfer` resolves automatically to the available local bank transfer rail in Bangladesh. ## Checking status ```http theme={null} GET /api/v2/wallet/transaction?transaction_type={POOL_SELL|EXTERNAL_SELL}&transaction_id={id} ``` | Transaction type | Possible `status` values | | - | - | | Pool Sell | `CREATED`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`, `APPROVAL_REQUIRED`, `SOURCE_INVALID`, `VERIFICATION_PENDING`, `REFUND_INITIATED`, `REFUND_COMPLETED` | | External Sell | `CREATED`, `PAYMENT_INITIATED`, `FUNDS_RECEIVED`, `COMPLETED`, `FAILED`, `REFUND_INITIATED`, `REFUND_COMPLETED`, `REFUND_FAILED` | Poll until the status reaches a terminal state, or configure a webhook URL to receive completion pushes instead — see [Order Lifecycle and Webhooks](/offramp/operations/lifecycle). ## Payout rails and settlement | Payout type | Destination | Maximum per payout | Processing | | - | - | - | - | | Bank payout | All banks | No corridor payout cap specified | Same day / T+1 | **Only bank-account payouts are currently supported.** Payouts to bank or payment-provider wallets (including bKash and Rocket) are not supported yet. **First-party payouts only:** the payout bank account must belong to the KYC-verified user. ## User limits and EDD Each user has a lifetime transaction limit (currently \$200,000 USD equivalent — this figure is suggestive and subject to change; confirm current limits with Saber). Once a user's limit is breached, no further transactions can be created until Enhanced Due Diligence (EDD) is completed to raise it. EDD is completed via the [EDD Hosted Widget](/users/limits-edd/widget), with a turnaround time of 2-3 working days. ## Related pages # EUR/GBP Offramp Source: https://guides.saber.money/offramp/currencies/eur-gbp Sell stablecoin for Euros or Pounds Sterling, paid out directly to a EUR (IBAN) or GBP (sort code) bank account. EUR/GBP offramp lets your users convert their stablecoin balance into Euros or Pounds Sterling, settled to a bank account. Every offramp flow follows the same four steps: **Create User → KYC → Bank Account Linkage → Transaction.** Both USDT and USDC are supported for EUR/GBP offramp. ```http theme={null} POST /api/v2/user ``` ```json theme={null} { "email": "user@example.com", "phone": "+491701234567" } ``` Response: ```json theme={null} { "success": true, "data": { "user_id": "550e8400-e29b-41d4-a716-446655440000" } } ``` Save `user_id` — every subsequent call needs it as the `X-User-Id` header. EUR/GBP supports two KYC methods: ```http theme={null} POST /api/v2/user/kyc ``` Headers: `X-User-Id: ` ```json theme={null} { "legal_name": "Hans Mueller", "poi": "", "poa": "", "dob": "1990-01-31", "email": "user@example.com", "phone": "+491701234567", "country_iso3": "DEU" } ``` Response: ```json theme={null} { "success": true, "data": {} } ``` Use the ISO3 code for the user's country (e.g. `DEU` for Germany, `GBR` for the UK). Saber may request supporting KYC documents at any time — these must be shared within 72 hours of the request. Redirect the user to a signed URL: ```javascript theme={null} const sigString = clientId + timestamp + "sdk" + userId const secret = HmacSHA256(sigString, clientSecret).toString().toUpperCase() // redirect to: https://app.saber.money/kyc?client_id=...&user_id=...×tamp=...&secret=...&redirect_url= ``` The URL is valid for **10 minutes** from generation. Append the optional `redirect_url` as the last query parameter (URL-encoded) to send the user back to your app when they exit the flow. See [KYC Hosted Widget](/user/kyc/widget) for the full parameter reference. There's no bank-code lookup step for EUR/GBP — banks are identified directly by IBAN (EUR) or sort code (GBP). ```http theme={null} POST /api/v2/user/bank_account ``` Headers: `X-User-Id: ` ```json theme={null} { "bank_iban": "DE89370400440532013000", "account_holder_name": "Hans Mueller", "country": "DEU" } ``` Mandatory: `bank_iban`, `account_holder_name`, `country`. ```json theme={null} { "account_holder_name": "James Smith", "account_number": "12345678", "bank_code": "040004", "country": "GBR" } ``` Mandatory: `account_holder_name`, `account_number` (max 8 digits), `bank_code` (sort code), `country`. Response: ```json theme={null} { "success": true, "data": { "bank_id": "550e8400-e29b-41d4-a716-446655440000", "account_number": "12345678", "iban": "DE89370400440532013000", "bic": null, "account_holder_name": "Hans Mueller", "name": null, "validation_status": "PENDING", "routing_type": "IBAN", "country": "DEU", "is_nre": false, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } } ``` This is the shared bank account response shape across corridors — `iban`/`bic` are populated for IBAN routing, `account_number` for GBP sort-code routing, with the unused identifier fields returned as `null`. Save `bank_id` for the sell step. Two variants, both requiring a quote first — a **Live Quote** (informational, executes at market rate regardless) or a **Locked Quote** (rate held \~30-60s, supplied as `quote_id` in the sell request): Get an informational quote: ```http theme={null} GET /api/v2/wallet/s/quote?from_currency=USDT&to_currency=EUR&from_amount=100 ``` Then create the sell: ```http theme={null} POST /api/v2/wallet/transaction/pool/sell ``` ```json theme={null} { "source_id": "", "fiat_symbol": "EUR", "crypto_symbol": "USDT", "crypto_amount": 25, "payment_method": "bank_transfer" } ``` Generate a locked quote (expires in \~30-60s): ```http theme={null} GET /api/v2/wallet/w/quote?from_currency=USDT&to_currency=GBP&network=BSC&from_amount=10 ``` Save `id` from the response as `quote_id`, then create the sell: ```http theme={null} POST /api/v2/wallet/transaction/crypto/sell ``` ```json theme={null} { "payment_method": "bank_transfer", "network": "BSC", "destination_instrument_id": "", "quote_id": "", "sender_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8", "refund_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8" } ``` Then fund `deposit_crypto_address` on-chain within the settlement expiry, and confirm it: ```http theme={null} POST /api/v2/wallet/transaction/crypto/sell/{id}/confirm-onchain-funding ``` ```json theme={null} { "transaction_hash": "0x1234567890abcdef" } ``` This returns **202 Accepted**, not 200 — Saber verifies the deposit asynchronously. **Payment rails:** `payment_method: bank_transfer` resolves internally based on currency and recipient bank: | Currency | Rail | | - | - | | GBP | Faster Payments | | EUR | SEPA Instant if supported by the recipient bank, otherwise standard SEPA | There are no cut-off times for EUR/GBP payouts. Standard SEPA transfers (where SEPA Instant isn't supported by the recipient bank) are subject to normal weekend and banking-holiday delays. ## Checking status ```http theme={null} GET /api/v2/wallet/transaction?transaction_type={POOL_SELL|EXTERNAL_SELL}&transaction_id={id} ``` | Transaction Type | Possible `status` values | | - | - | | Pool Sell | `CREATED`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`, `APPROVAL_REQUIRED`, `SOURCE_INVALID`, `VERIFICATION_PENDING`, `REFUND_INITIATED`, `REFUND_COMPLETED` | | External Sell | `CREATED`, `PAYMENT_INITIATED`, `FUNDS_RECEIVED`, `COMPLETED`, `FAILED`, `REFUND_INITIATED`, `REFUND_COMPLETED`, `REFUND_FAILED` | Poll until the status reaches a terminal state, or configure a webhook URL to receive completion pushes instead — see [Order Lifecycle and Webhooks](/offramp/operations/lifecycle). ## User limits and EDD Each user has a lifetime transaction limit (currently \$200,000 USD equivalent — this figure is suggestive and subject to change; confirm current limits with Saber). Once a user's limit is breached, no further transactions can be created until Enhanced Due Diligence (EDD) is completed to raise it. EDD is completed via the [EDD Hosted Widget](/users/limits-edd/widget), with a turnaround time of 2-3 working days. ## Related pages # IDR Offramp Source: https://guides.saber.money/offramp/currencies/idr Sell stablecoin for Indonesian Rupiah, paid out directly to an Indonesian bank account. IDR offramp lets your users convert their stablecoin balance into Indonesian Rupiah, settled to a bank account in Indonesia. Every offramp flow follows the same four steps: **Create User → KYC → Bank Account Linkage → Transaction.** Both USDT and USDC are supported for IDR offramp. ```http theme={null} POST /api/v2/user ``` ```json theme={null} { "email": "user@example.com", "phone": "+6281234567890" } ``` Response: ```json theme={null} { "success": true, "data": { "user_id": "550e8400-e29b-41d4-a716-446655440000" } } ``` Save `user_id` — every subsequent call needs it as the `X-User-Id` header. ```http theme={null} POST /api/v2/user/kyc ``` Headers: `X-User-Id: ` | Field | Description | Required | | - | - | - | | `legal_name` | Full legal name, as printed on the ID document | Yes | | `dob` | Date of birth (`YYYY-MM-DD`) | Yes | | `gender` | Gender (e.g. `M`, `F`) | No | | `nationality` | Nationality, ISO 3166-1 alpha-3 | Yes | | `birth_country` | Country of birth, ISO 3166-1 alpha-3 | No | | `country_iso3` | Country of residence, ISO 3166-1 alpha-3 | Yes | | `occupation` | Occupation (e.g. `SALARIED_EMPLOYEE`) | No | | `income` | Income amount, as a string | No | | `source_of_funds` | Source of funds (e.g. `SALARY`) | No | | `address` | Full address | Yes | | `line1` | Address line 1 | Yes | | `city` | City | Yes | | `state` | State or province | No | | `postal_code` | Postal code | No | | `poi` | Proof-of-identity document ID | Yes | | `poa` | Proof-of-address document ID | Yes | | `proof_of_identity` | Proof-of-identity document details — see below | Yes | | `proof_of_address` | Proof-of-address document details — see below | Yes | `proof_of_identity` and `proof_of_address` share the same shape: | Field | Description | Required | | - | - | - | | `document_type` | Type of document (e.g. `PASSPORT`, `NATIONAL_ID`) | Yes | | `issue_date` | Document issue date (`YYYY-MM-DD`) | No | | `expiry_date` | Document expiry date (`YYYY-MM-DD`) | Yes | | `issuing_country` | Country that issued the document, ISO 3166-1 alpha-3 | Yes | ```json theme={null} { "legal_name": "Budi Santoso", "poa": "POA-123e4e334222323223", "poi": "POI-1233e3422223423141", "country_iso3": "IDN", "dob": "1990-05-01", "gender": "M", "occupation": "SALARIED_EMPLOYEE", "income": "100000", "address": "Jl. Sudirman No. 10", "line1": "Jl. Sudirman No. 10", "state": "DKI Jakarta", "city": "Jakarta", "postal_code": "10220", "source_of_funds": "SALARY", "birth_country": "IDN", "nationality": "IDN", "proof_of_identity": { "document_type": "PASSPORT", "issue_date": "2019-01-02", "expiry_date": "2035-12-31", "issuing_country": "IDN" }, "proof_of_address": { "document_type": "NATIONAL_ID", "issue_date": "2018-03-04", "expiry_date": "2035-06-07", "issuing_country": "IDN" } } ``` Response: ```json theme={null} { "success": true, "data": {} } ``` Fields marked **Yes** are mandatory. Document `issue_date` is optional; `expiry_date` is mandatory for both `proof_of_identity` and `proof_of_address`. The example includes optional fields; all personal data is illustrative. `poa` and `poi` may reference the same document. `address` and `line1` should match. Saber may request supporting KYC documents at any time — these must be shared within 72 hours of the request. First, fetch the list of supported banks: ```http theme={null} GET /api/v2/user/bank_account/financial_institutions/IDN ``` Headers: `X-User-Id: ` Response: ```json theme={null} { "success": true, "data": [ { "id": "a48f59ce-6ea2-4beb-9895-1d8ed1d8cc39", "name": "ANZ Indonesia", "code": "IDN-ANZ00000", "type": "BANK" } ] } ``` | Field | Description | | - | - | | `id` | Saber identifier for the financial institution | | `name` | Display name of the bank | | `code` | Bank code — pass this as-is into `bank_code` below | | `type` | Institution type (e.g. `BANK`) | Then link the bank account: ```http theme={null} POST /api/v2/user/bank_account ``` Headers: `X-User-Id: ` ```json theme={null} { "account_holder_name": "Budi Santoso", "bank_code": "IDN-ANZ00000", "account_number": "1234567890", "bank_bic": "ANZBIDJX", "routing_type": "bank_code", "country": "IDN" } ``` | Field | Description | | - | - | | `account_holder_name` | Name on the bank account — must match the user's KYC `legal_name` | | `bank_code` | `code` from the supported banks list above | | `account_number` | Indonesian bank account number (no spaces) | | `bank_bic` | SWIFT / BIC code of the bank | | `routing_type` | Must be `bank_code` | | `country` | `IDN` | Response: ```json theme={null} { "success": true, "data": { "bank_id": "550e8400-e29b-41d4-a716-446655440000", "account_number": "1234567890", "account_holder_name": "Budi Santoso", "name": null, "bic": null, "validation_status": "PENDING", "country": "IDN", "is_nre": false, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } } ``` This is the shared bank account response shape across corridors — fields not applicable to bank-code routing (e.g. `bic`) are returned as `null`. Save `bank_id` for the sell step. IDR supports **Pool Sell and External Sell**. Both flows require a quote. See [Payout rails and settlement](#payout-rails-and-settlement) for amount limits and processing times. Get an informational quote: ```http theme={null} GET /api/v2/wallet/s/quote?from_currency=USDT&to_currency=IDR&from_amount=100 ``` Then create the sell: ```http theme={null} POST /api/v2/wallet/transaction/pool/sell ``` ```json theme={null} { "source_id": "", "fiat_symbol": "IDR", "crypto_symbol": "USDT", "crypto_amount": 25, "payment_method": "bank_transfer", "is_first_party": true } ``` Generate a locked quote (expires in \~30-60s): ```http theme={null} GET /api/v2/wallet/w/quote?from_currency=USDT&to_currency=IDR&network=BSC&from_amount=10 ``` Save `id` from the response as `quote_id`, then create the sell: ```http theme={null} POST /api/v2/wallet/transaction/crypto/sell ``` ```json theme={null} { "payment_method": "bank_transfer", "network": "BSC", "destination_instrument_id": "", "quote_id": "", "sender_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8", "refund_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8" } ``` Then fund `deposit_crypto_address` on-chain within the settlement expiry, and confirm it: ```http theme={null} POST /api/v2/wallet/transaction/crypto/sell/{id}/confirm-onchain-funding ``` ```json theme={null} { "transaction_hash": "0x1234567890abcdef" } ``` This returns **202 Accepted**, not 200 — Saber verifies the deposit asynchronously. **Payment rail:** `bank_transfer` resolves to the local Indonesian rail based on the amount and beneficiary bank. ## Checking status ```http theme={null} GET /api/v2/wallet/transaction?transaction_type={POOL_SELL|EXTERNAL_SELL}&transaction_id={id} ``` | Transaction type | Possible `status` values | | - | - | | Pool Sell | `CREATED`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`, `APPROVAL_REQUIRED`, `SOURCE_INVALID`, `VERIFICATION_PENDING`, `REFUND_INITIATED`, `REFUND_COMPLETED` | | External Sell | `CREATED`, `PAYMENT_INITIATED`, `FUNDS_RECEIVED`, `COMPLETED`, `FAILED`, `REFUND_INITIATED`, `REFUND_COMPLETED`, `REFUND_FAILED` | Poll until the status reaches a terminal state, or configure a webhook URL to receive completion pushes instead — see [Order Lifecycle and Webhooks](/offramp/operations/lifecycle). ## Payout rails and settlement | Payout type | Destination | Maximum per payout | Processing | | - | - | - | - | | Bank payout | All banks | IDR 100,000,000 | Real time | **Minimum payout:** USD 1 equivalent. **Only bank-account payouts are currently supported.** Payouts to bank or payment-provider wallets (including OVO, GoPay, and LinkAja) are not supported yet. **First-party payouts only:** the payout bank account must belong to the KYC-verified user. Due to Indonesian banking cut-offs, fiat withdrawals after **22:59 (UTC+7)** may be processed at **07:00 (UTC+7)** the following banking day. ## User limits and EDD Each user has a lifetime transaction limit (currently \$200,000 USD equivalent — this figure is suggestive and subject to change; confirm current limits with Saber). Once a user's limit is breached, no further transactions can be created until Enhanced Due Diligence (EDD) is completed to raise it. EDD is completed via the [EDD Hosted Widget](/users/limits-edd/widget), with a turnaround time of 2-3 working days. ## Related pages # INR Offramp Source: https://guides.saber.money/offramp/currencies/inr Sell stablecoin for Indian Rupees, paid out directly to an Indian bank account via IMPS, NEFT, or RTGS. INR offramp lets your users convert their stablecoin balance into Indian Rupees, settled to a bank account in India. Every offramp flow follows the same four steps: **Create User → KYC → Bank Account Linkage → Transaction.** Both USDT and USDC are supported for INR offramp. ```http theme={null} POST /api/v2/user ``` ```json theme={null} { "email": "user@example.com", "phone": "+919876543210" } ``` Response: ```json theme={null} { "success": true, "data": { "user_id": "550e8400-e29b-41d4-a716-446655440000" } } ``` Save `user_id` — every subsequent call needs it as the `X-User-Id` header. INR supports two KYC methods: ```http theme={null} POST /api/v2/user/kyc ``` Headers: `X-User-Id: ` ```json theme={null} { "legal_name": "John Doe", "poi": "DWSPP6492L", "poa": "500100121232", "dob": "31-01-2000", "email": "user@example.com", "phone": "+919876543210", "country_iso3": "IND", "is_nri": false } ``` Response: ```json theme={null} { "success": true, "data": {} } ``` `poi` = PAN, `poa` = Aadhaar. Set `is_nri: true` if the user is a Non-Resident Indian. TDS is not deducted from INR sell payouts for NRI users; it is deducted for third-party payouts to Indian residents. A user whose `country` is India cannot be marked `is_nri: true`. Saber may request supporting KYC documents for a KYC-Sharing user at any time — these must be shared within 72 hours of the request. Redirect the user to a signed URL: ```javascript theme={null} const sigString = clientId + timestamp + "sdk" + userId const secret = HmacSHA256(sigString, clientSecret).toString().toUpperCase() // redirect to: https://app.saber.money/kyc?client_id=...&user_id=...×tamp=...&secret=...&redirect_url= ``` The URL is valid for **10 minutes** from generation. Append the optional `redirect_url` as the last query parameter (URL-encoded) to send the user back to your app when they exit the flow. See [KYC Hosted Widget](/user/kyc/widget) for the full parameter reference. ```http theme={null} POST /api/v2/user/bank_account ``` Headers: `X-User-Id: ` ```json theme={null} { "account_number": "12345678901234", "bank_code": "HDFC0001234", "country": "IND" } ``` For India, only `account_number` and `bank_code` (IFSC) are required — `account_holder_name` is not required at this step; the account holder name is fetched from the bank during penny-drop and matched against the user's KYC `legal_name` instead. Response: ```json theme={null} { "success": true, "data": { "bank_id": "550e8400-e29b-41d4-a716-446655440000", "account_number": "12XXXXX1234", "ifsc_code": "HDFC0001234", "account_holder_name": "John Doe", "account_type": "SAVINGS", "name": "HDFC Bank", "iban": null, "bic": null, "validation_status": "PENDING", "routing_type": "IFSC", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "message": "" } } ``` `created_at`/`updated_at` here are ISO 8601 date strings — unlike wallet-transaction endpoints, which use epoch milliseconds. Save `bank_id` for the sell step. Indian bank accounts go to `validation_status: PENDING` and are validated asynchronously via **penny-drop**: Saber sends ₹1 to the account to confirm it exists, then fuzzy-matches the fetched account holder name against the user's KYC `legal_name`. The account is only marked `APPROVED` if the names match; otherwise it's `NAME_REJECTED`. For NRE accounts, `account_holder_name` is returned empty. This is expected — the penny-drop response for an NRE account does not include the account holder name, it only confirms whether the account is NRE. A user can have up to **4 `APPROVED` bank accounts** at any point in time (error `304012` when exceeded). Min ₹100 per transaction; max depends on the user's assigned limits (see [User Limits & EDD](#user-limits-and-edd) below). `payment_method: bank_transfer` resolves internally to one of: * **IMPS** — for amounts from ₹100 up to ₹5,00,000. Instant, no cut-off times. * **NEFT / RTGS** — for amounts above ₹5,00,000. Manual processing, \~12 hour SLA, operates 10 AM–6 PM IST. Support over weekends and public holidays may be limited. Two variants, both requiring a quote first — a **Live Quote** (informational, executes at market rate regardless) or a **Locked Quote** (rate held \~30-60s, supplied as `quote_id` in the sell request): Get an informational quote: ```http theme={null} GET /api/v2/wallet/s/quote?from_currency=USDT&to_currency=INR&from_amount=100 ``` ```json theme={null} { "success": true, "data": { "from_currency": "USDT", "to_currency": "INR", "from_amount": 100, "pre_fee_to_amount": 8650, "to_amount": 8600, "base_price": 86.5, "final_price": 86, "total_fee": 50, "fee_currency": "INR", "fee_breakup": { "platform_fee": 20, "network_fee": 0, "client_fee": 10, "discount": 0, "tax_on_fee": 20, "tds": 0 } } } ``` Then create the sell: ```http theme={null} POST /api/v2/wallet/transaction/pool/sell ``` ```json theme={null} { "source_id": "", "fiat_symbol": "INR", "crypto_symbol": "USDT", "crypto_amount": 25, "payment_method": "bank_transfer" } ``` Response: ```json theme={null} { "success": true, "data": { "id": "07825d5d-1879-4cc7-a87f-f5ed45ee391b", "transaction_type": "POOL_SELL", "status": "PROCESSING", "fiat_amount": 2160.5, "crypto_amount": 25, "fiat_symbol": "INR", "crypto_symbol": "USDT", "exchange_rate": 86.42, "failure_code": "", "failure_desc": "", "created_at": 1774343695000, "user_id": "65d246fc-6a5b-43d7-89bf-944e95ff2279" } } ``` Generate a locked quote (expires in \~30-60s): ```http theme={null} GET /api/v2/wallet/w/quote?from_currency=USDT&to_currency=INR&network=BSC&from_amount=10 ``` ```json theme={null} { "success": true, "data": { "from_currency": "USDT", "to_currency": "INR", "network": "BSC", "from_amount": 10, "pre_fee_to_amount": 863.2, "to_amount": 863.2, "base_price": 86.32, "final_price": 86.32, "total_fee": 0, "fee_currency": "INR", "fee_breakup": { "platform_fee": 0, "network_fee": 0, "client_fee": 0, "discount": 0, "tax_on_fee": 0, "tds": 0 }, "other_details": { "fiat_settlement_time": { "min": 0, "max": 1, "unit": "HOURS" } }, "transaction_type": "EXTERNAL_SELL", "id": "0c6ad6d3-6902-4a27-a30f-c176456a0835", "expiry": "2026-03-25T09:42:14.321113044Z", "settlement_expiry": "2026-03-25T10:41:44.321113044Z", "created_at": "2026-03-25T09:41:44.318189513Z", "updated_at": "2026-03-25T09:41:44.318189595Z" } } ``` Save `id` from the response as `quote_id`, then create the sell: ```http theme={null} POST /api/v2/wallet/transaction/crypto/sell ``` ```json theme={null} { "payment_method": "bank_transfer", "network": "BSC", "destination_instrument_id": "", "quote_id": "", "sender_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8", "refund_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8" } ``` Response: ```json theme={null} { "success": true, "data": { "id": "01992e48-db7d-7c55-bb87-6f4e1abf5fe3", "status": "CREATED", "fiat_symbol": "INR", "crypto_symbol": "USDT", "fiat_amount": 863.2, "crypto_amount": 10, "payment_method": "bank_transfer", "deposit_crypto_address": "0xabcdef1234567890abcdef1234567890abcdef12", "deposit_network": "BSC", "destination_instrument_id": "a4680ba9-5ac6-4629-9493-2a88b160cd33", "refund_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8", "refund_network": "", "refund_tag": "", "sender_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8", "created_at": 1757418150839, "updated_at": 1757418150841 } } ``` Then fund `deposit_crypto_address` on-chain within the settlement expiry, and confirm it: ```http theme={null} POST /api/v2/wallet/transaction/crypto/sell/{id}/confirm-onchain-funding ``` ```json theme={null} { "transaction_hash": "0x1234567890abcdef" } ``` This returns **202 Accepted**, not 200 — Saber verifies the deposit asynchronously. ```json theme={null} { "success": true, "data": { "id": "01992e48-db7d-7c55-bb87-6f4e1abf5fe3", "status": "CREATED", "transaction_hash": "0x1234567890abcdef", "fiat_symbol": "INR", "crypto_symbol": "USDT" } } ``` ## Payouts to NRE accounts Paying out to an NRE (Non-Resident External) bank account uses the same transaction flow with a few additional conditions — see [Traditional Rails](/offramp/currencies/inr-traditional). ## Checking status ```http theme={null} GET /api/v2/wallet/transaction?transaction_type={POOL_SELL|EXTERNAL_SELL}&transaction_id={id} ``` | Transaction Type | Possible `status` values | | - | - | | Pool Sell | `CREATED`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`, `APPROVAL_REQUIRED`, `SOURCE_INVALID`, `VERIFICATION_PENDING`, `REFUND_INITIATED`, `REFUND_COMPLETED` | | External Sell | `CREATED`, `PAYMENT_INITIATED`, `FUNDS_RECEIVED`, `COMPLETED`, `FAILED`, `REFUND_INITIATED`, `REFUND_COMPLETED`, `REFUND_FAILED` | Pool Sell: ```json theme={null} { "success": true, "data": { "transaction_type": "POOL_SELL", "status": "COMPLETED", "created_at": 1742207345000, "updated_at": 1742207346000, "id": "0195a3a6-62c2-766b-b984-7f9f1754b3f1", "crypto_symbol": "USDT", "fiat_symbol": "INR", "exchange_rate": 84.56973573047619, "crypto_amount": 21, "fiat_amount": 1775.96445034, "bank_transaction_id": "1234", "source_id": "" } } ``` External Sell: ```json theme={null} { "success": true, "data": { "id": "01992e48-db7d-7c55-bb87-6f4e1abf5fe3", "transaction_type": "EXTERNAL_SELL", "status": "FUNDS_RECEIVED", "created_at": 1757418150839, "updated_at": 1757418150841, "crypto_symbol": "USDT", "fiat_symbol": "INR", "crypto_amount": 0.695712, "fiat_amount": 55.38, "deposit_crypto_address": "0xabcdef1234567890abcdef1234567890abcdef12", "deposit_network": "BSC", "destination_instrument_id": "a4680ba9-5ac6-4629-9493-2a88b160cd33", "bank_transaction_id": "", "source_id": "" } } ``` Poll until the status reaches a terminal state, or configure a webhook URL to receive completion pushes instead — see [Order Lifecycle and Webhooks](/offramp/operations/lifecycle). ## Payout rails and settlement The supplied corridor table lists **all banks**, **no corridor-level bank payout cap**, and **real-time** bank payouts. These are general rail capabilities, not guarantees for every amount or integration. Saber's documented IMPS, NEFT and RTGS routing, banking hours, processing SLAs, and user limits above continue to apply. ## User limits and EDD Each user has a lifetime transaction limit (currently \$200,000 USD equivalent — this figure is suggestive and subject to change; confirm current limits with Saber). Once a user's limit is breached, no further transactions can be created until Enhanced Due Diligence (EDD) is completed to raise it. EDD is completed via the [EDD Hosted Widget](/users/limits-edd/widget), with a turnaround time of 2-3 working days. ## Related pages # Traditional Rails Source: https://guides.saber.money/offramp/currencies/inr-traditional Pay out INR to first-party NRE bank accounts in India over traditional banking rails. Saber supports INR payouts to **NRE (Non-Resident External) bank accounts** in India over traditional banking rails — funds are routed through compliant banking infrastructure rather than on-chain settlement. The payout flow is otherwise the same as a standard [INR Offramp](/offramp/currencies/inr) transaction, with a few conditions specific to NRE accounts. Both USDT and USDC are supported for NRE payouts. ## Key considerations NRE payouts are currently supported only for **first-party transactions** — third-party payouts to savings accounts aren't supported yet. * **KYC Sharing only** — this flow only works for users onboarded via KYC Sharing; other KYC methods aren't currently compatible. See [KYC Sharing](/user/kyc/sharing). * **`is_nre` flag** — only bank accounts with `is_nre: true` and `status: APPROVED` are eligible. This flag is available on the Get User API (within the bank object) and the Get Bank Account API. * **Address required** — the user's address must be populated during KYC for NRE eligibility. ## Limits Same min (₹100) and max (per assigned limits) as standard INR payouts — see [INR Offramp](/offramp/currencies/inr). ## Getting pricing for NRE payouts Call the **Sell Quote API** with the bank account UUID in the optional `payment_instrument_id` field: * **NRE account** → NRE (traditional-rails) pricing is returned. * **Non-NRE account** → standard stablecoin pricing is returned. This lets you use a single quote flow for all users, with pricing resolved automatically by destination account type. ```http theme={null} GET /api/v2/wallet/s/quote?from_currency=USDT&to_currency=INR&from_amount=100&payment_instrument_id= ``` ## Initiating an NRE payout Use the existing transaction APIs — `SELL`, `POOL SELL`, or `EXTERNAL SELL`. ```json theme={null} { "source_id": "", "is_first_party": true } ``` ```json theme={null} { "destination_instrument_id": "", "is_first_party": true } ``` `source_id` / `destination_instrument_id` must be the bank account ID of an approved NRE account, and `is_first_party` must be `true`. ## Transaction lifecycle Unchanged — status tracking, webhooks, error handling, and reconciliation follow the same [Order Lifecycle and Webhooks](/offramp/operations/lifecycle) as any other INR offramp transaction. ## Related pages # LKR Offramp Source: https://guides.saber.money/offramp/currencies/lkr Sell stablecoin for Sri Lankan Rupees, paid out directly to a Sri Lanka bank account. LKR offramp lets your users convert their stablecoin balance into Sri Lankan Rupees, settled to a bank account in Sri Lanka. Every offramp flow follows the same four steps: **Create User → KYC → Bank Account Linkage → Transaction.** Token/network availability should be confirmed for this corridor. ```http theme={null} POST /api/v2/user ``` ```json theme={null} { "email": "user@example.com", "phone": "+94771234567" } ``` Response: ```json theme={null} { "success": true, "data": { "user_id": "550e8400-e29b-41d4-a716-446655440000" } } ``` Save `user_id` — every subsequent call needs it as the `X-User-Id` header. ```http theme={null} POST /api/v2/user/kyc ``` Headers: `X-User-Id: ` | Field | Description | Required | | - | - | - | | `legal_name` | Full legal name, as printed on the ID document | Yes | | `dob` | Date of birth (`YYYY-MM-DD`) | Yes | | `gender` | Gender (e.g. `M`, `F`) | No | | `nationality` | Nationality, ISO 3166-1 alpha-3 | Yes | | `birth_country` | Country of birth, ISO 3166-1 alpha-3 | No | | `country_iso3` | Country of residence, ISO 3166-1 alpha-3 | Yes | | `occupation` | Occupation (e.g. `SALARIED_EMPLOYEE`) | No | | `income` | Income amount, as a string | No | | `source_of_funds` | Source of funds (e.g. `SALARY`) | No | | `address` | Full address | Yes | | `line1` | Address line 1 | Yes | | `city` | City | Yes | | `state` | State or province | No | | `postal_code` | Postal code | No | | `poi` | Proof-of-identity document ID | Yes | | `poa` | Proof-of-address document ID | Yes | | `proof_of_identity` | Proof-of-identity document details — see below | Yes | | `proof_of_address` | Proof-of-address document details — see below | Yes | `proof_of_identity` and `proof_of_address` share the same shape: | Field | Description | Required | | - | - | - | | `document_type` | Type of document (e.g. `PASSPORT`, `NATIONAL_ID`) | Yes | | `issue_date` | Document issue date (`YYYY-MM-DD`) | No | | `expiry_date` | Document expiry date (`YYYY-MM-DD`) | Yes | | `issuing_country` | Country that issued the document, ISO 3166-1 alpha-3 | Yes | ```json theme={null} { "legal_name": "Nimal Perera", "poa": "POA-123e4e334222323223", "poi": "POI-1233e3422223423141", "country_iso3": "LKA", "dob": "1990-05-01", "gender": "M", "occupation": "SALARIED_EMPLOYEE", "income": "100000", "address": "25 Galle Road, Colombo 03", "line1": "25 Galle Road, Colombo 03", "state": "Western Province", "city": "Colombo", "postal_code": "00300", "source_of_funds": "SALARY", "birth_country": "LKA", "nationality": "LKA", "proof_of_identity": { "document_type": "PASSPORT", "issue_date": "2019-01-02", "expiry_date": "2035-12-31", "issuing_country": "LKA" }, "proof_of_address": { "document_type": "NATIONAL_ID", "issue_date": "2018-03-04", "expiry_date": "2035-06-07", "issuing_country": "LKA" } } ``` Response: ```json theme={null} { "success": true, "data": {} } ``` Fields marked **Yes** are mandatory. Document `issue_date` is optional; `expiry_date` is mandatory for both `proof_of_identity` and `proof_of_address`. The example includes optional fields; all personal data is illustrative. `poa` and `poi` may reference the same document. `address` and `line1` should match. ```http theme={null} POST /api/v2/user/bank_account ``` Headers: `X-User-Id: ` ```json theme={null} { "account_holder_name": "Nimal Perera", "account_number": "123456789012", "bank_bic": "BCEYLKLX", "routing_type": "bank_code", "country": "LKA" } ``` | Field | Description | | - | - | | `account_holder_name` | Must match KYC legal name; first-party payouts only | | `account_number` | Beneficiary bank account number | | `bank_bic` | SWIFT / BIC code | | `routing_type` | `bank_code` | | `country` | `LKA` | The account number is illustrative. Confirm whether the API also requires a separate `bank_code` field; its value format has not been supplied. Save the returned `bank_id` for the sell request. Two variants, both requiring a quote first — a **Live Quote** (informational, executes at market rate regardless) or a **Locked Quote** (rate held \~30-60s, supplied as `quote_id` in the sell request): Get an informational quote: ```http theme={null} GET /api/v2/wallet/s/quote?from_currency=USDT&to_currency=LKR&from_amount=100 ``` Then create the sell: ```http theme={null} POST /api/v2/wallet/transaction/pool/sell ``` ```json theme={null} { "source_id": "", "fiat_symbol": "LKR", "crypto_symbol": "USDT", "crypto_amount": 25, "payment_method": "bank_transfer", "is_first_party": true } ``` Generate a locked quote (expires in \~30-60s): ```http theme={null} GET /api/v2/wallet/w/quote?from_currency=USDT&to_currency=LKR&network=BSC&from_amount=10 ``` Save `id` from the response as `quote_id`, then create the sell: ```http theme={null} POST /api/v2/wallet/transaction/crypto/sell ``` ```json theme={null} { "payment_method": "bank_transfer", "network": "BSC", "destination_instrument_id": "", "quote_id": "", "sender_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8", "refund_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8" } ``` Then fund `deposit_crypto_address` on-chain within the settlement expiry, and confirm it: ```http theme={null} POST /api/v2/wallet/transaction/crypto/sell/{id}/confirm-onchain-funding ``` ```json theme={null} { "transaction_hash": "0x1234567890abcdef" } ``` This returns **202 Accepted**, not 200 — Saber verifies the deposit asynchronously. **Payment rail:** `bank_transfer`. See [Payout rails and settlement](#payout-rails-and-settlement) for amount limits and processing times. ## Checking status ```http theme={null} GET /api/v2/wallet/transaction?transaction_type={POOL_SELL|EXTERNAL_SELL}&transaction_id={id} ``` | Transaction type | Possible `status` values | | - | - | | Pool Sell | `CREATED`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`, `APPROVAL_REQUIRED`, `SOURCE_INVALID`, `VERIFICATION_PENDING`, `REFUND_INITIATED`, `REFUND_COMPLETED` | | External Sell | `CREATED`, `PAYMENT_INITIATED`, `FUNDS_RECEIVED`, `COMPLETED`, `FAILED`, `REFUND_INITIATED`, `REFUND_COMPLETED`, `REFUND_FAILED` | Poll until the status reaches a terminal state, or configure a webhook URL to receive completion pushes instead — see [Order Lifecycle and Webhooks](/offramp/operations/lifecycle). ## Payout rails and settlement | Payout type | Destination | Maximum per payout | Processing | | - | - | - | - | | Bank payout | All banks | LKR 1,000,000 | Real time | **Minimum payout:** USD 1 equivalent. **Only bank-account payouts are currently supported.** Payouts to bank or payment-provider wallets are not supported yet. **First-party payouts only:** the payout bank account must belong to the KYC-verified user. Processing is subject to banking and operational conditions. The sample API inputs are illustrative and do not override the payout limits. ## User limits and EDD Separate user-level limits may apply. Use [Get User Limits](/api-reference/limits/get-user-limits) for current user-level limits. EDD is completed via the [EDD Hosted Widget](/users/limits-edd/widget), with a turnaround time of 2-3 working days. ## Related pages # NPR Offramp Source: https://guides.saber.money/offramp/currencies/npr Sell stablecoin for Nepalese Rupees, paid out directly to a Nepal bank account. NPR offramp lets your users convert their stablecoin balance into Nepalese Rupees, settled to a bank account in Nepal. Every offramp flow follows the same four steps: **Create User → KYC → Bank Account Linkage → Transaction.** Token/network availability should be confirmed for this corridor. ```http theme={null} POST /api/v2/user ``` ```json theme={null} { "email": "user@example.com", "phone": "+9779841234567" } ``` Response: ```json theme={null} { "success": true, "data": { "user_id": "550e8400-e29b-41d4-a716-446655440000" } } ``` Save `user_id` — every subsequent call needs it as the `X-User-Id` header. ```http theme={null} POST /api/v2/user/kyc ``` Headers: `X-User-Id: ` | Field | Description | Required | | - | - | - | | `legal_name` | Full legal name, as printed on the ID document | Yes | | `dob` | Date of birth (`YYYY-MM-DD`) | Yes | | `gender` | Gender (e.g. `M`, `F`) | No | | `nationality` | Nationality, ISO 3166-1 alpha-3 | Yes | | `birth_country` | Country of birth, ISO 3166-1 alpha-3 | No | | `country_iso3` | Country of residence, ISO 3166-1 alpha-3 | Yes | | `occupation` | Occupation (e.g. `SALARIED_EMPLOYEE`) | No | | `income` | Income amount, as a string | No | | `source_of_funds` | Source of funds (e.g. `SALARY`) | No | | `address` | Full address | Yes | | `line1` | Address line 1 | Yes | | `city` | City | Yes | | `state` | State or province | No | | `postal_code` | Postal code | No | | `poi` | Proof-of-identity document ID | Yes | | `poa` | Proof-of-address document ID | Yes | | `proof_of_identity` | Proof-of-identity document details — see below | Yes | | `proof_of_address` | Proof-of-address document details — see below | Yes | `proof_of_identity` and `proof_of_address` share the same shape: | Field | Description | Required | | - | - | - | | `document_type` | Type of document (e.g. `PASSPORT`, `NATIONAL_ID`) | Yes | | `issue_date` | Document issue date (`YYYY-MM-DD`) | No | | `expiry_date` | Document expiry date (`YYYY-MM-DD`) | Yes | | `issuing_country` | Country that issued the document, ISO 3166-1 alpha-3 | Yes | ```json theme={null} { "legal_name": "Suman Shrestha", "poa": "POA-123e4e334222323223", "poi": "POI-1233e3422223423141", "country_iso3": "NPL", "dob": "1990-05-01", "gender": "M", "occupation": "SALARIED_EMPLOYEE", "income": "100000", "address": "Ward 10, New Baneshwor, Kathmandu", "line1": "Ward 10, New Baneshwor, Kathmandu", "state": "Bagmati Province", "city": "Kathmandu", "postal_code": "44600", "source_of_funds": "SALARY", "birth_country": "NPL", "nationality": "NPL", "proof_of_identity": { "document_type": "PASSPORT", "issue_date": "2019-01-02", "expiry_date": "2035-12-31", "issuing_country": "NPL" }, "proof_of_address": { "document_type": "NATIONAL_ID", "issue_date": "2018-03-04", "expiry_date": "2035-06-07", "issuing_country": "NPL" } } ``` Response: ```json theme={null} { "success": true, "data": {} } ``` Fields marked **Yes** are mandatory. Document `issue_date` is optional; `expiry_date` is mandatory for both `proof_of_identity` and `proof_of_address`. The example includes optional fields; all personal data is illustrative. `poa` and `poi` may reference the same document. `address` and `line1` should match. ```http theme={null} POST /api/v2/user/bank_account ``` Headers: `X-User-Id: ` ```json theme={null} { "account_holder_name": "Suman Shrestha", "account_number": "123456789012", "bank_bic": "NIBLNPKT", "routing_type": "bank_code", "country": "NPL" } ``` | Field | Description | | - | - | | `account_holder_name` | Must match KYC legal name; first-party payouts only | | `account_number` | Beneficiary bank account number | | `bank_bic` | SWIFT / BIC code | | `routing_type` | `bank_code` | | `country` | `NPL` | The account number is illustrative. Confirm whether the API also requires a separate `bank_code` field; its value format has not been supplied. Save the returned `bank_id` for the sell request. Two variants, both requiring a quote first — a **Live Quote** (informational, executes at market rate regardless) or a **Locked Quote** (rate held \~30-60s, supplied as `quote_id` in the sell request): Get an informational quote: ```http theme={null} GET /api/v2/wallet/s/quote?from_currency=USDT&to_currency=NPR&from_amount=100 ``` Then create the sell: ```http theme={null} POST /api/v2/wallet/transaction/pool/sell ``` ```json theme={null} { "source_id": "", "fiat_symbol": "NPR", "crypto_symbol": "USDT", "crypto_amount": 25, "payment_method": "bank_transfer", "is_first_party": true } ``` Generate a locked quote (expires in \~30-60s): ```http theme={null} GET /api/v2/wallet/w/quote?from_currency=USDT&to_currency=NPR&network=BSC&from_amount=10 ``` Save `id` from the response as `quote_id`, then create the sell: ```http theme={null} POST /api/v2/wallet/transaction/crypto/sell ``` ```json theme={null} { "payment_method": "bank_transfer", "network": "BSC", "destination_instrument_id": "", "quote_id": "", "sender_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8", "refund_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8" } ``` Then fund `deposit_crypto_address` on-chain within the settlement expiry, and confirm it: ```http theme={null} POST /api/v2/wallet/transaction/crypto/sell/{id}/confirm-onchain-funding ``` ```json theme={null} { "transaction_hash": "0x1234567890abcdef" } ``` This returns **202 Accepted**, not 200 — Saber verifies the deposit asynchronously. **Payment rail:** `bank_transfer`. See [Payout rails and settlement](#payout-rails-and-settlement) for amount limits and processing times. ## Checking status ```http theme={null} GET /api/v2/wallet/transaction?transaction_type={POOL_SELL|EXTERNAL_SELL}&transaction_id={id} ``` | Transaction type | Possible `status` values | | - | - | | Pool Sell | `CREATED`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`, `APPROVAL_REQUIRED`, `SOURCE_INVALID`, `VERIFICATION_PENDING`, `REFUND_INITIATED`, `REFUND_COMPLETED` | | External Sell | `CREATED`, `PAYMENT_INITIATED`, `FUNDS_RECEIVED`, `COMPLETED`, `FAILED`, `REFUND_INITIATED`, `REFUND_COMPLETED`, `REFUND_FAILED` | Poll until the status reaches a terminal state, or configure a webhook URL to receive completion pushes instead — see [Order Lifecycle and Webhooks](/offramp/operations/lifecycle). ## Payout rails and settlement | Payout type | Destination | Maximum per payout | Processing | | - | - | - | - | | Bank payout | All banks | NPR 1,000,000 | Real time | **Minimum payout:** USD 1 equivalent. **Only bank-account payouts are currently supported.** Payouts to bank or payment-provider wallets (including eSewa) are not supported yet. **First-party payouts only:** the payout bank account must belong to the KYC-verified user. Processing is subject to banking and operational conditions. The sample API inputs are illustrative and do not override the payout limits. ## User limits and EDD Separate user-level limits may apply. Use [Get User Limits](/api-reference/limits/get-user-limits) for current user-level limits. EDD is completed via the [EDD Hosted Widget](/users/limits-edd/widget), with a turnaround time of 2-3 working days. ## Related pages # PHP Offramp Source: https://guides.saber.money/offramp/currencies/php Sell stablecoin for Philippine Pesos, paid out directly to a Philippine bank account via InstaPay or PESONet. PHP offramp lets your users convert their stablecoin balance into Philippine Pesos, settled to a bank account in the Philippines. Every offramp flow follows the same four steps: **Create User → KYC → Bank Account Linkage → Transaction.** Both USDT and USDC are supported for PHP offramp. ```http theme={null} POST /api/v2/user ``` ```json theme={null} { "email": "user@example.com", "phone": "+639171234567" } ``` Response: ```json theme={null} { "success": true, "data": { "user_id": "550e8400-e29b-41d4-a716-446655440000" } } ``` Save `user_id` — every subsequent call needs it as the `X-User-Id` header. PHP supports two KYC methods: ```http theme={null} POST /api/v2/user/kyc ``` Headers: `X-User-Id: ` ```json theme={null} { "legal_name": "Juan dela Cruz", "poi": "", "poa": "", "dob": "1990-01-31", "email": "user@example.com", "phone": "+639171234567", "country_iso3": "PHL" } ``` Response: ```json theme={null} { "success": true, "data": {} } ``` Saber may request supporting KYC documents at any time — these must be shared within 72 hours of the request. Redirect the user to a signed URL: ```javascript theme={null} const sigString = clientId + timestamp + "sdk" + userId const secret = HmacSHA256(sigString, clientSecret).toString().toUpperCase() // redirect to: https://app.saber.money/kyc?client_id=...&user_id=...×tamp=...&secret=...&redirect_url= ``` The URL is valid for **10 minutes** from generation. Append the optional `redirect_url` as the last query parameter (URL-encoded) to send the user back to your app when they exit the flow. See [KYC Hosted Widget](/user/kyc/widget) for the full parameter reference. First, fetch the list of supported banks and their codes. This lookup only works for `PHL` — it returns an error for any other country: ```http theme={null} GET /api/v2/user/bank_account/bank-codes/PHL ``` ```json theme={null} { "success": true, "data": { "BDO Unibank": "BDO", "Bank of the Philippine Islands": "BPI", "Metropolitan Bank and Trust Company": "MBTC", "Land Bank of the Philippines": "LBP" } } ``` `data` is a flat map of bank name to `bank_code` — pass the code as-is into `bank_code` below. ```http theme={null} POST /api/v2/user/bank_account ``` Headers: `X-User-Id: ` ```json theme={null} { "account_number": "123456789012", "account_holder_name": "Juan dela Cruz", "bank_code": "BDO", "bank_name": "BDO Unibank", "bank_bic": "BOPIPHMM", "country": "PHL" } ``` For the Philippines, `account_number` (no spaces) and `account_holder_name` are required, plus at least one of `bank_code` or `swift_code`; `bank_name` is accepted alongside them. Response: ```json theme={null} { "success": true, "data": { "bank_id": "550e8400-e29b-41d4-a716-446655440000", "account_number": "1234XXXX9012", "ifsc_code": "", "account_holder_name": "Juan dela Cruz", "account_type": null, "name": "BDO Unibank", "iban": null, "bic": null, "validation_status": "PENDING", "routing_type": null, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "message": "" } } ``` `created_at`/`updated_at` here are ISO 8601 date strings — unlike wallet-transaction endpoints, which use epoch milliseconds. Save `bank_id` for the sell step. Unlike India, PHP bank accounts are approved **instantly** — there's no penny-drop step. A user can have up to **4 `APPROVED` bank accounts** at any point in time (error `304012` when exceeded). Min ₱60 (\~\$1) per transaction; max depends on the user's assigned limits (see [User Limits & EDD](#user-limits-and-edd) below). `payment_method: bank_transfer` resolves internally to one of: * **InstaPay** — for amounts up to ₱50,000. Instant, no cut-offs. Banking fee: ₱8. * **SwiftPay PESONet** — for amounts above ₱50,000. T+1 settlement on working days. Banking fee: ₱4. Two variants, both requiring a quote first — a **Live Quote** (informational, executes at market rate regardless) or a **Locked Quote** (rate held \~30-60s, supplied as `quote_id` in the sell request): Get an informational quote: ```http theme={null} GET /api/v2/wallet/s/quote?from_currency=USDT&to_currency=PHP&from_amount=100 ``` ```json theme={null} { "success": true, "data": { "from_currency": "USDT", "to_currency": "PHP", "from_amount": 100, "pre_fee_to_amount": 5650, "to_amount": 5600, "base_price": 56.5, "final_price": 56, "total_fee": 50, "fee_currency": "PHP", "fee_breakup": { "platform_fee": 20, "network_fee": 0, "client_fee": 10, "discount": 0, "tax_on_fee": 20, "tds": 0 } } } ``` Then create the sell: ```http theme={null} POST /api/v2/wallet/transaction/pool/sell ``` ```json theme={null} { "source_id": "", "fiat_symbol": "PHP", "crypto_symbol": "USDT", "crypto_amount": 25, "payment_method": "bank_transfer" } ``` Response: ```json theme={null} { "success": true, "data": { "id": "07825d5d-1879-4cc7-a87f-f5ed45ee391b", "transaction_type": "POOL_SELL", "status": "PROCESSING", "fiat_amount": 1410.5, "crypto_amount": 25, "fiat_symbol": "PHP", "crypto_symbol": "USDT", "exchange_rate": 56.42, "failure_code": "", "failure_desc": "", "created_at": 1774343695000, "user_id": "65d246fc-6a5b-43d7-89bf-944e95ff2279" } } ``` Generate a locked quote (expires in \~30-60s): ```http theme={null} GET /api/v2/wallet/w/quote?from_currency=USDT&to_currency=PHP&network=BSC&from_amount=10 ``` ```json theme={null} { "success": true, "data": { "from_currency": "USDT", "to_currency": "PHP", "network": "BSC", "from_amount": 10, "pre_fee_to_amount": 563.2, "to_amount": 563.2, "base_price": 56.32, "final_price": 56.32, "total_fee": 0, "fee_currency": "PHP", "fee_breakup": { "platform_fee": 0, "network_fee": 0, "client_fee": 0, "discount": 0, "tax_on_fee": 0, "tds": 0 }, "other_details": { "fiat_settlement_time": { "min": 0, "max": 1, "unit": "HOURS" } }, "transaction_type": "EXTERNAL_SELL", "id": "0c6ad6d3-6902-4a27-a30f-c176456a0835", "expiry": "2026-03-25T09:42:14.321113044Z", "settlement_expiry": "2026-03-25T10:41:44.321113044Z", "created_at": "2026-03-25T09:41:44.318189513Z", "updated_at": "2026-03-25T09:41:44.318189595Z" } } ``` Save `id` from the response as `quote_id`, then create the sell: ```http theme={null} POST /api/v2/wallet/transaction/crypto/sell ``` ```json theme={null} { "payment_method": "bank_transfer", "network": "BSC", "destination_instrument_id": "", "quote_id": "", "sender_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8", "refund_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8" } ``` Response: ```json theme={null} { "success": true, "data": { "id": "01992e48-db7d-7c55-bb87-6f4e1abf5fe3", "status": "CREATED", "fiat_symbol": "PHP", "crypto_symbol": "USDT", "fiat_amount": 563.2, "crypto_amount": 10, "payment_method": "bank_transfer", "deposit_crypto_address": "0xabcdef1234567890abcdef1234567890abcdef12", "deposit_network": "BSC", "destination_instrument_id": "a4680ba9-5ac6-4629-9493-2a88b160cd33", "refund_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8", "refund_network": "", "refund_tag": "", "sender_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8", "created_at": 1757418150839, "updated_at": 1757418150841 } } ``` Then fund `deposit_crypto_address` on-chain within the settlement expiry, and confirm it: ```http theme={null} POST /api/v2/wallet/transaction/crypto/sell/{id}/confirm-onchain-funding ``` ```json theme={null} { "transaction_hash": "0x1234567890abcdef" } ``` This returns **202 Accepted**, not 200 — Saber verifies the deposit asynchronously. ```json theme={null} { "success": true, "data": { "id": "01992e48-db7d-7c55-bb87-6f4e1abf5fe3", "status": "CREATED", "transaction_hash": "0x1234567890abcdef", "fiat_symbol": "PHP", "crypto_symbol": "USDT" } } ``` ## Checking status ```http theme={null} GET /api/v2/wallet/transaction?transaction_type={POOL_SELL|EXTERNAL_SELL}&transaction_id={id} ``` | Transaction Type | Possible `status` values | | - | - | | Pool Sell | `CREATED`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`, `APPROVAL_REQUIRED`, `SOURCE_INVALID`, `VERIFICATION_PENDING`, `REFUND_INITIATED`, `REFUND_COMPLETED` | | External Sell | `CREATED`, `PAYMENT_INITIATED`, `FUNDS_RECEIVED`, `COMPLETED`, `FAILED`, `REFUND_INITIATED`, `REFUND_COMPLETED`, `REFUND_FAILED` | Pool Sell: ```json theme={null} { "success": true, "data": { "transaction_type": "POOL_SELL", "status": "COMPLETED", "created_at": 1742207345000, "updated_at": 1742207346000, "id": "0195a3a6-62c2-766b-b984-7f9f1754b3f1", "crypto_symbol": "USDT", "fiat_symbol": "PHP", "exchange_rate": 56.4, "crypto_amount": 21, "fiat_amount": 1184.4, "bank_transaction_id": "1234", "source_id": "" } } ``` External Sell: ```json theme={null} { "success": true, "data": { "id": "01992e48-db7d-7c55-bb87-6f4e1abf5fe3", "transaction_type": "EXTERNAL_SELL", "status": "FUNDS_RECEIVED", "created_at": 1757418150839, "updated_at": 1757418150841, "crypto_symbol": "USDT", "fiat_symbol": "PHP", "crypto_amount": 0.695712, "fiat_amount": 55.38, "deposit_crypto_address": "0xabcdef1234567890abcdef1234567890abcdef12", "deposit_network": "BSC", "destination_instrument_id": "a4680ba9-5ac6-4629-9493-2a88b160cd33", "bank_transaction_id": "", "source_id": "" } } ``` Poll until the status reaches a terminal state, or configure a webhook URL to receive completion pushes instead — see [Order Lifecycle and Webhooks](/offramp/operations/lifecycle). ## Payout rails and settlement | Payout type | Destination | Maximum per payout | Processing | | - | - | - | - | | Bank payout | All banks | No corridor payout cap specified | Real time up to PHP 50,000; same day / T+1 above PHP 50,000 | **Only bank-account payouts are currently supported. Payouts to payment-provider wallets, including GCash, PayMaya, Coins.ph, and GrabPay, are not supported yet.** Existing API minimums, provider-specific rules and user-level limits still apply. ## User limits and EDD Each user has a lifetime transaction limit (currently \$200,000 USD equivalent — this figure is suggestive and subject to change; confirm current limits with Saber). Once a user's limit is breached, no further transactions can be created until Enhanced Due Diligence (EDD) is completed to raise it. EDD is completed via the [EDD Hosted Widget](/users/limits-edd/widget), with a turnaround time of 2-3 working days. ## Related pages # Fetching Transaction Status Source: https://guides.saber.money/offramp/operations/fetch-status GET the status and details of a crypto-to-fiat sell transaction (Pool Sell, regular Sell, or External Sell) via the v2 generic transaction endpoint, including status enums, exchange rate, amounts, and failure codes. ## Overview Use the generic v2 transaction endpoint to track the current status of a sell transaction, no matter which offramp flow created it. This allows merchants to poll for completion, surface progress to users, and investigate failures. *** ## Endpoint **URL**: `https://api.saber.money/api/v2/wallet/transaction` **Method**: `GET` | Query Param | Required | Description | | - | - | - | | `transaction_type` | Yes | `POOL_SELL`, `SELL`, or `EXTERNAL_SELL` — must match the flow that created the transaction. | | `transaction_id` | Yes | The transaction UUID (`id`) returned from the corresponding create call. | ### Request Headers | **Header** | **Description** | | - | - | | `X-Api-Key` | Your API key | | `X-Signature` | HMAC-SHA256 signature (auto-generated) | | `X-Timestamp` | Unix timestamp in seconds (auto-generated) | | `X-Request-Id` | Unique request identifier (auto-generated) | | `X-User-Id` | The user's UUID | *** ### Status values by transaction type | Transaction Type | Possible `status` values | | - | - | | `POOL_SELL`, `SELL` | `CREATED`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`, `APPROVAL_REQUIRED`, `SOURCE_INVALID`, `VERIFICATION_PENDING`, `REFUND_INITIATED`, `REFUND_COMPLETED` | | `EXTERNAL_SELL` | `CREATED`, `PAYMENT_INITIATED`, `FUNDS_RECEIVED`, `COMPLETED`, `FAILED`, `REFUND_INITIATED`, `REFUND_COMPLETED`, `REFUND_FAILED` | `PAYMENT_INITIATED` and `FUNDS_RECEIVED` reflect on-chain funding progress after calling Confirm External Sell On-chain Funding — see [Available Flows](/offramp/available-flows) for the full External Sell sequence, or the [INR Offramp Flow](/offramp/currencies/inr) walkthrough for the exact confirm call. *** ### Sample Request (Pool Sell) ```bash theme={null} curl --location 'https://api.saber.money/api/v2/wallet/transaction?transaction_type=POOL_SELL&transaction_id=0195a3a6-62c2-766b-b984-7f9f1754b3f1' \ --header 'X-Api-Key: YOUR_API_KEY' \ --header 'X-Signature: GENERATED_SIGNATURE' \ --header 'X-Timestamp: 1732265171' \ --header 'X-Request-Id: unique-request-id' \ --header 'X-User-Id: USER_UUID_HERE' ``` ### Sample Response ```json theme={null} { "success": true, "data": { "transaction_type": "POOL_SELL", "status": "COMPLETED", "created_at": 1742207345000, "updated_at": 1742207346000, "id": "0195a3a6-62c2-766b-b984-7f9f1754b3f1", "crypto_symbol": "USDT", "fiat_symbol": "INR", "exchange_rate": 84.56973573047619, "crypto_amount": 21, "fiat_amount": 1775.96445034, "bank_transaction_id": "1234", "source_id": "" } } ``` *** ### Key Response Fields | **Field** | **Description** | **Example Value** | | - | - | - | | `transaction_type` | Echoes the queried type. | `POOL_SELL` | | `status` | Current status — see the table above. | `COMPLETED` | | `exchange_rate` | The conversion rate used for the transaction. | `84.56973573047619` | | `crypto_amount` | The amount of cryptocurrency sold. | `21` | | `fiat_amount` | The amount of fiat currency delivered. | `1775.96445034` | | `fiat_symbol` / `crypto_symbol` | Currency/asset involved. | `INR` / `USDT` | | `id` | Unique identifier for the transaction. | `0195a3a6-62c2-766b-b984-7f9f1754b3f1` | | `created_at` / `updated_at` | Unix timestamp **in milliseconds**. | `1742207345000` | | `bank_transaction_id` | Bank reference ID for the fiat transfer. Empty string until settled. | `1234` | | `source_id` | Source instrument identifier. Empty string when not applicable. | `""` | | `failure_code` / `failure_desc` | Present only if the transaction failed — omitted (key absent) on success. | `FIAT_PAYMENT_FAILED` | *** ## Use Cases 1. **Transaction Tracking** — Poll status until it reaches a terminal state, to confirm completion. 2. **User Notifications** — Reflect progress to end users based on status. 3. **Error Investigation** — Use `failure_code`/`failure_desc` to diagnose a failed transaction. *** ## Legacy (v1) The original v1 endpoint remains available for existing integrations, but new integrations should use the v2 endpoint above. **URL**: `https://api.saber.money/api/v1/wallet/conversion/fiat/transaction/{transaction_id}` **Method**: `GET` | **Header** | **Description** | | - | - | | `X-Timestamp` | UNIX timestamp for the request. | | `X-Client-Id` | Unique identifier for the merchant. | | `X-Request-Id` | A unique identifier for the request (optional but recommended). | | `X-Secret-Key` | API secret key for authenticating the request. | | `X-User-Id` | The unique ID of the user making the request. | ```bash theme={null} curl --location 'https://api.saber.money/api/v1/wallet/conversion/fiat/transaction/a892dc4a-aecb-4a22-9de4-2990b4179b77' \ --header 'X-Timestamp: 1732265171' \ --header 'X-Client-Id: CLIENT_ID_HERE' \ --header 'X-Request-Id: CC' \ --header 'X-Secret-Key: SECRET_KEY_HERE' \ --header 'X-User-Id: USER_ID_HERE' ``` # Fund Management Source: https://guides.saber.money/offramp/operations/fund-management Managing Pool Account funds for offramp — how balance is deducted for Pool Sell transactions, depositing and withdrawing funds, checking balance, and fetching the pool wallet address. For merchants running Pool Sell transactions, offramp payouts draw directly from your Pool Account balance. This page covers how that balance moves and how to manage it. ## How balance is deducted Each time a Pool Sell transaction is created, the equivalent stablecoin amount for that payout is deducted from your Pool Account balance immediately, and the fiat leg is settled to the user's bank account in the same step. If the Pool Account doesn't have sufficient balance to cover a payout, the transaction will fail — keeping the Pool Account topped up is necessary for Pool Sell to keep working. ## Checking balance ```http theme={null} GET /api/v1/wallet/balance?account=SPOT&symbol=USDT ``` ```json theme={null} { "success": true, "data": { "coin_info": { "USDT": { "coin": "USDT", "name": "TetherUS", "available_balance": 76.7, "available_balance_usd": 76.7, "total_balance": 76.7, "total_balance_usd": 76.7, "withdraw_enable": true, "deposit_enable": true } }, "available_balance": 76.7, "total_balance": 76.7 } } ``` Check this before initiating high-value payouts, or on a regular schedule, to make sure the Pool Account can cover expected offramp volume. ## Fetching the pool wallet address To deposit stablecoin into the Pool Account, first fetch its deposit address: ```http theme={null} GET /api/v1/wallet/user_deposit_address?symbol=USDT&network=BSC ``` ```json theme={null} { "success": true, "data": [ { "coin": "USDT", "network": "BSC", "name": "Binance Smart Chain (BEP20)", "address": "0xc37e9cbe6e1d481d9372585e1b91cb387853d352", "tag": "", "url": "https://bscscan.com/address/0xc37e9cbe6e1d481d9372585e1b91cb387853d352", "batchReleaseLimit": 0 } ] } ``` ## Depositing funds Send stablecoin on-chain to the fetched deposit address, on the network specified. Once the deposit is confirmed, the balance reflects in your Pool Account and becomes available for Pool Sell transactions. ## Withdrawing funds To withdraw available Pool Account balance out to an external wallet: ```http theme={null} POST /api/v2/pool/wallet/withdraw ``` ```json theme={null} { "crypto_symbol": "USDT", "amount": "", "network": "BSC", "wallet_address": "" } ``` Withdrawals can also be initiated directly from the dashboard — see [Dashboard Overview](/client/dashboard/overview). # Initiating Transaction Source: https://guides.saber.money/offramp/operations/initiate-transaction Sell USDT or USDC for INR via bank transfer using Saber's fiat/sell or pool/sell endpoints, covering parameters and response fields. Sell cryptocurrency (USDT or USDC) for fiat (currently INR) via bank transfer, using the fiat/sell endpoint. ## Initiate Sell Transaction **Endpoint:** Sell: `POST /api/v1/wallet/conversion/fiat/sell` Pool Sell: `POST /api/wallet-services/v2/transaction/pool/sell` **Sample request:** ```bash theme={null} curl --location 'https://api.saber.money/api/v1/wallet/conversion/fiat/sell' \ --header 'X-Timestamp: {{timestamp}}' \ --header 'X-Client-Id: •••••••' \ --header 'X-Secret-Key: •••••••' \ --header 'X-Request-Id: {{request_id}}' \ --header 'X-User-Id: •••••••' \ --data '{ "source_id": "65d246fc-6a5b-43d7-89bf-944e95ff2279", "fiat_symbol": "INR", "crypto_symbol": "USDT", "crypto_amount": 1.14, "payment_method": "bank_transfer" }' ``` **Sample response:** ```json theme={null} { "success": true, "data": { "status": "CREATED", "fiat_amount": "100", "transaction_type": "SELL", "fiat_symbol": "INR", "failure_code": "", "exchange_rate": "0", "id": "a892dc4a-aecb-4a22-9de4-2990b4179b77", "crypto_symbol": "USDT", "created_at": 1664783475000, "failure_desc": "", "crypto_amount": "1.14" } } ``` ### Required Parameters | Parameter | Description | | - | - | | `source_id` | The unique wallet address ID of the user initiating the transaction. | | `fiat_symbol` | Fiat currency to receive. Currently only `INR` is supported. | | `crypto_symbol` | Crypto to sell — `USDT` or `USDC` (others available on request). | | `crypto_amount` | Amount of crypto to sell. Required (Pool Sell may use `quote_id` instead). | | `payment_method` | Must be `bank_transfer`. | ### Response Parameters | Field | Description | | - | - | | `success` | Whether the request succeeded. | | `status` | Transaction status — `CREATED`, `PENDING`, `COMPLETED`, etc. | | `fiat_amount` | Fiat amount, as a string. Monetary/rate fields (`fiat_amount`, `crypto_amount`, `exchange_rate`) are serialized as JSON strings — parse as decimals, not numbers. | | `transaction_type` | `SELL` for the standard endpoint, `POOL_SELL` for the pool endpoint. | | `fiat_symbol` | Fiat currency symbol (`INR`). | | `exchange_rate` | Conversion rate, as a string. `"0"` at creation for standard Sell; populated for Pool Sell. | | `id` | Unique transaction ID. | | `crypto_symbol` | Crypto symbol sold (e.g. `USDT`). | | `crypto_amount` | Amount of crypto sold. | | `reason_code` | Present only when applicable. | | `created_at` | Unix epoch **milliseconds** (int64). | | `failure_code` / `failure_desc` | Present only on failure; empty strings on success. | ## Notes * INR is currently the only supported fiat currency; `bank_transfer` the only payment method. * Only USDT or USDC can be sold — other cryptocurrencies are available on request. * Sell can also be initiated directly from the pool wallet through this API — the request body is the same, but see [Pool Sell transaction status](https://saber-money.readme.io/reference/post_api-v2-wallet-transaction-pool-sell) for the different create/status endpoints. **API Reference:** [Create Sell Transaction](https://saber-money.readme.io/reference/createselltransaction) # Order lifecycle and Webhooks Source: https://guides.saber.money/offramp/operations/lifecycle Webhook statuses and sample payloads for the Sell, Pool Sell, and External Sell offramp flows. This page describes the webhook events and order statuses for the offramp flows: Sell, Pool Sell, and External Sell. ## Sell `transaction_status` values sent: `COMPLETED`, `FAILED`, `REFUND_COMPLETED` ```json theme={null} { "transaction_id": "2f4e6d8c-0b1a-4c3e-9f5d-7a6b8c9d0e1f", "client_id": "3a9f0c12-6b4e-4d7a-9c21-5e8f7a6b4c3d", "user_id": "e2d4c6b8-1a3f-4e5d-8c7b-9a0f1e2d3c4b", "event": "SELL", "fiat_amount": 8800, "usd_amount": 100, "transfer_type": "BANK_TRANSFER", "invested_at": "2026-10-09T12:30:00Z", "bank_reference_id": "UTR000000003456", "transaction_status": "COMPLETED" } ``` ## Pool Sell `transaction_status` values sent: `COMPLETED`, `FAILED`, `REFUND_COMPLETED`. Same body as Sell; `event` is `SELL`. ```json theme={null} { "transaction_id": "8c0a2e4f-6b1d-4f3a-8e5c-9d7b1a3c5e7f", "client_id": "3a9f0c12-6b4e-4d7a-9c21-5e8f7a6b4c3d", "user_id": "e2d4c6b8-1a3f-4e5d-8c7b-9a0f1e2d3c4b", "event": "SELL", "fiat_amount": 44000, "usd_amount": 500, "transfer_type": "BANK_TRANSFER", "invested_at": "2026-10-09T13:45:20Z", "bank_reference_id": "UTR000000007890", "transaction_status": "REFUND_COMPLETED" } ``` ## External Sell `status` values sent, in order: `FUNDS_RECEIVED` → `PAYMENT_INITIATED` → `COMPLETED`. Failure path: `REFUND_INITIATED`, `REFUND_FAILED`, `REFUND_COMPLETED` (always followed by a `FAILED` webhook), `FAILED`. ```json theme={null} { "id": "4d6f8a0c-2e1b-4a3d-9c5e-6f7a8b9c0d1e", "status": "COMPLETED", "fiat_symbol": "INR", "crypto_symbol": "USDT", "fiat_amount": 8800, "crypto_amount": 100, "payment_method": "BANK_TRANSFER", "deposit_crypto_address": "TXyz...mock...Deposit01", "deposit_network": "TRC20", "deposit_tag": "", "sender_wallet_address": "TXyz...mock...Sender01", "transaction_hash": "0xmocktxnhash000000000000000000000000000000000000000000000000ef01", "destination_instrument_id": "inst_mock_001", "refund_wallet_address": "TXyz...mock...Refund01", "refund_network": "TRC20", "refund_tag": "", "failure_code": "", "failure_desc": "", "created_at": 1791545000000, "updated_at": 1791548600000 } ``` On refund, `refund_transaction_hash` is also included. # Fetching Quote Source: https://guides.saber.money/offramp/operations/quote The GET /wallet/s/quote Sell Quote API returns real-time crypto-to-fiat off-ramp conversion rates, fees, and breakdowns without creating an order. Real-time conversion quote for the off-ramp (sell) process — shows users accurate rates, fees, and final amounts before they commit to a transaction. It's read-only and independent of order creation. ## Endpoint **URL**: `https://api.saber.money/api/v2/wallet/s/quote`
**Method**: `GET` ## Request Parameters | **Parameter** | **Type** | **Required** | **Description** | **Example Value** | | - | - | - | - | - | | `from_currency` | `String` | Yes | The cryptocurrency being sold. | `USDT` | | `to_currency` | `String` | Yes | The fiat currency the user wants to receive. | `INR` | | `from_amount` | `Float` | Conditional | The amount of cryptocurrency to convert. Provide either `from_amount` or `to_amount`. | `1` | | `to_amount` | `Float` | Conditional | The target fiat amount to receive. Provide either `from_amount` or `to_amount`. | `86.24` | | `payment_method` | `String` | No | Payment method used for the fiat withdrawal; affects fee/price resolution. Defaults to the standard withdrawal method for the fiat currency when omitted. | `bank_transfer` | | `payment_instrument_id` | `String` | No | Identifier of a specific saved payment instrument to price the quote against. | `pi_123` | > **Note:** At least one of `from_amount` or `to_amount` is required. ### Headers | **Header** | **Description** | | - | - | | `X-Timestamp` | UNIX timestamp for the request. | | `X-Client-Id` | Unique identifier for the merchant. | | `X-Request-Id` | A unique identifier for the request (optional but recommended). | | `X-Secret-Key` | API secret key for authenticating the request. | | `X-User-Id` | The unique ID of the user making the request. | *** ### Sample Request ```bash theme={null} curl --location 'https://api.saber.money/api/v2/wallet/s/quote?from_currency=USDT&from_amount=1&to_currency=INR' \ --header 'X-Timestamp: 1732265171' \ --header 'X-Client-Id: CLIENT_ID_HERE' \ --header 'X-Request-Id: CC' \ --header 'X-Secret-Key: SECRET_KEY_HERE' \ --header 'X-User-Id: USER_ID_HERE' ``` *** ### Sample Response ```json theme={null} { "success": true, "data": { "transaction_type": "SELL", "from_currency": "USDT", "to_currency": "INR", "from_amount": 1, "pre_fee_to_amount": 87.11, "to_amount": 86.24, "base_price": 87.11, "final_price": 86.24, "total_fee": 0.87, "fee_currency": "INR", "fee_breakup": { "platform_fee": 0, "network_fee": 0, "client_fee": 0, "discount": 0, "tax_on_fee": 0, "tds": 0.87 } } } ``` *** ### Key Response Fields | **Field** | **Description** | **Example Value** | | - | - | - | | `transaction_type` | The type of transaction for this quote; always `SELL` for the sell-quote endpoint. | `SELL` | | `from_currency` | The cryptocurrency being converted. | `USDT` | | `to_currency` | The fiat currency being received. | `INR` | | `from_amount` | Amount of cryptocurrency being sold. | `1` | | `pre_fee_to_amount` | The amount in fiat before fees are applied. | `87.11` | | `to_amount` | Final amount in fiat after deducting fees. | `86.24` | | `base_price` | Conversion rate before fees. | `87.11` | | `final_price` | Conversion rate after fees. | `86.24` | | `total_fee` | Total fee applied to the transaction. | `0.87` | | `fee_currency` | The currency in which the fee is charged. | `INR` | | `fee_breakup` | Detailed breakdown of the fees (platform\_fee, network\_fee, client\_fee, discount, tax\_on\_fee, tds). May also include an optional banking\_fee field, which appears only for payment methods/rails that charge a banking fee and is omitted when zero (JSON tag banking\_fee, omitempty). | `{...}` | ## Notes * Rates reflect current market conditions and are subject to change. * `fee_breakup` may contain an additional `banking_fee` field for certain payment rails; it's omitted when zero. **API Reference:** [Fetch Sell Quote](https://saber-money.readme.io/reference/fetchsellquote) # Overview Source: https://guides.saber.money/offramp/overview Saber's offramp offerings — supported countries, currencies, tokens, transaction models, and where to find flow-specific guides. Offramp lets your users convert their stablecoin balance into fiat, paid out directly to a linked bank account. Both USDT and USDC are supported as settlement assets for offramp. ## Transaction flow ```mermaid theme={null} flowchart LR A[Fetch crypto deposit address] --> B[Deposit crypto] B --> C[Receive deposit webhook] C --> D[Create sell order] D --> E[Convert to fiat] E --> F[Deposit fiat to user's bank account] ``` ## Supported countries and currencies Saber currently supports offramp across multiple corridors, including India (INR), Bangladesh (BDT), the Philippines (PHP), the Eurozone (EUR), the UK (GBP), and Indonesia (IDR), with more added over time. Each currency has its own supported payment rails, bank account requirements, and settlement characteristics. ## Transaction models Offramp transactions follow one of three models: * **Sell** — pre-funded directly from the user's own wallet. * **Pool Sell (recommended)** — pre-funded from the merchant's pool wallet, deducted and settled in one step. * **External Sell** — post-funded: the payout is initiated against a locked quote first, then funded on-chain and confirmed before fiat settlement completes. See [Transaction Flows](/offramp/available-flows) for a full comparison, and [Traditional Rails](/offramp/currencies/inr-traditional) for rail-specific payout options for INR. ## Currency-specific flows Each supported currency has its own end-to-end walkthrough covering user creation, KYC, bank account linkage, and the sell flow itself: ## Operations reference For the underlying operations shared across currencies — fetching quotes, initiating transactions, checking status, funding/withdrawal, and order lifecycle/webhooks — see [Offramp Operations](/offramp/operations/quote). # Available Flows Source: https://guides.saber.money/onramp/available-flows Compares Saber's two onramp flows — Pool Buy and External Buy — covering fund flow, liquidity, refunds, fees, and quote handling. Saber supports two onramp flows, trading off liquidity management and settlement destination differently. Both USDT and USDC are supported across both flows. Collected fiat is converted to stablecoin and credited to the **merchant's pool wallet**, rather than to any individual user wallet. This accumulates as reusable liquidity — for example, to fund [Pool Sell](/offramp/available-flows) payouts on the offramp side through the same pool. Fiat payment is collected from the user via the applicable payment method (bank transfer, Open Banking, ACH Pull, etc., depending on corridor). The onramp (Pool Buy) is initiated for the user. Fiat is converted to stablecoin and credited to the pool wallet. ```mermaid theme={null} sequenceDiagram participant U as User participant S as Saber participant P as Pool Wallet U->>S: Pay fiat (bank transfer / Open Banking / ACH Pull) S->>S: Convert fiat to stablecoin S->>P: Credit pool wallet ``` Liquidity credited via Pool Buy is reusable — it becomes available balance for offramp Pool Sell transactions, letting fiat collected on the onramp side directly fund payouts on the offramp side. See [Onramp Fund Management](/client/pool-account/overview). Collected fiat is converted to stablecoin and sent directly to a **wallet address you specify**, on a given network — rather than staying within Saber's system. Fiat payment is collected from the user via the applicable payment method. The onramp (External Buy) is initiated, specifying the destination `wallet_address` and `network`. Fiat is converted to stablecoin and sent on-chain to the specified wallet address. ```mermaid theme={null} sequenceDiagram participant U as User participant S as Saber participant W as External Wallet U->>S: Pay fiat S->>S: Convert fiat to stablecoin S->>W: Send stablecoin on-chain ``` Network fees apply per External Buy transaction, and the specified wallet address/network combination must be supported — see [Available Networks and Tokens](/client/pool-account/supported-networks). ## Comparison | Factor | Pool Buy (Recommended) | External Buy | | - | - | - | | Liquidity management | Accumulates as reusable balance in the merchant's pool wallet | Sent out immediately — no accumulation, funds leave Saber's system per transaction | | Crypto settlement | Credited to pool wallet, available for reuse (e.g. funding offramp Pool Sell) | Sent on-chain to the specified wallet address and network | | Fee | Network fee applies on withdrawal out of the pool, not per Pool Buy transaction | Network fee applies on each transaction | | Additional fields required | None beyond the standard buy request | `wallet_address` and `network` required | | Quote validity | Live Quote (market rate) or Locked Quote (valid \~30-60s) | Live Quote (market rate) or Locked Quote (valid \~30-60s) | ## Related pages # EUR/GBP Onramp Source: https://guides.saber.money/onramp/currencies/eur-gbp Buy stablecoin with Euros or Pounds Sterling via an Open Banking redirect. EUR/GBP onramp lets your users convert Euros or Pounds Sterling into stablecoin. Every onramp flow follows the same four steps: **Create User → KYC → Bank Account Linkage → Transaction** — though EUR/GBP onramp handles the linkage step a little differently, as covered below. Both USDT and USDC are supported for EUR/GBP onramp. ```http theme={null} POST /api/v2/user ``` ```json theme={null} { "email": "user@example.com", "phone": "+491701234567" } ``` Response: ```json theme={null} { "success": true, "data": { "user_id": "550e8400-e29b-41d4-a716-446655440000" } } ``` Save `user_id` — every subsequent call needs it as the `X-User-Id` header. EUR/GBP supports two KYC methods. For onramp specifically, the user's **address (address line 1, town, country)** is mandatory. ```http theme={null} POST /api/v2/user/kyc ``` Headers: `X-User-Id: ` ```json theme={null} { "legal_name": "Hans Mueller", "poi": "", "poa": "", "dob": "1990-01-31", "email": "user@example.com", "phone": "+491701234567", "country_iso3": "DEU", "address_line_1": "10 Musterstrasse", "town": "Berlin", "country": "DEU" } ``` Response: ```json theme={null} { "success": true, "data": {} } ``` Since KYC Sharing means Saber isn't collecting the KYC itself, the client must explicitly pass `address_line_1`, `town`, and `country` in this call. See [KYC Sharing](/user/kyc/sharing) for the full field reference. Saber may request supporting KYC documents at any time — these must be shared within 72 hours of the request. Redirect the user to a signed URL: ```javascript theme={null} const sigString = clientId + timestamp + "sdk" + userId const secret = HmacSHA256(sigString, clientSecret).toString().toUpperCase() // redirect to: https://app.saber.money/kyc?client_id=...&user_id=...×tamp=...&secret=...&redirect_url= ``` The URL is valid for **10 minutes** from generation. Append the optional `redirect_url` as the last query parameter (URL-encoded) to send the user back to your app when they exit the flow. Since the address fields are captured as part of the widget's own KYC flow, no separate address submission is needed here. See [KYC Hosted Widget](/user/kyc/widget) for the full parameter reference. There is no bank account registration step for EUR/GBP onramp — funds are pulled directly via an Open Banking redirect at payment time, covered in the next step. Two variants: * **Pool Buy** — collected funds are converted to stablecoin and deposited into the pool wallet on the Saber system. * **External Buy** — collected funds are converted to stablecoin and sent to a specified wallet address on a given network. The flow is identical for both — the only difference is that External Buy requires `wallet_address` and `network` in the request. See [Available Networks and Tokens](/client/pool-account/supported-networks) for supported networks and per-network limits. Two kinds of quotes are available — a **Live Quote** (informational, executes at market rate) or a **Locked Quote** (rate held \~30-60s, supplied as `quote_id` in the buy request): Live quote: ```http theme={null} GET /api/v2/wallet/s/quote?from_currency=EUR&to_currency=USDT&from_amount=100 ``` Locked quote: ```http theme={null} GET /api/v2/wallet/w/quote?from_currency=EUR&to_currency=USDT&from_amount=100 ``` Save `id` from the locked-quote response as `quote_id`. ```http theme={null} POST /api/v2/wallet/transaction/pool/buy ``` ```json theme={null} { "fiat_symbol": "EUR", "crypto_symbol": "USDT", "fiat_amount": 100, "payment_method": "open_banking", "quote_id": "" } ``` `quote_id` is only required when using a Locked Quote — omit it when using a Live Quote. ```json theme={null} { "fiat_symbol": "EUR", "crypto_symbol": "USDT", "fiat_amount": 100, "payment_method": "open_banking", "quote_id": "", "wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8", "network": "BSC" } ``` **Response:** the `payment_method: open_banking` response includes a payment URL. Redirect the user there to select their bank and complete payment. ```json theme={null} { "success": true, "data": { "id": "xxxxxx", "status": "CREATED", "payment_url": "https://pay.saber.money/...", "expiry": "xxxxxx" } } ``` The payment URL is valid for **1 hour**. If the user doesn't complete payment within that window, the link expires. See [EUR/GBP Open Banking best practices](/onramp/currencies/eur-gbp-best-practices) for guidance on handling the redirect flow well. ## Checking status ```http theme={null} GET /api/v2/wallet/transaction?transaction_type={POOL_BUY|EXTERNAL_BUY}&transaction_id={id} ``` Status values mirror the offramp equivalents (`CREATED`, `PROCESSING`/`PAYMENT_INITIATED`, `COMPLETED`, `FAILED`, `CANCELLED`, `REFUND_INITIATED`, `REFUND_COMPLETED`, etc.). Poll until the status reaches a terminal state, or configure a webhook URL to receive completion pushes instead — see [Order Lifecycle and Webhooks](/onramp/operations/lifecycle). ## User limits and EDD Each user has a lifetime transaction limit (currently \$200,000 USD equivalent — this figure is suggestive and subject to change; confirm current limits with Saber). Once a user's limit is breached, no further transactions can be created until Enhanced Due Diligence (EDD) is completed to raise it. EDD is completed via the [EDD Hosted Widget](/users/limits-edd/widget), with a turnaround time of 2-3 working days. ## Related pages # EUR/GBP Openbanking: Best Practices Source: https://guides.saber.money/onramp/currencies/eur-gbp-best-practices UI/UX recommendations for presenting the EUR/GBP OpenBanking instant bank transfer onramp: titles, bank logos, labels, tooltips, and CTAs. Best practices on integrating OpenBanking onramp into your platform. ![Mobile mockup of a suggested deposit method screen with Instant bank transfer highlighted as the recommended option](https://files.readme.io/356de67d6db273895695491b71a78cb59d2602942f892ca992340391044d8f05-image.png) ## Best practices ### Title and Subtitle Display a clear title and subtitle that communicates the payment method’s benefits. **Suggested implementation:** * Title: “Instant bank transfer” * Subtitle: “Add funds securely and directly from your bank account.” ### Bank Logos Show logos of popular banks to build trust and familiarity. Suggested implementation: * Include the logos of the most popular banks in the region. * [Download bank logos](https://docs.getivy.de/docs/assets) Bank logo alternatives: If the bank logos don’t match your existing UI, try one of these alternatives: * Icon * A lightning bolt icon for Instant bank transfer (emphasises speed/convenience) * If you don’t offer other bank transfer options, a simple bank icon works well for Instant bank transfer * Geographic Indicators * EU flag for Euro payments * UK flag for GBP payments * [Download non-bank logo assets](https://drive.google.com/drive/folders/1Ia_c9Ad9gx9GIV1CewjqWPy6XyzWE5E7?usp=sharing) ### Default positioning If using multiple payment options, position Instant bank transfer as the default and primary payment option. * Suggested implementation: * Place Instant bank transfer as the first payment option in your list * Pre-select it by default if possible ### Recommended label Add visual indicators to help guide users toward the recommended option. * Suggested implementation: * Add a “Recommended” label next to Instant bank transfer * Use consistent styling with other UI elements in your payment flow ![Two mobile mockups comparing a short tooltip versus a long tooltip explaining how instant bank transfer works](https://files.readme.io/cac90ee1ca0355624241b98a35610caf6d8f5b387b2e5a1e0895948f3b5bc038-image.png) ### Educational tooltip Add a tooltip icon next to Instant bank transfer that shows key information on hover/tap. * Short version: “Select your bank, authorise the transfer using your bank’s security, and we’ll bring you back automatically to complete your deposit.” * Long version: * “1 · Select your bank Choose from our list of supported banks - all major banks are available * 2 · Approve payment We’ll safely redirect you to authorise the transfer in your bank’s secure environment * 3 · Receive funds In most cases, the funds are transferred immediately after approval” ![Mobile mockup showing deposit method options (instant bank transfer, manual bank transfer, debit/credit card) each tagged with speed, cost, and limit comparisons](https://files.readme.io/5921f6e1c553002b581f899862dab636ea6ceb7491720058733ebce5ac0e31ee-image.png) ### Payment method comparison labels When presenting multiple payment methods, clear labels help users quickly understand the benefits and limitations of each option. By consistently labeling key attributes across payment methods, you enable users to make informed decisions based on what matters most to them. * Suggested labels: * Speed: “Instant” vs e.g. “3-5 Business days” * Cost: “Free” vs e.g. “3% Fee” * Limit: “No limit” vs e.g. “10K weekly limit” ![Mobile mockup of an amount-to-deposit screen with a dynamic call-to-action button reflecting the chosen deposit method](https://files.readme.io/869fd083056577b3d25653e08cd8fe82b47e96d3a0109a504029db7d2b407760-image.png) ### Dynamic CTA Set clear expectations about the next step by adding a dynamic CTA when Instant bank transfer is selected Suggested implementation: * On the final page before redirecting to Payment, label the CTA “Continue to select your bank” # USD Onramp Source: https://guides.saber.money/onramp/currencies/usd Buy stablecoin with USD via Plaid-linked ACH Pull, with KYC completed through a Partner URL. USD onramp lets your users convert USD into stablecoin, pulled directly from a Plaid-linked bank account. Every onramp flow follows the same four steps: **Create User → KYC → Bank Account Linkage → Transaction.** Both USDT and USDC are supported for USD onramp. ```http theme={null} POST /api/v2/user ``` ```json theme={null} { "email": "user@example.com", "phone": "+12025550123" } ``` Response: ```json theme={null} { "success": true, "data": { "user_id": "550e8400-e29b-41d4-a716-446655440000" } } ``` Save `user_id` — every subsequent call needs it as the `X-User-Id` header. USD supports **only** the KYC Partner URL method — there is no KYC Widget or KYC Sharing option for USD. The user must submit US ID documents only. Request a Partner KYC URL: ```http theme={null} POST /api/v2/user/kyc/partner-url ``` Headers: `X-User-Id: ` This call is **asynchronous**. The first response may come back pending: ```json theme={null} { "success": true, "data": { "status": "PENDING" } } ``` Poll the same endpoint again after a few seconds until the status becomes `ACTIVE`, at which point the response includes the KYC URL: ```json theme={null} { "success": true, "data": { "status": "ACTIVE", "url": "https://withpersona.com/verify?..." } } ``` Redirect the user to `url` to complete identity verification. See [Partner URL](/user/kyc/partner-url) for the full reference. To return the user to your app afterwards, append `redirect-uri` as the last query parameter on the returned Persona URL: ```text theme={null} &redirect-uri= ``` USD bank accounts are linked through Plaid, using the Bank Account Widget: ```javascript theme={null} const sigString = clientId + timestamp + "sdk" + userId const secret = HmacSHA256(sigString, clientSecret).toString().toUpperCase() // redirect to: https://app.saber.money/bank/add?client_id=...&user_id=...×tamp=...&secret=... ``` The URL is valid for **10 minutes** from generation. The user selects and authorizes their bank account via Plaid on this page. To return the user to your app once they exit the Plaid flow, append `redirect_url` as the last query parameter: ```text theme={null} &redirect_url= ``` Once the user adds their bank account and it's successfully linked and approved, the client can fetch the list of approved bank accounts: ```http theme={null} GET /api/v2/user/bank_account?status=APPROVED ``` A user can have up to **10 active bank accounts** at any point in time. See [Bank Account — Through Hosted Widget](/user/bank-account/widget) for setup details. Two variants: * **Pool Buy** — collected funds are converted to stablecoin and deposited into the pool wallet on the Saber system. * **External Buy** — collected funds are converted to stablecoin and sent to a specified wallet address on a given network. The flow is identical for both — the only difference is that External Buy requires `wallet_address` and `network` in the request. See [Available Networks and Tokens](/client/pool-account/supported-networks) for supported networks and per-network limits. The request body must also include `source_id` — the ID of the linked, approved bank account funds should be pulled from. Two kinds of quotes are available — a **Live Quote** (informational, executes at market rate) or a **Locked Quote** (rate held \~30-60s, supplied as `quote_id` in the buy request): Live quote: ```http theme={null} GET /api/v2/wallet/s/quote?from_currency=USD&to_currency=USDT&from_amount=100 ``` Locked quote: ```http theme={null} GET /api/v2/wallet/w/quote?from_currency=USD&to_currency=USDT&from_amount=100 ``` Save `id` from the locked-quote response as `quote_id`. ```http theme={null} POST /api/v2/wallet/transaction/pool/buy ``` ```json theme={null} { "source_id": "", "fiat_symbol": "USD", "crypto_symbol": "USDT", "fiat_amount": 100, "payment_method": "bank_transfer_ach_pull", "quote_id": "" } ``` `quote_id` is only required when using a Locked Quote — omit it when using a Live Quote. ```json theme={null} { "source_id": "", "fiat_symbol": "USD", "crypto_symbol": "USDT", "fiat_amount": 100, "payment_method": "bank_transfer_ach_pull", "quote_id": "", "wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8", "network": "BSC" } ``` ## Checking status ```http theme={null} GET /api/v2/wallet/transaction?transaction_type={POOL_BUY|EXTERNAL_BUY}&transaction_id={id} ``` Status values mirror the offramp equivalents (`CREATED`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`, `REFUND_INITIATED`, `REFUND_COMPLETED`, etc.). Poll until the status reaches a terminal state, or configure a webhook URL to receive completion pushes instead — see [Order Lifecycle and Webhooks](/onramp/operations/lifecycle). ## User limits and EDD SSN is optional at KYC time, so limits are tiered based on whether an SSN was submitted: | US Customers | 24-Hour Limit | 7-Day Limit | | - | - | - | | Individual customers without SSN | \$2,999.99 | \$15,000.00 | | Individual customers with SSN | \$5,000.00 | \$15,000.00 | There is no EDD widget for USD. For higher limits, the client requests an EDD link via the same async Partner URL pattern as KYC (`POST` → poll until `status: ACTIVE` → receive `url`): * **If an SSN was already submitted** at KYC, the user is taken directly to a broader questionnaire with a Persona link, where they upload the relevant supporting documents. * **If no SSN was submitted**, the first EDD pass collects the SSN; the second pass then runs the actual EDD process (questionnaire and document upload). ## Related pages # Fetching Transaction Status Source: https://guides.saber.money/onramp/operations/fetch-status GET the buy transaction endpoint to retrieve an onramp order's status, amounts, fees, payment method, network, and withdrawal details. Fetch details of a completed onramp transaction with a cryptocurrency withdrawal — fees, payment method, status, and more. ### Endpoint **URL**: `https://api.saber.money/api/v2/wallet/w/transaction/buy/{transaction_id}`
**Method**: `GET` *** ### Headers | Header | Description | | - | - | | `X-Timestamp` | A UNIX timestamp for the request. Used for generating a secure signature. | | `X-Client-Id` | Your unique client ID is provided by Saber Money. | | `X-Request-Id` | A unique identifier for the request (optional but recommended). | | `X-User-Id` | The unique ID of the user whose transaction is being fetched. | | `X-Secret-Key` | Your API secret key for authenticating the request. | *** ### Request Parameters | Parameter | Description | Example Value | | - | - | - | | `transaction_id` | The unique identifier of the transaction to fetch details for. | `50232ef8-5d3f-4d04-9889-ad963ef34ff1` | **Sample request:** ```bash theme={null} curl --location 'https://api.saber.money/api/v2/wallet/w/transaction/buy/50232ef8-5d3f-4d04-9889-ad963ef34ff1' \ --header 'X-Timestamp: 1705491300' \ --header 'X-Client-Id: CLIENT_ID_HERE' \ --header 'X-Request-Id: hggqwei' \ --header 'X-User-Id: USER_ID_HERE' \ --header 'X-Secret-Key: SECRET_KEY_HERE' ``` ### Response **Sample response:** ```json theme={null} { "success": true, "data": { "id": "50232ef8-5d3f-4d04-9889-ad963ef34ff1", "transaction_type": "ONRAMP_AND_WITHDRAW", "status": "ONRAMP_COMPLETED", "crypto_symbol": "USDT", "fiat_symbol": "INR", "source_id": "", "payment_method": "gpay_intent", "withdraw_address": "0x1f4d75864f1ceb33a75e26ded3393caa55f87d8e", "network": "BSC", "crypto_amount": 20, "fiat_amount": 1868.18, "base_price": 92.53, "final_price": 93.41, "total_fee": 0.19, "fee_currency": "USDT", "fee_breakup": { "platform_fee": 0, "network_fee": 0.19, "client_fee": 0, "discount": 0, "tax_on_fee": 0, "tds": 0 }, "created_at": 1705491300000, "updated_at": 1705491301000, "txn_hash": "" } } ``` *** ### Key Response Fields | Field | Description | Example Value | | - | - | - | | `id` | Unique identifier of the transaction. | `50232ef8-5d3f-4d04-9889-ad963ef34ff1` | | `transaction_type` | Type of transaction. Returns `ONRAMP_AND_WITHDRAW` for standard onramp users; may return `CRYPTO_BUY` for migrated users on the same endpoint. | `ONRAMP_AND_WITHDRAW` | | `status` | Current status of the transaction. | `ONRAMP_COMPLETED` | | `crypto_symbol` | Cryptocurrency involved in the transaction. | `USDT` | | `fiat_symbol` | Fiat currency used in the transaction. | `INR` | | `payment_method` | Payment method used (e.g., `gpay_intent`). | `gpay_intent` | | `withdraw_address` | The wallet address to which the cryptocurrency was withdrawn. | `0x1f4d75864f1ceb33a75e26ded3393caa55f87d8e` | | `network` | Blockchain network used for the transaction. | `BSC` | | `crypto_amount` | Amount of cryptocurrency involved. | `20` | | `fiat_amount` | Amount of fiat currency used. | `1868.18` | | `total_fee` | Total fees charged for the transaction. | `0.19` | | `fee_currency` | Currency in which the fees were charged. | `USDT` | | `fee_breakup` | Detailed breakdown of the fees (e.g., platform, network, client fees). | `{ "network_fee": 0.19 }` | *** ### Notes 1. **Transaction Status**: Common statuses include `CREATED`, `ONRAMP_INITIATED`, `ONRAMP_COMPLETED`, `ONRAMP_INCONSISTENT`, `PROCESSING`, `COMPLETED`, `FAILED`, `REFUND_INITIATED`, `REFUND_COMPLETED`, and `REFUND_FAILED`. Use this field to determine the progress of the transaction. (The withdrawal-in-progress phase is reported as `PROCESSING`.) 2. **Fee Details**: `total_fee` gives the overall fee, while `fee_breakup` provides granular insights like platform and network fees. 3. **Network and Wallet Address**: Ensure that the `withdraw_address` and `network` match the user's intended withdrawal details. 4. **Transaction Type**: `transaction_type` will be `ONRAMP_AND_WITHDRAW` for most accounts but may be `CRYPTO_BUY` for migrated accounts; clients should accept both values. 5. **Use Cases**: Monitor the transaction progress; provide detailed insights to users about their transaction history. **API Reference:** [Fetch Buy Transaction Status](https://saber-money.readme.io/reference/fetchbuytransactionstatus) # Fund Management Source: https://guides.saber.money/onramp/operations/fund-management Managing Pool Account funds for onramp — how funds are added post Pool Buy, using that balance for the second leg of a Pool Sell, or withdrawing it externally. For merchants running Pool Buy transactions, converted stablecoin from onramp lands in your Pool Account rather than an individual wallet. This page covers how that balance is added and what you can do with it afterward. ## How balance is added Each time a Pool Buy transaction completes, the fiat collected from the user is converted to stablecoin at the transacted rate, and that amount is credited to your Pool Account balance. This happens automatically as part of the transaction — no separate deposit step is needed for Pool Buy proceeds specifically. ## Checking balance ```http theme={null} GET /api/v1/wallet/balance?account=SPOT&symbol=USDT ``` ```json theme={null} { "success": true, "data": { "coin_info": { "USDT": { "coin": "USDT", "name": "TetherUS", "available_balance": 76.7, "available_balance_usd": 76.7, "total_balance": 76.7, "total_balance_usd": 76.7, "withdraw_enable": true, "deposit_enable": true } }, "available_balance": 76.7, "total_balance": 76.7 } } ``` ## Using the balance for Pool Sell (offramp) If you're running both onramp and offramp through the same Pool Account, stablecoin credited from Pool Buy becomes available balance that Pool Sell transactions can draw from directly — see [Available Transaction Models](/onramp/available-flows) for how the two link, and [Offramp Fund Management](/offramp/operations/fund-management) for how that balance is deducted on the sell side. ## Withdrawing externally If you'd rather move accumulated Pool Account balance out to an external wallet instead of using it for offramp: ```http theme={null} POST /api/v2/pool/wallet/withdraw ``` ```json theme={null} { "crypto_symbol": "USDT", "amount": "", "network": "BSC", "wallet_address": "" } ``` Withdrawals can also be initiated directly from the dashboard — see [Dashboard Overview](/client/dashboard/overview). # Initiating Transaction Source: https://guides.saber.money/onramp/operations/initiate-transaction Initiates a fiat-to-crypto buy via /wallet/crypto/buy, sending crypto to an external wallet, with quote_id price-lock, parameters, and response fields. Initiate a buy transaction converting fiat to cryptocurrency, withdrawn to an external wallet specified by the user/merchant. ### Endpoint `https://api.saber.money/api/v1/wallet/crypto/buy/` *** ### Sample Request **Sample request:** ```bash theme={null} curl --location 'https://api.saber.money/api/v1/wallet/crypto/buy/' \ --header 'X-Timestamp: {{timestamp}}' \ --header 'X-Client-Id: •••••••' \ --header 'X-Request-Id: qsfe' \ --header 'X-User-Id: •••••••' \ --header 'X-Secret-Key: •••••••' \ --data '{ "from_currency": "INR", "to_currency": "USDT", "to_amount": 10, "source_id": "c41f7d27-781c-41da-b74c-278fe7202af5", "payment_method": "bank_transfer", "crypto_wallet_address": "0xb84b44140b9c9d2a32707ba35332969c65bc918d", "network": "BSC" }' ``` **Sample response:** ```json theme={null} { "success": true, "data": { "fiat_symbol": "INR", "crypto_amount": 10, "source_id": "c41f7d27-781c-41da-b74c-278fe7202af5", "id": "74cbe2f9-4e2a-4bd2-92d2-1a139e7cdcf9", "uuid": "74cbe2f9-4e2a-4bd2-92d2-1a139e7cdcf9", "crypto_symbol": "USDT", "crypto_wallet_address": "0xb84b44140b9c9d2a32707ba35332969c65bc918d", "network": "BSC", "created_at": 1684394172000, "bank_transaction_id": "", "fiat_amount": 976.11, "status": "INCOMING_PAYMENT_INITIATED", "payment_method": "bank_transfer", "exchange_rate": 97.61, "tag": "" } } ``` ### Required Parameters | Parameter | Type | Description | | - | - | - | | `from_currency` | `string` | The fiat currency symbol. Supported values: `INR`, `EUR`, `GBP`. Required unless `quote_id` is supplied. | | `to_currency` | `string` | The cryptocurrency symbol. Currently supported: `USDT` (other tokens supported on request). Required unless `quote_id` is supplied. | | `crypto_wallet_address` | `string` | The destination crypto wallet address where the purchased crypto will be sent. | | `from_amount` or `to_amount` | `number` | Specify either the amount of fiat to convert (`from_amount`) or the desired crypto amount (`to_amount`). | | `network` | `string` | The blockchain network. Example: `BSC` (Binance Smart Chain). Required unless `quote_id` is supplied. | ### Optional Parameters | Parameter | Type | Description | | - | - | - | | `source_id` | `string` | IBAN of sender in case of "sepa\_transfer" payment method | | `bank_transaction_id` | `string` | UTR of transaction in case of "upi\_transfer" and "bank\_transfer" "IBAN\_Transaction-Reference" in case of "sepa\_transfer" | | `payment_method` | `string` | The payment method to be used. Example: `bank_transfer`. Required unless a `quote_id` (price-lock flow) is provided; if `quote_id` is omitted, an empty or missing `payment_method` returns a 400 "missing or invalid parameters" error. | | `uuid` | `string` | Merchant's custom transaction ID (must be a valid UUID). If provided, this value is used as the transaction's identifier; otherwise a UUID is auto-generated. | | `tag` | `string` | Additional metadata or tags to associate with the transaction for tracking or categorization. | | `quote_id` | `string` (UUID) | Optional. ID of a previously locked price quote. When provided, the request uses the price-lock flow: `from_currency`, `to_currency`, `payment_method`, and `network` become optional (they are taken from the locked quote) and are not validated at request time. `crypto_wallet_address` and one of `from_amount`/`to_amount` are still required. | ### Response Details | Item | Type | Description | | - | - | - | | `success` | `boolean` | Indicates whether the API call was successful or not. | | `fiat_symbol` | `string` | The symbol of the fiat currency used in the transaction (e.g., `INR`, `EUR`, `GBP`). | | `crypto_amount` | `number` | The amount of cryptocurrency purchased. | | `source_id` | `string` | Echoes the `source_id` from the request, if provided. | | `id` | `string` | The unique identifier for the transaction generated by the system. | | `uuid` | `string` | Duplicate of `id`, returned for backward compatibility. Always equals the `id` value. | | `crypto_symbol` | `string` | The symbol of the cryptocurrency purchased (e.g., `USDT`). | | `crypto_wallet_address` | `string` | The wallet address where the purchased cryptocurrency will be sent. | | `network` | `string` | The blockchain network used for the transaction (e.g., `BSC`). | | `failure_desc` | `string` | A description of the failure if the transaction was unsuccessful. | | `created_at` | `number` | The timestamp when the transaction was created, in milliseconds since the Unix epoch. | | `bank_transaction_id` | `string` | The identifier for the bank transaction, if applicable. | | `fiat_amount` | `number` | The amount of fiat currency used in the transaction. | | `status` | `string` | The current state of the transaction. A newly created order returns `INCOMING_PAYMENT_INITIATED` (transitioning to `INCOMING_PAYMENT_PROCESSING`). Possible values include `INCOMING_PAYMENT_INITIATED`, `INCOMING_PAYMENT_PROCESSING`, `INTERNAL_CONVERSION_COMPLETED`, `OUTGOING_PAYMENT_INITIATED`, `OUTGOING_PAYMENT_PROCESSING`, `COMPLETED` (terminal success), and `FAILED` / `OUTGOING_PAYMENT_FAILED` (terminal failure). | | `payment_method` | `string` | The payment method used for the transaction (e.g., `bank_transfer`). | | `failure_code` | `string` | A code indicating the reason for failure, if applicable. | | `exchange_rate` | `number` | The exchange rate applied for the transaction. | | `tag` | `string` | Any metadata or tags associated with the transaction, as provided in the request. | | `payment_intent` | `string` | Payment intent link, returned only for payment flows that require it (omitted when empty). | | `eta` | `number` | Estimated time (in seconds) for the transaction, returned only when applicable (omitted when empty). | | `redirect_url` | `string` | Redirect URL the user must be sent to in order to complete payment, returned only for redirect-based flows (omitted when empty). | | `links` | `object` | Map of related action links (e.g. for hosted payment flows), returned only when applicable (omitted when empty). | ## Key Notes * Pass a custom transaction ID via `uuid` (must be a valid UUID) for tracking on your own systems. * Supported fiat: INR, EUR, GBP. Supported crypto: USDT (others available on request). **API Reference:** [Create Buy Transaction](https://saber-money.readme.io/reference/createbuytransaction) # Order lifecycle and Webhooks Source: https://guides.saber.money/onramp/operations/lifecycle Webhook statuses and sample payloads for the Buy, Pool Buy, and External Buy onramp flows. This page describes the webhook events and order statuses for the onramp flows: Buy, Pool Buy, and External Buy. ## Buy `transaction_status` values sent: `COMPLETED`, `FAILED`, `CANCELLED` ```json theme={null} { "transaction_id": "7c1e4b2a-9f3d-4a61-8e2b-1d5c6f7a8b90", "client_id": "3a9f0c12-6b4e-4d7a-9c21-5e8f7a6b4c3d", "user_id": "e2d4c6b8-1a3f-4e5d-8c7b-9a0f1e2d3c4b", "event": "BUY", "fiat_amount": 10000, "usd_amount": 113.25, "transfer_type": "BANK_TRANSFER", "invested_at": "2026-10-09T10:15:30Z", "bank_reference_id": "UTR000000001234", "transaction_status": "COMPLETED" } ``` ## Pool Buy `transaction_status` values sent: `CREATED`, `COMPLETED`, `FAILED`. Same body as Buy; `event` is `BUY`. ```json theme={null} { "transaction_id": "5b8d2e1f-3c4a-4b7e-9d6f-2a1b3c4d5e6f", "client_id": "3a9f0c12-6b4e-4d7a-9c21-5e8f7a6b4c3d", "user_id": "e2d4c6b8-1a3f-4e5d-8c7b-9a0f1e2d3c4b", "event": "BUY", "fiat_amount": 50000, "usd_amount": 566.25, "transfer_type": "BANK_TRANSFER", "invested_at": "2026-10-09T11:02:10Z", "bank_reference_id": "UTR000000005678", "transaction_status": "CREATED" } ``` ## External Buy `status` values sent: | **Status** | **Description** | | - | - | | `ONRAMP_COMPLETED` | Fiat received, crypto being sent. | | `COMPLETED` | Crypto withdrawal to the specified wallet address is completed. | | `FAILED` | The order failed. | ```json theme={null} { "id": "9a7b6c5d-4e3f-4a2b-8c1d-0e9f8a7b6c5d", "uuid": "9a7b6c5d-4e3f-4a2b-8c1d-0e9f8a7b6c5d", "status": "COMPLETED", "fiat_symbol": "INR", "crypto_symbol": "USDT", "fiat_amount": 10000, "crypto_amount": 113.25, "payment_method": "UPI", "exchange_rate": 88.3, "crypto_wallet_address": "TXyz...mock...Wallet01", "created_at": 1791540930000, "source_id": "src_mock_001", "bank_transaction_id": "UTR000000009012", "tag": "", "network": "TRC20", "event": "CRYPTO_BUY", "user_id": "e2d4c6b8-1a3f-4e5d-8c7b-9a0f1e2d3c4b", "txn_hash": "0xmocktxnhash000000000000000000000000000000000000000000000000abcd" } ``` On `FAILED`, `failure_code` and `failure_desc` are also included. # Fetching Quote Source: https://guides.saber.money/onramp/operations/quote The buy-quote API returns real-time onramp prices, fees, and amounts from fiat currency, crypto token, network, and amount inputs. The Fetching Buy Quote API enables merchants to retrieve the buy price for specific crypto assets in real time. This is essential for providing accurate quotes to users during the onramp process. Merchants can use this API to get the buy price for the required crypto assets in exchange for supported fiat currencies on the required blockchain network. ## Functionality Overview The API calculates the price based on: 1. The fiat currency (from\_currency). 2. The crypto token to be purchased (to\_currency). 3. The blockchain network (network). 4. Either the fiat amount to be converted (from\_amount) or the crypto amount required (to\_amount). This flexibility ensures real-time, accurate pricing tailored to merchant and user needs. ## Sample Request Here’s an example API request. **Sample request:** ```bash theme={null} curl --location 'https://api.saber.money/api/v2/wallet/w/quote?from_currency=INR&to_currency=USDT&network=BSC&to_amount=55.38' \ --header 'X-Timestamp: {{timestamp}}' \ --header 'X-Client-Id: •••••••' \ --header 'X-Request-Id: 3456' \ --header 'X-User-Id: •••••••' \ --header 'X-Secret-Key: •••••••' ``` **Sample response:** ```json theme={null} { "success": true, "data": { "from_currency": "INR", "to_currency": "USDT", "network": "BSC", "from_amount": 5036.81, "pre_fee_to_amount": 55.49, "to_amount": 55.38, "base_price": 90.77, "final_price": 90.95, "total_fee": 0.11, "fee_currency": "USDT", "fee_breakup": { "platform_fee": 0.111, "network_fee": 0, "client_fee": 0, "discount": 0, "tax_on_fee": 0, "tds": 0 }, "transaction_type": "EXTERNAL_BUY" } } ``` > **Note:** For payment methods that incur a banking/partner fee, the `fee_breakup` object may also include an additional `banking_fee` key (a number). It is omitted from the response when zero. ## Required Parameters | Parameter | Description | Supported Values | Required | | - | - | - | - | | from\_currency | The fiat currency to be converted | INR, EUR, GBP | Yes | | to\_currency | The crypto asset to be purchased | USDT, USDC | Yes | | network | The blockchain network for the transaction | All major networks (Matic preferred for cost-efficiency) | Yes | | from\_amount | Amount of fiat currency to be converted | Numeric value | Either this or to\_amount required | | to\_amount | Amount of crypto asset to be purchased | Numeric value | Either this or from\_amount required | | payment\_method | The payment method used for the transaction; influences spread/fees in the returned price. Should match the payment\_method used at order creation. | A supported payment method code (e.g. sepa\_transfer) — see "Fetching Payment Methods" | No | | payment\_instrument\_id | The payment instrument used for the transaction | String | No | | is\_first\_party | Whether the transaction is a first-party transaction | Boolean (true/false) | No | ## Additional Notes * Preferred Network: While all major networks are supported, Matic is recommended due to lower costs. * Mandatory Parameters: Ensure all required parameters are included to avoid errors. For more details on the quote, refer to the Fetch Buy Quote API Reference [here](https://saber-money.readme.io/reference/fetchbuyprice). # Overview Source: https://guides.saber.money/onramp/overview Saber's onramp offerings — supported countries, currencies, tokens, transaction models, and where to find flow-specific guides. Onramp lets your users convert fiat into stablecoin, either credited to your Pool Account or sent to an external wallet address. Both USDT and USDC are supported as settlement assets for onramp. ## Transaction flow ```mermaid theme={null} flowchart LR A[View payment method & beneficiary details] --> B[Create buy order] B --> C[Convert fiat to crypto] C --> D[Deposit crypto to user's account] D --> E[Wait for completed callback] ``` ## Supported countries and currencies Saber currently supports onramp across multiple corridors, including the Eurozone (EUR), the UK (GBP), and the United States (USD), with more added over time. Each currency has its own supported payment rails, KYC requirements, and settlement characteristics. ## Transaction models Onramp transactions follow one of two models: * **Pool Buy (recommended)** — collected fiat is converted to stablecoin and deposited into your merchant Pool Account. * **External Buy** — collected fiat is converted to stablecoin and sent to a wallet address and network you specify. See [Transaction Models](/onramp/available-flows) for a full comparison, including how Pool Buy can feed liquidity into offramp Pool Sell transactions. ## Currency-specific flows Each supported currency has its own end-to-end walkthrough covering user creation, KYC, any required account linkage, and the buy flow itself: ## Operations reference For the underlying operations shared across currencies — fetching quotes, initiating transactions, checking status, funding/withdrawal, and order lifecycle/webhooks — see [Onramp Operations](/onramp/operations/quote). # Through API Endpoints Source: https://guides.saber.money/user/bank-account/api POST endpoint for merchants to add an Indian user's off-ramp bank account, covering headers and body parameters. Deletion uses a separate DELETE endpoint. This API allows merchants to add a bank account for a user. It is primarily used for Indian users who require a bank account to perform off-ramp operations. To remove an account, use the Delete Bank Account endpoint (`DELETE /api/v2/user/bank_account`). ## Endpoint **URL:** `https://api.saber.money/api/v2/user/bank_account`
**Method:** `POST` ### Headers | Header | Description | | - | - | | `X-Timestamp` | A UNIX timestamp for the request. Used for generating a secure signature. | | `X-Api-Key` | Your unique API key (client ID) provided by Saber Money. | | `X-Signature` | HMAC-SHA256 signature authenticating the request. [Authentication: Configuring Your Keys](/getting-started/auth) | | `X-Request-Id` | A unique identifier for the request (optional but recommended). | | `X-User-Id` | The unique ID of the user whose bank account is being added. | ### Request Body Parameters | Parameter | Type | Description | Mandatory | | - | - | - | - | | `account_number` | `String` | The user's bank account number. | Yes (for India) | | `account_holder_name` | `String` | Full name of the account holder. | Yes | | `bank_code` | `String` | The IFSC code of the user's bank branch (routing code). | Yes (for India) | | `account_type` | `String` | The type of bank account (`SAVINGS`, `CURRENT`, `CHECKING`). | Optional | | `country` | `String` | ISO 3166-1 alpha-3 country code. Use `IND` for Indian accounts. | Yes | ### Sample request ```bash theme={null} curl --location 'https://api.saber.money/api/v2/user/bank_account' \ --header 'X-Timestamp: 1705491300' \ --header 'X-Api-Key: CLIENT_ID_HERE' \ --header 'X-Signature: SIGNATURE_HERE' \ --header 'X-Request-Id: 123456789876' \ --header 'X-User-Id: USER_ID_HERE' \ --data '{ "account_number": "1234567890", "account_holder_name": "John Doe", "bank_code": "SBIN0004855", "account_type": "SAVINGS", "country": "IND" }' ``` ## Deleting a bank account To delete a bank account, call the Delete Bank Account endpoint: **URL:** `https://api.saber.money/api/v2/user/bank_account`
**Method:** `DELETE` ```bash theme={null} curl --location --request DELETE 'https://api.saber.money/api/v2/user/bank_account' \ --header 'X-Timestamp: 1705491300' \ --header 'X-Api-Key: CLIENT_ID_HERE' \ --header 'X-Signature: SIGNATURE_HERE' \ --header 'X-Request-Id: 123456789876' \ --header 'X-User-Id: USER_ID_HERE' \ --data '{ "bank_id": "550e8400-e29b-41d4-a716-446655440000", "reason": "User requested deletion" }' ``` ## Notes * This API is mandatory for Indian users after KYC approval to enable off-ramp transactions. * Ensure the `bank_code` (IFSC code) is valid and corresponds to the user's bank branch. * Use unique `Request-Id` values for tracking each request. * For NRE accounts, `account_holder_name` comes back empty. This is expected — the penny-drop response for an NRE account does not include the account holder name, it only confirms whether the account is NRE. **API Reference:** [Add/Delete Bank](https://saber-money.readme.io/reference/adddeletebankaccount) # Overview Source: https://guides.saber.money/user/bank-account/overview Linking a user's bank account to pull funds for onramp or deliver payouts for offramp — the available linking methods and how they vary by flow. A user's bank account is what connects Saber to real-world money movement on their behalf. Depending on the flow, a linked bank account is used to either **pull** funds from the user or **deliver** funds to them. Bank account linkage is the third step in most flows: **Create User → KYC → Bank Account Linkage → Transaction.** ## Why a bank account gets linked On offramp, the linked bank account is the payout destination — once a sell transaction is confirmed, fiat is sent there. On some onramp flows, the linked bank account is the funding source money is pulled from to convert into stablecoin. ## Ways to link a bank account There's no single method used everywhere — which one applies depends on the corridor and the flow. The client collects the user's bank details directly (account number, bank code, IBAN, sort code, etc., depending on the country) and submits them via a direct API call. This is the most common method for offramp corridors, where Saber needs a specific payout destination on file. See [Bank Account — Through API](/user/bank-account/api) for the request format and country-specific field requirements. The user is redirected to a Saber-hosted flow where they authenticate with their bank directly (for example, via Plaid) and authorize the link themselves. This is used where a stronger, verified connection to the bank is required — such as onramp flows that later pull funds from that account. Append `redirect_url=` as the last query parameter to return the user to your app when they exit the flow. See [Bank Account — Through Hosted Widget](/user/bank-account/widget) for integration details. Not every flow has a standalone bank-linkage step. Some onramp payment methods (for example, an Open Banking redirect) collect and authorize the bank connection at the moment of payment instead of as a separate upfront step. Where that's the case, it's called out on that flow's own page — see the currency-specific guides below. ## Approval and validation Once submitted, a bank account typically enters a `PENDING` state before becoming `APPROVED`. How that approval happens differs by country: * **Instant approval** — the account is validated and approved immediately, with no further action needed. * **Penny-drop verification** — a small verification transfer is made to the account to confirm it's valid and belongs to the named holder before it's approved. Account limits also vary — some corridors cap the number of active or approved bank accounts a single user can hold at once. Exact approval behavior and limits are documented per corridor on its flow page, since they differ from country to country. ## Webhooks Bank account status changes (for example, moving from `PENDING` to `APPROVED`, or a validation failure) can be pushed to your configured webhook URL instead of requiring you to poll. See [Bank Account — Webhooks](/user/bank-account/webhooks) for the event payloads. ## Corridor-specific details Exact mandatory fields, linking method, and approval behavior are documented on each flow's own guide: # Webhooks Source: https://guides.saber.money/user/bank-account/webhooks Bank Account Status webhooks POST the bank_account.status.updated event payload, signature secret, and APPROVED, REJECTED, DELETED statuses. Bank Account Status webhooks notify you in real time when a user's bank account status changes post-verification, so you can track readiness without polling. Configure the endpoint in your dashboard to receive events via HTTP `POST`. Only one webhook URL is supported — transaction, bank status, and KYC status events all go to the same URL. ## Webhook payload ```json theme={null} { "event": "bank_account.status.updated", "data": { "user_uuid": "9cf0ca7c-9176-426d-b25f-62eed795b46a", "bank_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "account_number": "XXXXXXXX1234", "account_holder_name": "JOHN DOE", "routing_code": "HDFC0001234", "routing_type": "IFSC", "status": "APPROVED", "is_default": true, "message": "Congratulations! Your bank account is verified", "created_at": "2026-04-28T10:30:00Z", "updated_at": "2026-04-28T11:00:00Z", "is_nre": false, "iban": "GB82WEST12345698765432", "country": "IN" } } ``` ## Events | Event name | Description | | - | - | | `bank_account.status.updated` | Triggered whenever the status of a bank account changes | ## Statuses Webhooks are sent only when the bank account `status` in the payload is one of the following: | Status | Description | | - | - | | `APPROVED` | Bank account verified and approved for use | | `REJECTED` | Verification rejected (e.g. compliance or validation) | | `NAME_REJECTED` | Rejected because the account holder name did not pass checks | | `DELETED` | Bank account was deleted or removed from the system | | `DELETED_BY_USER` | End user removed the bank account | ## Security All webhook events will be signed using a secret key obtained from the Saber dashboard. # Through Saber Widget Source: https://guides.saber.money/user/bank-account/widget Integrate the Saber Bank Addition Widget for Plaid-linked deposit accounts via HMAC-SHA256 signature, redirect URL parameters, and sandbox endpoints. The Saber Bank Addition Widget guides your users through adding a deposit bank account. Required for flows like USD onramp, where the bank is linked via Plaid. ## Steps for Bank Web Widget **Prerequisite:** 1. **Create the User:** Ensure the user is initialised in the Saber system with proper phone and email. 2. **User KYC:** Ensure the user KYC is approved with Saber. ### Step 1: Generate the signature (secret) for the Widget For each session, a unique secret needs to be generated every time. ```javascript theme={null} // Variables (these should be securely stored and handled) var clientId = 'YOUR_CLIENT_ID'; // Replace with your actual client ID var clientSecret = 'YOUR_CLIENT_SECRET'; // Replace with your actual client secret var user_id = 'USER_ID'; // Replace with the user's ID received when creating the user in Step 2 // Step 1: Generate timestamp var timestamp = Math.floor(Date.now() / 1000).toString(); // Step 2: Create the signature string var sigString = clientId + timestamp + "sdk" + user_id; // Step 3: Generate the HMAC-SHA256 signature var secret = CryptoJS.HmacSHA256(sigString, clientSecret).toString().toUpperCase(); // The 'secret' can now be used to authenticate the SDK request ``` ### Step 2: Generate redirect URL Once the secret is created for the user, the following query parameters are **required** to be passed along in the base URL to create the link: | Query parameter | Description | | - | - | | `API Key(client_id)` | Provided by Saber | | `user_id` | When creating the user, the UUIDv4 will be generated for the user | | `timestamp` | The timestamp of the payment request (used at the time of creating the secret) | | `signature(secret)` | The secret generated in step 1 | Optionally, you can also pass: | Query parameter | Description | | - | - | | `redirect_url` | Where the user is sent once the Plaid bank linking flow finishes. Must be URL-encoded and appended as the **last** query parameter. | **The Base URL** | Environment | Link | | - | - | | Production | [https://app.saber.money/bank/add](https://app.saber.money/bank/add) | | Sandbox | [https://app.sandbox.saber.money/bank/add](https://app.sandbox.saber.money/bank/add) | **Example of a full link** ```text theme={null} https://app.sandbox.saber.money/bank/add?client_id=d951b040-ecb0-432b-ae3c-2ae7d2d19987&user_id=d951b040-ecb0-432b-ae3c-2ae7d2d1998×tamp=1687276964&secret=CE1B5BD087BA408C2AFF01B00595007858DF496D3468CE3307CB1A7966DDC265 ``` > 📘 > > Does the URL expire? > > The URL validity is **10 minutes** from the creation of the secret. ### Step 3: Redirect your user to the link Once the URL is generated, redirect your user to the link, and the user will see the Plaid Bank addition screen. #### Returning the user to your app To bring the user back into your own app once they exit the Plaid flow, append `redirect_url` as the last query parameter: ```text theme={null} ?redirect_url= ``` Full example: ```text theme={null} https://app.sandbox.saber.money/bank/add?client_id=d951b040-ecb0-432b-ae3c-2ae7d2d19987&user_id=d951b040-ecb0-432b-ae3c-2ae7d2d1998×tamp=1687276964&secret=CE1B5BD087BA408C2AFF01B00595007858DF496D3468CE3307CB1A7966DDC265&redirect_url=https%3A%2F%2Fyourapp.com%2Fbank%2Fcomplete ``` The value must be URL-encoded, otherwise its own query string is parsed as part of the widget URL. If `redirect_url` is omitted, the user stays on the Saber-hosted page after finishing. A redirect only signals that the user left the flow — it is not a confirmation that the account was linked or approved. Confirm the outcome from the [bank account webhook](/user/bank-account/webhooks) or by fetching the user's bank accounts. # Overview Source: https://guides.saber.money/user/kyc/overview Compares Saber's three KYC methods (Sharing, Widget, Partner URL), per-flow currency support, required documents, approval, and bank account addition. KYC (Know Your Customer) verifies user identity before they can onramp or offramp, in line with local and international regulatory standards. ## Types of KYC You verify the user yourself and submit the details to Saber for record-keeping — Saber doesn't re-verify. Indian users still need a bank account added via API even when onboarded through KYC Sharing. See [KYC Sharing](/user/kyc/sharing) for the full field reference. Saber handles document collection, verification, and status updates end-to-end, including automated bank account addition for Indian users (this automation can be disabled). Append `redirect_url=` as the last query parameter to return the user to your app when they exit the flow. See [KYC Hosted Widget](/user/kyc/widget) for setup. Saber's partner handles verification. You generate a KYC URL via an endpoint and redirect the user to it. Mandatory for USD Onramp — completed via Persona. USD doesn't support KYC Sharing or the KYC Widget. Because the returned URL is a Persona URL, the redirect-back parameter here is `redirect-uri=`. See [Partner URL](/user/kyc/partner-url) for the full reference. ## Supported approach by flow | Flow | Currency | Supported approach | | - | - | - | | Offramp | INR | KYC Widget, KYC Sharing | | Offramp | PHP, IDR | KYC not required | | Offramp | EUR, GBP | KYC Widget, KYC Sharing | | Onramp | USD | KYC Partner URL | | Onramp | EUR, GBP | KYC Widget, KYC Sharing | This table lists PHP and IDR offramp as not requiring KYC. This conflicts with the currency-specific PHP Offramp and IDR Offramp guide pages, which both currently document a mandatory KYC step. This discrepancy needs to be resolved with engineering/product before either page is published — don't treat one as authoritative over the other yet. ## Steps Create the user in Saber. See [User Creation](/user/registration). Submit details via API (KYC Sharing), automate it via the Widget, or generate a URL via the Initiate KYC API (Partner URL) — whichever method the flow supports. ## Key points Aadhaar + PAN for Indian users. For everyone else: a passport, driver's license, or locally accepted national ID, plus proof of address. Only KYC-approved users can transact. Mandatory for Indian users after KYC approval — automated via the Widget, manual via API for KYC Sharing. Optional for non-Indian users depending on the flow. ## Related pages # KYC Partner URL Source: https://guides.saber.money/user/kyc/partner-url Generate a partner KYC URL via the initiate KYC endpoint for USD onramp flows, then poll status. Saber relies on external partners to offer services in certain geographies. This comes with specific KYC requirements, mostly through a partner URL. Currently, the KYC Partner URL is required for **USD Onramp** flows. You need to initiate KYC using an API endpoint, which returns the KYC URL. This URL needs to be shared with the end user to submit the required documents and proceed with KYC. ## Steps for generating KYC URL 1. Create a User with the respective country's phone and email. 2. **Generate URL:** The URL can be generated using the initiate KYC endpoint. 3. **Fetch KYC Status:** Use the GET User endpoint to fetch KYC status. ### Sample request ```bash theme={null} curl --location 'https://api.saber.money/api/v2/user/kyc/initiate' \ --header 'X-Timestamp: 1756203151' \ --header 'X-Api-Key: 0739df4c-b22b-4874-8ca7-33dc89a832da' \ --header 'X-Signature: 161AF50C3A6880583DFAE2D4AE1002734C60ACD2285057664A6C29FF83922DD6' \ --header 'X-Request-Id: 123456789876' \ --header 'X-User-Id: e3cc357f-453d-44f0-b1c1-859ec229312c' \ --header 'Content-Type: application/json' \ --data '{ "country": "USA" }' ``` ## Returning the user to your app The partner KYC URL returned by the initiate endpoint is a **Persona** hosted URL. Persona uses `redirect-uri` (hyphenated) rather than `redirect_url`, so append it as the last query parameter on the URL you received: ```text theme={null} &redirect-uri= ``` Full example: ```text theme={null} https://withpersona.com/verify?inquiry-template-id=itmpl_ABC123&environment-id=env_ABC123&reference-id=e3cc357f-453d-44f0-b1c1-859ec229312c&redirect-uri=https%3A%2F%2Fyourapp.com%2Fkyc%2Fcomplete ``` Append the parameter to the URL exactly as returned — don't rebuild or reorder the existing query parameters, as they carry the inquiry and reference IDs. The value must be URL-encoded. A redirect only signals that the user left the flow — it is not confirmation that KYC passed. Confirm the outcome via the GET User endpoint or the [KYC status webhook](/user/kyc/webhooks). **Notes:** * Required for USD onramp flows — no alternative flow is supported. * Use the correct phone number format for the source country (US format) to avoid errors. # KYC Sharing Source: https://guides.saber.money/user/kyc/sharing Merchants submit independently-verified KYC PII and documents to Saber for record-keeping, covering file specifications and Indian-user bank account requirements. KYC Sharing lets you verify users independently and submit their KYC information to Saber for record-keeping — Saber stores it but doesn't re-verify. ## Steps 1. **Create the user** via the [create user API](/user/registration). 2. **Submit KYC information** via the [KYC Sharing API](https://saber-money.readme.io/reference/sharekycdata): * Share KYC PII: legal name, proof of address (document number), proof of identity (document number), country ISO. * Share the supporting document files (see requirements below) — these can be sent async. 3. **Add a bank account for Indian users** once KYC is approved, via the [Update Bank API](https://saber-money.readme.io/reference/adddeletebankaccount). Not required for non-Indian users. KYC is marked approved as soon as PII data is shared. KYC data must be unique per user and match the standard regex format. ## Document Requirements * **Formats:** `.pdf`, `.jpg`, `.png`, `.xml`, `.zip` * **Max size:** 2 MB ### Key APIs | API | Description | Reference | | - | - | - | | **Share KYC Information** | Shares user KYC details with Saber. | [here](https://saber-money.readme.io/reference/sharekycdata) | | **Share KYC Document** | Shares user KYC documents with Saber. | [here](https://saber-money.readme.io/reference/sharekycdocument) | # Webhooks Source: https://guides.saber.money/user/kyc/webhooks Configuring KYC status webhooks that POST kyc.status.update events with PENDING, APPROVED, or REJECTED statuses, signed via dashboard secret. KYC Status webhooks notify you when a user's KYC verification status changes, so you can manage onboarding and trigger next steps. Configure the endpoint in your dashboard to receive events via HTTP `POST`. Only one webhook URL is supported — transaction, bank status, and KYC status events all go to the same URL. ## Webhook payload ```json theme={null} { "event": "kyc.status.update", "data": { "user_uuid": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "kyc_status": "APPROVED", "updated_at": "2026-04-28T12:00:00Z" } } ``` ## Events | Event name | Description | | - | - | | `kyc.status.update` | Triggered when the user’s KYC status updates to a value listed below | ## Statuses Webhooks are sent only when the `kyc_status` in the payload is one of the following: | Status | Description | | - | - | | `PENDING` | KYC is in progress or awaiting verification | | `APPROVED` | KYC completed successfully | | `REJECTED` | KYC was rejected | ## Security All webhook events will be signed using a secret key obtained from Saber dashboard. # KYC Widget Source: https://guides.saber.money/user/kyc/widget Integrating the Saber Money KYC Widget: HMAC-SHA256 signature generation, redirect URL parameters, and region-specific Indian and non-Indian verification flows. The Saber KYC Widget guides your users through KYC verification tailored to their service and region. After registering a user, redirect them to the widget to complete it. Note: for merchants still on the Onramp SDK (deprecated), KYC can also trigger automatically on the user's first transaction. ## Steps for KYC Web Widget **Prerequisite:** Create the user with a valid phone and email. ### Step 1: Generate the signature (secret) for the Widget A unique secret is required for every session. ```javascript theme={null} // Variables (these should be securely stored and handled) var clientId = 'YOUR_CLIENT_ID'; // Replace with your actual client ID var clientSecret = 'YOUR_CLIENT_SECRET'; // Replace with your actual client secret var user_id = 'USER_ID'; // Replace with the user's ID received when creating the user in Step 2 // Step 1: Generate timestamp var timestamp = Math.floor(Date.now() / 1000).toString(); // Step 2: Create the signature string var sigString = clientId + timestamp + "sdk" + user_id; // Step 3: Generate the HMAC-SHA256 signature var secret = CryptoJS.HmacSHA256(sigString, clientSecret).toString().toUpperCase(); // The 'secret' can now be used to authenticate the SDK request ``` ### Step 2: Generate redirect URL Pass these query parameters on the base URL: | Query parameter | Description | | - | - | | `API Key(client_id)` | Provided by Saber | | `user_id` | Generated when creating the user | | `timestamp` | The timestamp used when creating the secret | | `signature(secret)` | The secret generated in Step 1 | | `redirect_url` | *Optional.* Where the user is sent once the KYC flow finishes. Must be URL-encoded and appended as the **last** query parameter. | **Base URL** | Environment | Link | | - | - | | Production | [https://app.saber.money/kyc](https://app.saber.money/kyc) | | Sandbox | [https://app.sandbox.saber.money/kyc](https://app.sandbox.saber.money/kyc) | **Example** ```text theme={null} https://app.sandbox.saber.money/kyc?client_id=d951b040-ecb0-432b-ae3c-2ae7d2d19987&user_id=d951b040-ecb0-432b-ae3c-2ae7d2d1998×tamp=1687276964&secret=CE1B5BD087BA408C2AFF01B00595007858DF496D3468CE3307CB1A7966DDC265 ``` > 📘 > > The URL is valid for **10 minutes** from when the secret is created. ### Step 3: Redirect your user to the link #### Returning the user to your app To bring the user back into your own app once they exit the KYC flow, append `redirect_url` as the last query parameter: ```text theme={null} ?redirect_url= ``` Full example: ```text theme={null} https://app.sandbox.saber.money/kyc?client_id=d951b040-ecb0-432b-ae3c-2ae7d2d19987&user_id=d951b040-ecb0-432b-ae3c-2ae7d2d1998×tamp=1687276964&secret=CE1B5BD087BA408C2AFF01B00595007858DF496D3468CE3307CB1A7966DDC265&redirect_url=https%3A%2F%2Fyourapp.com%2Fkyc%2Fcomplete ``` The value must be URL-encoded, otherwise its own query string is parsed as part of the widget URL. If `redirect_url` is omitted, the user stays on the Saber-hosted page after finishing. A redirect only signals that the user left the flow — it is not a confirmation that KYC passed. Always confirm the outcome from the [KYC status webhook](/user/kyc/webhooks) or by fetching the user. ## KYC Flows The flow is chosen automatically from the phone country ID passed at user creation. ### Indian KYC * **Personal information** * **PAN verification** * **Aadhaar verification** — via Digilocker * **Live selfie** * **Bank account verification** (optional, penny-drop — can be disabled per merchant) ### Indian KYC (Passport) Used where PAN verification isn't required: * **Personal information** * **Passport verification** — in place of PAN * **Live selfie** * **Bank account verification** (optional, same as above) ### Non-Indian KYC * **Country-specific steps** — ID, address verification, and other local requirements. * **Selfie and bank verification** — included where locally mandated or configured. KYC Widget can't be used for USD Onramp — see [KYC Partner URL](/user/kyc/partner-url). # User Limits and EDD Source: https://guides.saber.money/user/limits-edd Explains transaction limits by payment method, the 12,500 USDT post-KYC lifetime limit, and the manual Enhanced Due Diligence process triggered when exceeded. Transaction limits depend on the payment method, transaction type, and payment channel used. ## Lifetime Limit After KYC verification, every user gets daily, weekly, monthly and lifetime limits across all their transaction activity on the platform. Once the limits are breached, the user cannot initiate any further transactions until the limit duration are reset or user's EDD is complete. Please contact your Saber POC to understand the limits set up for you. ## Enhanced Due Diligence (EDD) Exceeding the limit triggers EDD — verifying the user's source of funds and financial background for regulatory compliance. You can refer to get user limits API endpoint to track and monitor user's consumed and remaining limits and trigger the EDD flow accordingly. The EDD process is driven by Saber Web-hosted Widget. Refer to Triggering Limit Upgrade section for more details. # Overview Source: https://guides.saber.money/user/overview The identity and account layer underneath onramp and offramp — user creation, KYC, bank account linkage, wallet operations, user-level limits, and enhanced due diligence. Before any onramp or offramp transaction can be initiated, a user needs to exist in Saber's system and clear a few prerequisite steps. User Operations covers that entire identity and account layer. ## Prerequisites Every flow starts by creating a user record in Saber's system. This gives you a `user_id` that every subsequent action — KYC, bank linkage, transactions — is tied to, letting you map Saber users back to your own internal user records. See [User Creation](/user/registration). Users must complete KYC before transacting. Depending on the corridor, this happens through a hosted widget, KYC Sharing (if you've already collected KYC yourself), or a KYC Partner URL. See [User KYC](/user/kyc/overview). Users need a linked, approved bank account before offramp payouts can go out, or before certain onramp payment methods (like USD's ACH pull) can be used. Depending on the currency, this happens through a direct API call or a hosted widget. See [Bank Account](/user/bank-account/overview). ## Ongoing capabilities Alongside these prerequisites, two things apply continuously to a user's account rather than as a one-time setup step: Each user has a wallet within Saber's system that holds their crypto balance. Wallet operations cover checking balance and, where applicable, depositing or withdrawing from that wallet. Every user has a transaction limit. Once reached, no further transactions can be created until Enhanced Due Diligence (EDD) is completed to raise it — either through a hosted EDD widget or a Partner URL flow, depending on the corridor. ## Related pages # User Creation Source: https://guides.saber.money/user/registration Covers registering merchant users in Saber Money, completing KYC, and linking bank accounts to enable on-ramp and off-ramp transactions. Every user belonging to a merchant must be registered in Saber before they can transact — this is the foundation for all onramp and offramp operations. Two steps are required to process transactions: **creating a user** and **completing KYC**. Some flows also require **adding a bank account**. ## 1. Creating a User Registers the user in Saber and assigns them a wallet for offramp. ```bash theme={null} curl --location 'https://api.saber.money/api/v2/user' \ --header 'X-Timestamp: {{timestamp}}' \ --header 'X-Api-Key: •••••••' \ --header 'X-Signature: •••••••' \ --header 'X-Request-Id: 123456789876' \ --data-raw '{ "user_uuid": "77c4562e-ce47-4054-9d4e-4df69ca61ac2", "client_user_id": "Test ACcount", "email": "kk@mail1.com", "phone": "+918734567891" // E.164 format "+" + country_code + number }' ``` Email and phone are required depending on your client registration config. The wallet is created automatically. **API Reference:** [Register User](https://saber-money.readme.io/reference/registerauser) ## 2. KYC for a User Required before any transaction. Three methods, depending on your flow: * **KYC Sharing via API:** You verify the user yourself and share the approved details with Saber. A bank account may still need to be added separately. * **KYC via Web Widget:** Saber handles verification and bank account addition end-to-end — recommended for a seamless integration. * **KYC via Partner URL:** Required for certain flows that mandate partner KYC — see the KYC section for details. **Get started:** [KYC Sharing](https://guides.saber.money/docs/kyc-sharing) · [KYC SDK](https://guides.saber.money/docs/kyc-sdk) ## 3. Add Bank Account Required for most onramp/offramp flows. Three ways to add one: * **API endpoint** — for INR, EUR, and GBP flows. * **Web widget** — for USD onramp, linked via Plaid. * **Post-KYC prompt** — if using the KYC web widget, users can add their INR bank account right after KYC (enabled by default; contact Saber to disable). ```bash theme={null} curl --location 'https://api.saber.money/api/v2/user/bank_account' \ --header 'X-Timestamp: {{timestamp}}' \ --header 'X-Api-Key: •••••••' \ --header 'X-Signature: •••••••' \ --header 'X-Request-Id: 123456789876' \ --header 'X-User-Id: •••••••' \ --data '{ "account_number": "1234567890", "account_holder_name": "John Doe", "bank_code": "SBIN0004855", "account_type": "SAVINGS", "country": "IND" }' ``` **API Reference:** [Add/Delete Bank](https://saber-money.readme.io/reference/adddeletebankaccount) ## Other User Operations | Operation | Description | | - | - | | **Retrieve User** | Fetch details of a registered user. | | **Update User** *(in development)* | Modify user information. | | **Fetch User Wallet Balance** | Wallet balance for Indian users, to check funds before offramp. | Only KYC-approved users can perform onramp and offramp operations. # Triggering Limit Upgrade and EDD Source: https://guides.saber.money/users/limits-edd/widget Integrate the Saber EDD Web Widget using HMAC-SHA256 signed redirect URLs, iframe or WebView, status states, and event handling. The Saber EDD Web Widget guides users through enhanced due diligence for higher transaction limits and advanced features, once they've completed initial KYC. Requirements vary by geography and merchant configuration — Saber sets these up per merchant. The widget uses HMAC-based authentication: generate a unique signature on your backend for each session, so your API secret never leaves your server. ## Integration Steps ### Prerequisite **Create the User**: Ensure the user is initialised in the Saber system with proper phone, email, and has completed basic KYC verification. ### Step 1: Generate the HMAC Signature A unique signature must be generated dynamically on your secure server backend for each user session: ```javascript theme={null} // Variables (these should be securely stored on your server) var clientId = 'YOUR_CLIENT_ID'; // Replace with your actual client ID var clientSecret = 'YOUR_CLIENT_SECRET'; // Replace with your actual client secret var user_id = 'USER_ID'; // Replace with the user's ID from Saber system // Step 1: Generate timestamp var timestamp = Math.floor(Date.now() / 1000).toString(); // Step 2: Create the signature string var sigString = clientId + timestamp + "sdk" + user_id; // Step 3: Generate the HMAC-SHA256 signature var signature = CryptoJS.HmacSHA256(sigString, clientSecret).toString().toUpperCase(); // The 'signature' can now be used to authenticate the EDD Widget request ``` ### Step 2: Generate Redirect URL Once the signature is created for the user, compile the redirect link with the following query parameters: | Query Parameter | Description | | - | - | | `client_id` (or `api_key`) | Provided by Saber | | `user_id` | The user's UUID from the Saber system | | `timestamp` | The timestamp used when creating the signature | | `signature` (or `secret`) | The HMAC signature generated in step 1 | | `redirect_url` (optional) | URL to redirect user after EDD completion. If not provided, user will see the status screen | **Base URLs:** | Environment | URL | | - | - | | Production | `https://edd.saber.money` | | Sandbox | `https://edd.sandbox.saber.money` | **Example URLs:** With redirect URL (user will be redirected after completion): ```text theme={null} https://edd.sandbox.saber.money?client_id=d951b040-ecb0-432b-ae3c-2ae7d2d19987&user_id=d951b040-ecb0-432b-ae3c-2ae7d1998×tamp=1687276964&signature=CE1B5BD087BA408C2AFF01B00595007858DF496D3468CE3307CB1A7966DDC265&redirect_url=https://yourapp.com/success ``` Without a redirect URL (user will see a Saber status screen after completion): ```text theme={null} https://edd.sandbox.saber.money?client_id=d951b040-ecb0-432b-ae3c-2ae7d2d19987&user_id=d951b040-ecb0-432b-ae3c-2ae7d1998×tamp=1687276964&signature=CE1B5BD087BA408C2AFF01B00595007858DF496D3468CE3307CB1A7966DDC265 ``` ### Step 3: Redirect User to the Widget Once the URL is generated, redirect your user to the link. The user will see the EDD form interface. **Post-Completion Behaviour:** * **With `redirect_url`**: After successful EDD completion, the user will be automatically redirected to the specified URL after 10 seconds. * **Without `redirect_url`**: After completion, the user will remain in the widget and see the EDD status screen showing their verification status. ## Integration Options ### Option 1: Direct Redirect Redirect the user directly to the EDD Widget URL in the same browser tab: ```javascript theme={null} window.location.href = eddWidgetUrl; ``` ### Option 2: Iframe Integration (Recommended) Embed the EDD Widget as an iframe inside your page for a seamless user experience: ```html theme={null} ``` > 📘 For iframe integration, it is recommended to omit the `redirect_url` parameter so users see the status screen inside the iframe rather than being redirected away from your application. ### Option 3: WebView Integration (Mobile Apps) For React Native or native mobile apps, use a standard WebView component: ```javascript theme={null} import { WebView } from 'react-native-webview'; ``` ## EDD Status Flow The EDD Widget supports the following status states: | Status | Description | | - | - | | `UNINITIATED` | EDD process not started | | `INITIATED` | User has started the EDD process | | `RESUBMISSION_REQUESTED` | Additional information required | | `VERIFICATION_IN_PROGRESS` | EDD under review | | `APPROVED` | EDD approved - user can access higher limits | | `REJECTED` | EDD rejected - user cannot proceed | ## Security Considerations ### URL Expiration > ⚠️ **Warning**: The URL validity is 10 minutes from the creation of the signature. Always generate URLs dynamically in real-time just before redirecting users. ### CORS and Origin Validation The widget validates the origin of incoming requests. Ensure your web domain is correctly registered and whitelisted with Saber Money for iframe integrations. ### HTTPS Required All integrations must use HTTPS securely in production environments. ## Error Handling ### Common Error Scenarios 1. **Invalid Signature**: Check timestamp validity and signature generation logic. 2. **Expired URL**: Generate a fresh signature. 3. **User Not Found**: Ensure user is created and exists in the Saber system. 4. **Insufficient Permissions**: Verify user has completed basic KYC. ### Event Timing * **`close`**: Fired when the user clicks the close button or navigates away from the widget. * **`completed`**: Fired when the user successfully submits all required EDD information. * **`error`**: Fired when validation fails, API errors occur, or other issues prevent EDD completion. * **`token_expired`**: Fired when the HMAC signature expires (10-minute validity window). ### Event Handling Best Practices 1. **Always Verify Origin**: Check that events come from the correct Saber Money domain. 2. **Handle All Events**: Implement handlers for all four event types to provide a smooth user experience. 3. **Token Expiration**: For `token_expired`, automatically generate a new signature and reload the widget. 4. **Error Recovery**: For `error` events, provide clear messaging and retry options when appropriate. 5. **User Feedback**: Show appropriate loading states, success messages, and error notifications. 6. **Graceful Closure**: Handle `close` events by returning users to a logical point in your application flow. # Add Dummy Balance [Sandbox Only] Source: https://guides.saber.money/api-reference/wallet-operations/crypto-deposit/add-dummy-balance-sandbox-only openapi-v2.json POST /api/v2/wallet/transaction/create-crypto-deposit-record Creates a dummy crypto deposit record against a user's wallet so you can fund a sandbox user without a real on-chain transfer. Sandbox only, not available in production. Once the record is created, the credited balance is visible via [Get Wallet Balance](/api-reference/wallet-operations/get-wallet-balance). # Create Internal Transfer Source: https://guides.saber.money/api-reference/wallet-operations/crypto-internal-transfer/create-internal-transfer openapi-v2.json POST /api/v1/wallet/internal_transfer Create Internal Transfer **Response field values** | Field | Possible Values | | --- | --- | | `asset` (body) | Coin symbol, e.g. `USDT` | | `from_user_id`, `to_user_id`, `uuid` (body) | UUID strings | # Get List of Internal Transfers (All) Source: https://guides.saber.money/api-reference/wallet-operations/crypto-internal-transfer/get-list-of-internal-transfers-all openapi-v2.json GET /api/v1/wallet/internal_transfer/history/ Get List of Internal Transfers (All) **Response field values** | Field | Possible Values | | --- | --- | | `type` (history item) | `DEBIT`, `CREDIT` | # Get List of Internal Transfers (Single Asset) Source: https://guides.saber.money/api-reference/wallet-operations/crypto-internal-transfer/get-list-of-internal-transfers-single-asset openapi-v2.json GET /api/v1/wallet/internal_transfer/history/USDT Get List of Internal Transfers (Single Asset) **Response field values** | Field | Possible Values | | --- | --- | | `symbol` (path segment) | Coin symbol, e.g. `USDT` | | `type` (history item) | `DEBIT`, `CREDIT` | # Create Crypto Withdraw Transaction Source: https://guides.saber.money/api-reference/wallet-operations/crypto-withdraw/create-crypto-withdraw-transaction openapi-v2.json POST /api/v1/wallet/withdraw/ Create Crypto Withdraw Transaction **Response field values** | Field | Possible Values | | --- | --- | | `network` (query) | e.g. `BSC`, `TRX` | | `data.msg` | e.g. `WITHDRAWAL_REQUEST_SUBMITTED_SUCCESSFULLY` | | `data.data.transaction_type` | e.g. `WITHDRAW` | | `data.data.status` | `CREATED`, `PROCESSING`, `PENDING`, `COMPLETED`, `FAILED`, `CANCELLED`, `REVERTED`, `REJECTED`, `EXPIRED`, `SOURCE_INVALID`, `APPROVAL_REQUIRED`, `SUCCESSFUL`, `REFUND_INITIATED`, `REFUND_COMPLETED`, `REFUND_FAILED` | # Get Crypto Withdraw Quote Source: https://guides.saber.money/api-reference/wallet-operations/crypto-withdraw/get-crypto-withdraw-quote openapi-v2.json GET /api/v2/wallet/withdraw/quote Get a crypto withdrawal quote for a coin and network. **Response field values** | Field | Possible Values | | --- | --- | | `fee_breakup` keys | `platform_fee`, `network_fee`, `client_fee`, `discount`, `tax_on_fee`, `tds` | # Get Crypto Withdraw Transaction Details Source: https://guides.saber.money/api-reference/wallet-operations/crypto-withdraw/get-crypto-withdraw-transaction-details openapi-v2.json GET /api/v2/wallet/transaction Fetch details and status for a transaction. Pass `transaction_type` and `transaction_id` to select which record to fetch — see the specific transaction-details page for the value to use. Pass `transaction_type=WITHDRAW` to fetch details for a crypto withdrawal. # Get List of Crypto Withdraw Transactions Source: https://guides.saber.money/api-reference/wallet-operations/crypto-withdraw/get-list-of-crypto-withdraw-transactions openapi-v2.json GET /api/v2/wallet/transactions Fetch a paginated list of transactions. Pass `transaction_type` to select which list to fetch — see the specific transaction-list page for the value to use. Pass `transaction_type=WITHDRAW` to list crypto withdraw transactions. **Possible `status` values:** `CREATED`, `PROCESSING`, `PENDING`, `COMPLETED`, `FAILED`, `CANCELLED`, `REVERTED`, `REJECTED`, `EXPIRED`, `SOURCE_INVALID`, `APPROVAL_REQUIRED`, `SUCCESSFUL`, `REFUND_INITIATED`, `REFUND_COMPLETED`, `REFUND_FAILED` # Get Supported Networks and Limits Source: https://guides.saber.money/api-reference/wallet-operations/crypto-withdraw/get-supported-networks-and-limits openapi-v2.json GET /api/v1/wallet/coin/info Get Supported Networks and Limits **Response field values** | Field | Possible Values | | --- | --- | | `transaction_type` (query) | `WITHDRAW`, `DEPOSIT` | | `coins` (query) | Comma-separated symbols, e.g. `USDT`, `USDT,BTC` | | `network_list[]` fields | Varies by network; may include `network_fee`, `depositMin`, `minAccuracy`, `address_regex`, `memo_regex`, `processing_time` | # Webhooks Source: https://guides.saber.money/api-reference/wallet-operations/webhooks Crypto deposit and crypto withdraw webhook statuses and sample payloads. This page describes the webhook events and statuses for crypto deposits and withdrawals. ## Crypto Deposit `status` value sent: `COMPLETED`. There is no failed state, as failed deposits are not recorded. ```json theme={null} { "transaction_type": "DEPOSIT", "status": "COMPLETED", "crypto_symbol": "USDT", "network": "BSC", "amount": "10.00000000", "created_at": 1675171122000, "tag": "", "fees": null, "updated_at": 1675171203000, "usd_value": "10.00", "txn_hash": "Internal transfer 127897710444", "id": "91994108-9eff-4999-8e8f-238df8bd4961", "event": "deposit", "address": "0x94f30b2a43ec890848c64ecdbb434ea1bcc4b798", "source_address": "0x1a2b3c4d5e6f7890abcdef1234567890abcdef12", "user_id": "3faced19-20c7-48b8-952b-6143c8c198d2" } ``` ## Crypto Withdraw `transaction_type` values sent: `CREATED`, `PROCESSING`, `COMPLETED`, `FAILED`. For withdraw webhooks, `transaction_type` carries the transaction status, not a transaction-type string; the separate `status` field carries the internal wallet transaction status. ```json theme={null} { "transaction_type": "COMPLETED", "status": "COMPLETED", "crypto_symbol": "USDT", "network": "BSC", "amount": "10.00000000", "created_at": 1675171122000, "tag": "", "fees": null, "updated_at": 1675171203000, "txn_hash": "Internal transfer 127897710444", "id": "91994108-9eff-4999-8e8f-238df8bd4961", "event": "withdraw", "address": "0x94f30b2a43ec890848c64ecdbb434ea1bcc4b798", "user_id": "3faced19-20c7-48b8-952b-6143c8c198d2" } ``` # Product Updates Source: https://guides.saber.money/changelogs What's new in the Saber platform and documentation. ## New Additions * **MCP Server for API Integration:** Saber now offers an MCP server, letting AI agents and MCP-compatible tools interact with the Saber API directly. * **AI Assistant in Docs:** Ask questions directly within the documentation and get answers grounded in the latest guides and reference content. * **FAQs Section:** A dedicated FAQs section has been added to help you find quick answers without digging through full guides. * **Changelog Section:** This changelog has been added to track documentation and platform updates going forward. ## Improvements * **Guide Section Revamp:** The Guide section has been restructured into a clearer, narrative walkthrough of onramp, offramp, user operations, and pool account concepts, with detailed API information now separated out into its own Reference section. * **Saber Interactive Demo:** The Interactive Demo has been upgraded to support all currently available corridors, and is now linked directly from the documentation. # Overview Source: https://guides.saber.money/client/dashboard/overview What you can do in the Saber merchant dashboard — stats, user and transaction details, reports, status checks, balance withdrawals, and access to the developer and team management sections. The Saber dashboard is the web-based portal for merchants to monitor and manage their account without needing to call the API directly. Saber offers two dashboards: | Dashboard | Description | Link | | :- | :- | :- | | **Sandbox Testing Dashboard** | View and act on testing data. | [Sandbox Dashboard](https://dashboard.sandbox.saber.money) | | **Production Dashboard** | Manage and view production data. | [Production Dashboard](https://dashboard.saber.money) | ## Getting Access Once you've registered as a merchant, your relationship manager will send you a dashboard invite. Further team members can be directly added from the dashboard or can be requested to be added by your Saber POC. ## Logging in Dashboard login is via OTP — enter your registered email or phone number, and verify with the one-time passcode sent to you to access the dashboard. ## What you can do * **View stats** — a summary view of transaction volumes, success rates, and other account-level metrics. * **View user details** — look up individual users on your account, including their KYC status and linked bank accounts. * **View transaction details** — inspect individual onramp/offramp transactions, including status, amounts, and timestamps. * **Download reports** — export transaction and user data for reconciliation or internal reporting. * **Check transaction status** — search for a specific transaction and see its current lifecycle state without calling the status API. * **Withdraw balances** — initiate a withdrawal of available Pool Account balance directly from the dashboard. ## Developer Section A dedicated area for managing your API integration — API keys, webhook URLs, and webhook keys. See [Developer Section](/client/dashboard/developers) for details. ## Team Management Add teammates to your Saber account and control what they can access based on role. See [Team Management](/client/dashboard/team) for details. # Webhook Configuration Source: https://guides.saber.money/client/dashboard/webhooks Configuring Saber Money webhook URLs via the Developer Console or your POC, with Webhook Key/Secret signature authentication and sandbox testing. Webhooks let Saber send your system real-time notifications about transaction updates and other status changes. ## Setting up Webhook URL To configure webhooks: ### Through the Dashboard Saber dashboard allows you to configure webhooks directly from the [Developer Console](https://dashboard.saber.money/developer-console) → webhook tab. Setting up webhook credentials is mandatory for enabling webhooks ## Authentication Webhooks are signed with a Webhook Key/Secret pair, generated through the Developer Console. Merchants onboarded before August 2025 can use their API Keys as Webhook Keys. We recommend switching to separate API and Webhook keys via the dashboard. # Overview Source: https://guides.saber.money/client/pool-account/overview What a Saber Pool Account is, its purpose, and how to fetch pool wallet details and balance. A Pool Account is a merchant-level stablecoin wallet held with Saber, separate from any individual user's wallet. It acts as a shared liquidity source (or destination) that your transactions can draw from or settle into, instead of every transaction being tied to a single user's own wallet. ```mermaid theme={null} flowchart LR U1[User A pay-in] --> P((Pool Wallet)) U2[User B pay-in] --> P U3[User C pay-in] --> P P --> D1[Payout to User X] P --> D2[Payout to User Y] P --> D3[Payout to User Z] ``` ## Purpose Pool Accounts exist to simplify liquidity management at scale: Instead of pre-funding each individual user's wallet with crypto before every payout, a **Pool Sell** transaction deducts stablecoin directly from your Pool Account balance and pays the user out in fiat. Instead of sending every converted stablecoin amount to an external wallet, a **Pool Buy** transaction deposits the converted stablecoin into your Pool Account, where it accumulates as usable liquidity. A single Pool Account can fund offramp payouts and receive onramp proceeds, letting you net fiat collected on one side against fiat paid out on the other, settling the difference in stablecoin rather than moving fiat directly. A Pool Account is provisioned per merchant on request — reach out to your Saber account contact to have one set up. ## Fetching wallet details and balance To check what's available in your Pool Account, fetch the pool wallet's balance and deposit address: ```http theme={null} GET /api/v1/wallet/balance?account=SPOT&symbol=USDT ``` ```json theme={null} { "success": true, "data": { "coin_info": { "USDT": { "coin": "USDT", "name": "TetherUS", "available_balance": 76.7, "available_balance_usd": 76.7, "total_balance": 76.7, "total_balance_usd": 76.7, "withdraw_enable": true, "deposit_enable": true } }, "available_balance": 76.7, "total_balance": 76.7 } } ``` ```http theme={null} GET /api/v1/wallet/user_deposit_address?symbol=USDT&network=BSC ``` ```json theme={null} { "success": true, "data": [ { "coin": "USDT", "network": "BSC", "name": "Binance Smart Chain (BEP20)", "address": "0xc37e9cbe6e1d481d9372585e1b91cb387853d352", "tag": "", "url": "https://bscscan.com/address/0xc37e9cbe6e1d481d9372585e1b91cb387853d352", "batchReleaseLimit": 0 } ] } ``` The deposit address is where you send stablecoin to top up the Pool Account manually (outside of onramp proceeds). ## Related pages # Demo Source: https://guides.saber.money/demo Try Saber's onramp and offramp flows in an interactive demo.
``` > 📘 For iframe integration, it is recommended to omit the `redirect_url` parameter so users see the status screen inside the iframe rather than being redirected away from your application. ### Option 3: WebView Integration (Mobile Apps) For React Native or native mobile apps, use a standard WebView component: ```javascript theme={null} import { WebView } from 'react-native-webview'; ``` ## EDD Status Flow The EDD Widget supports the following status states: | Status | Description | | - | - | | `UNINITIATED` | EDD process not started | | `INITIATED` | User has started the EDD process | | `RESUBMISSION_REQUESTED` | Additional information required | | `VERIFICATION_IN_PROGRESS` | EDD under review | | `APPROVED` | EDD approved - user can access higher limits | | `REJECTED` | EDD rejected - user cannot proceed | ## Security Considerations ### URL Expiration > ⚠️ **Warning**: The URL validity is 10 minutes from the creation of the signature. Always generate URLs dynamically in real-time just before redirecting users. ### CORS and Origin Validation The widget validates the origin of incoming requests. Ensure your web domain is correctly registered and whitelisted with Saber Money for iframe integrations. ### HTTPS Required All integrations must use HTTPS securely in production environments. ## Error Handling ### Common Error Scenarios 1. **Invalid Signature**: Check timestamp validity and signature generation logic. 2. **Expired URL**: Generate a fresh signature. 3. **User Not Found**: Ensure user is created and exists in the Saber system. 4. **Insufficient Permissions**: Verify user has completed basic KYC. ### Event Timing * **`close`**: Fired when the user clicks the close button or navigates away from the widget. * **`completed`**: Fired when the user successfully submits all required EDD information. * **`error`**: Fired when validation fails, API errors occur, or other issues prevent EDD completion. * **`token_expired`**: Fired when the HMAC signature expires (10-minute validity window). ### Event Handling Best Practices 1. **Always Verify Origin**: Check that events come from the correct Saber Money domain. 2. **Handle All Events**: Implement handlers for all four event types to provide a smooth user experience. 3. **Token Expiration**: For `token_expired`, automatically generate a new signature and reload the widget. 4. **Error Recovery**: For `error` events, provide clear messaging and retry options when appropriate. 5. **User Feedback**: Show appropriate loading states, success messages, and error notifications. 6. **Graceful Closure**: Handle `close` events by returning users to a logical point in your application flow.