TDME Pay Integration Guide
A step-by-step reference for integrating cryptocurrency deposits and payouts into TD Markets using the TDME Pay service and payment widget.
Before You Start
TDME Pay is the payment gateway that runs on top of the TD Markets v4 exchange. It lets you (the merchant) accept crypto payments from your end-users, auto-convert them to a preferred currency, and withdraw funds to external wallets, all through a single REST API and a hosted, customizable payment widget.
Before writing any integration code, it helps to understand the parties involved, the services you will call, and how authentication works. This section covers everything you need in place before creating your first payment.
The three parties
| Party | Role |
|---|---|
| Merchant | Your business. Holds an account on the TD Markets exchange, creates payments and payouts, and configures currencies, webhooks and the widget. |
| End-user | Your customer. Interacts only with the TDME Pay widget to choose a currency, get an address and send funds. Requires no authentication. |
| Sub-merchant | An optional child account created under a parent merchant, with its own fee configuration. |
Environments & base URLs
TDME Pay uses your existing TD Markets account. You authenticate once against TD Markets and use the same JWT for every TDME Pay request, so there are no separate credentials to manage. These are the base URLs you will work with:
| Service | Base URL | Purpose |
|---|---|---|
| TD Markets exchange | https://tdmarkets.cryptosrvc.com | Merchant web platform: sign in, manage API keys, KYC & MFA. |
| TD Markets GraphQL API | https://api.tdmarkets.io/graphql | Authentication endpoint. Exchange your API key/secret for a JWT. |
| TDME Pay API | https://crypto-pay-service.cryptosrvc.com | All merchant, client and payout REST endpoints. |
| TDME Pay widget | https://crypto-pay-widget.cryptosrvc.com | Hosted checkout widget you redirect end-users to. |
Every REST endpoint in this guide is served under https://crypto-pay-service.cryptosrvc.com
and is versioned with a /v1 prefix.
Prerequisites
Make sure you have all of the following before you begin:
- A merchant account on the TD Markets exchange.
- An API key and API secret, generated from the exchange platform.
- KYC verification approved, required to create payments and payouts.
- MFA enabled, required specifically to create and approve payouts.
- At least one currency enabled on your account.
Authentication
TDME Pay secures its merchant endpoints with a JWT bearer token. This is the same token
you use for TD Markets: obtain it by exchanging your API key and secret through the TD Markets
GraphQL API using the service_signin mutation, then send it on every TDME Pay request.
mutation Service_signin { service_signin( service_api_key: "<your_api_key>", service_api_secret: "<your_api_secret>" ) { jwt expires_at } }
| Field | Description |
|---|---|
jwt | Bearer token to send on every authenticated TDME Pay request. |
expires_at | Expiration as a Unix timestamp in milliseconds. Refresh the token before it elapses. |
Send the returned token in the Authorization header of each TDME Pay request:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Your API key, secret and JWT must never be exposed in client-side code. Perform sign-in and all authenticated calls from your backend.
Endpoints under /v1/client/* are public and require no authentication.
They are designed to be called by the widget or your checkout front-end on behalf of the end-user.
Additional validations
| Operation | Requirement |
|---|---|
| Create payment | KYC approved |
| Create / approve payout | KYC approved and MFA enabled |
Account setup
Once authenticated, configure your merchant account. All of the endpoints below live under
/v1/user and require the bearer token.
1 · Check-in
Check-in ensures your TDME Pay user exists and keeps the currency list in sync with the exchange. Run it after initial setup and whenever new currencies become available.
curl -X PATCH "https://crypto-pay-service.cryptosrvc.com/v1/user/checkin" \ -H "Authorization: Bearer YOUR_JWT_TOKEN"
2 · Available currencies
Retrieve the currencies available to your account. Use this to learn which
currency_id and network pairs you can enable, set as preferred, or accept in
payments.
curl -X GET "https://crypto-pay-service.cryptosrvc.com/v1/currency" \ -H "Authorization: Bearer YOUR_JWT_TOKEN"
Response:
{
"success": true,
"message": "",
"currencies": [
{
"currency_id": "BTC",
"type": "Crypto",
"network": "BITCOIN",
"min_confirmations": 1,
"max_confirmations": 3,
"block_time": 600
},
{
"currency_id": "USDT",
"type": "Crypto",
"network": "ETHEREUM",
"min_confirmations": 1,
"max_confirmations": 12,
"block_time": 15
},
{
"currency_id": "USD",
"type": "Fiat",
"network": "DEFAULT"
}
]
}
| Field | Type | Description |
|---|---|---|
currency_id | string | Currency symbol, e.g. BTC. |
type | string | Crypto or Fiat. |
network | string | Blockchain network (DEFAULT for fiat). |
min_confirmations | integer | Minimum blockchain confirmations. Omitted for fiat. |
max_confirmations | integer | Confirmations required to finalize. Omitted for fiat. |
block_time | number | Approximate network block time in seconds. Omitted for fiat. |
GET /v1/currency/{currencyId} lists the currencies convertible from a given currency, and
GET /v1/currency/payout lists the currencies available for payouts.
3 · Preferred currency
The preferred currency is the destination currency credited to your exchange account when
auto-conversion happens. It defaults to BTC.
curl -X PATCH "https://crypto-pay-service.cryptosrvc.com/v1/user/update-preferred-currency" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"preferred_currency_id": "BTC"}'
4 · Currency settings
Enable the currencies you accept and decide, per currency, whether received funds are auto-converted to your preferred currency.
curl -X PATCH "https://crypto-pay-service.cryptosrvc.com/v1/user/update-currency-settings" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "settings": [ { "currency_id": "BTC", "network": "BITCOIN", "enabled": true, "is_converted": true }, { "currency_id": "ETH", "network": "ETHEREUM", "enabled": true, "is_converted": false }, { "currency_id": "USDT", "network": "ETHEREUM", "enabled": true, "is_converted": true }, { "currency_id": "XRP", "network": "RIPPLE", "enabled": false, "is_converted": true } ] }'
| Field | Type | Description |
|---|---|---|
currency_id | string | Currency symbol, e.g. BTC. |
network | string | Blockchain network, e.g. BITCOIN, ETHEREUM. |
enabled | boolean | Whether end-users may pay in this currency. |
is_converted | boolean | Auto-convert received funds to the preferred currency. |
If a payment currency equals the preferred currency, no conversion is needed. If it is enabled with
is_converted: true, funds are converted on arrival. If is_converted is
false, funds are kept as-is. A disabled currency cannot be used to pay at all.
5 · Crypto confirmations (optional)
Lower the number of blockchain confirmations required before a payment advances: faster, but riskier. TDME Pay still waits for the network's full confirmation count before finalizing; the reduced threshold only changes the interim status (prefixed Client).
curl -X PATCH "https://crypto-pay-service.cryptosrvc.com/v1/user/update-crypto-confirmations" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "confirmations": [ { "currency_id": "BTC", "network": "BITCOIN", "confirmations": 1 }, { "currency_id": "ETH", "network": "ETHEREUM", "confirmations": 6 } ] }'
Pricing modes
Every payment is created with one of two pricing modes, which decides when the crypto amount is locked and who carries the price risk.
| Mode (API value) | Amount locked | Risk | Best for |
|---|---|---|---|
LongLivedQuote | At quote creation | Merchant | E-commerce, fixed prices |
ConvertOnArrival | At blockchain confirmation | Customer | Donations, open deposits |
With LongLivedQuote the end-user must generate a quote that fixes the exact
crypto amount for a limited time. With ConvertOnArrival no quote is used; the preferred-currency
value is computed from the market rate at the moment the transaction confirms, and you can show an
indicative rate via the market-price endpoint.
Payment statuses
A payment moves through the following statuses. The Client variants only appear when you have lowered the confirmation threshold for a currency (see above).
| Status | Meaning | Final |
|---|---|---|
| New | Payment created; no address generated yet. | No |
| Pending | An address exists and is awaiting funds. | No |
| Processing | A transaction was detected; awaiting confirmations. | No |
| ClientPartiallyCompleted | Partial payment reached the merchant's custom confirmation threshold. | No |
| PartiallyCompleted | Less than the required amount was received. | No |
| ClientCompleted | Full amount reached the custom confirmation threshold; awaiting full confirmations. | No |
| Completed | Fully paid and confirmed. | Yes |
| CompletedProfit | Convert-on-arrival completion where the price moved in the merchant's favour. | Yes |
| CompletedLoss | Convert-on-arrival completion where the price moved against the merchant. | Yes |
| ManualCompleted | Manually marked as completed by the merchant. | Yes |
| ManualRejected | Manually rejected so users can no longer pay this invoice. | Yes |
| Expired | Expiration elapsed before completion. | Yes |
Deposit: Accepting Payments
A deposit is the flow where your end-user pays you in crypto.
You create a payment server-side, send the user to the widget, and the widget drives the public client endpoints to quote, generate an address and track confirmations. You are notified of the outcome through webhooks.
Create the payment
Call this from your backend when the customer starts checkout.
curl -X POST "https://crypto-pay-service.cryptosrvc.com/v1/payment" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "type": "Invoice", "pricing_mode": "LongLivedQuote", "merchant_order_currency_id": "USD", "amount_required": 100.00, "merchant_order_id": "ORDER-12345", "merchant_order_description": "Premium Subscription", "expiration": "01:00:00", "fees_in_price": true }'
| Parameter | Type | Req. | Description |
|---|---|---|---|
type | string | yes | Invoice or OpenDeposit. |
pricing_mode | string | yes | LongLivedQuote or ConvertOnArrival. |
merchant_order_currency_id | string | yes* | Invoice currency, e.g. USD. *Required for Invoice. |
amount_required | decimal | yes* | Amount to collect. Must be > 0. *Required for Invoice. |
merchant_order_id | string | no | Your order reference (max 36 chars). |
merchant_order_description | string | no | Description shown in the widget. |
merchant_order_metadata | JSON | no | Custom JSON echoed back in webhooks. |
expiration | string | no | Format HH:MM:SS (or d:HH:MM:SS). Minimum 1 hour. |
fees_in_price | boolean | no | Include fees in the quoted amount. Default true. |
passthrough_fees | boolean | no | Pass network fees on to the customer. |
The response returns the payment with status New. The example below reflects the
exact PaymentResponse shape returned by the service:
{
"success": true,
"message": "",
"payment": {
"id": "857e94e4-f7f0-490d-9a53-9c0c99b47978",
"owner_user_id": "1a1909b9-f0dc-4de1-a2a8-afaa04e6b482",
"status": "New",
"kyt_status": null,
"currency_id": "USD",
"expiration": 3599,
"target_currency_amount_required": 100.5,
"target_currency_amount_paid": 0,
"merchant_label": "TD Markets Merchant",
"merchant_order_id": "ORDER-12345",
"merchant_order_metadata": null,
"merchant_order_description": "Premium Subscription",
"own_merchant_fee": 0.25,
"type": "Invoice",
"pricing_mode": "Long Lived Quote",
"fees_in_price": true,
"passthrough_fees": false,
"created": "2026-02-08T12:52:47.006606+00:00",
"updated": "2026-02-08T12:52:46.986953+00:00",
"expired": "2026-02-08T13:52:47.006606+00:00"
}
}
| Field | Type | Description |
|---|---|---|
success / message | boolean / string | Envelope status and message (empty on success). |
id | UUID | Payment identifier. Use it to build the widget URL. |
owner_user_id | string | Your merchant user id. |
status | string | Payment status; initially New. |
kyt_status | string | Know-Your-Transaction screening status: Pending, Approved, Partially KYT Rejected or Rejected. null until screening runs. |
currency_id | string | Invoice currency. |
expiration | integer | Seconds remaining until expiry. |
target_currency_amount_required / _paid | decimal | Amount to collect and amount received so far. |
merchant_label | string | Merchant display name. |
merchant_order_id / _description / _metadata | string / JSON | Your order reference, description and custom metadata. |
own_merchant_fee | decimal | Calculated merchant fee. |
type | string | Invoice or Open Deposit. |
pricing_mode | string | Long Lived Quote or Convert On Arrival. |
fees_in_price / passthrough_fees | boolean | Fee handling flags. |
created / updated / expired | datetime | ISO-8601 timestamps with offset. |
Responses serialize enums using their display label, so pricing_mode comes back as
"Long Lived Quote" and type as "Open Deposit" (with spaces).
On requests the API accepts either the spaced label or the compact form
("LongLivedQuote", "OpenDeposit"), which is why the request examples above
use the compact form.
Invoice collects a specific amount and supports quotes. OpenDeposit is
open-ended; the customer may send any amount and quotes are not supported
(pair it with ConvertOnArrival).
Redirect the customer to the widget
Build the widget URL from the payment id and redirect the customer (or embed it in an iframe).
const paymentUrl = `https://crypto-pay-widget.cryptosrvc.com/?invoice=${payment.id}`; // redirect window.location.href = paymentUrl; // or embed const iframe = document.createElement("iframe"); iframe.src = paymentUrl; document.body.appendChild(iframe);
The remaining steps (3 to 5) are performed by the widget against the public /v1/client
endpoints. They are documented here so you can build your own checkout UI if you prefer not to use the
hosted widget.
Get payment details & create a quote
The widget first loads the payment, then, for LongLivedQuote payments, creates a quote
for the currency the customer chose. The quote fixes the exact crypto amount and its expiration.
The response returns the payment (with a simplified, client-facing status), the widget configuration and languages, and any transactions received so far:
{
"success": true,
"message": "",
"payment": {
"id": "857e94e4-f7f0-490d-9a53-9c0c99b47978",
"client_status": "Pending",
"currency_id": "USD",
"currency_network": "DEFAULT",
"currency_type": "Fiat",
"target_currency_amount_required": 100.00,
"total_fee_amount": 0.50,
"merchant_fee_amount": 0.25,
"parent_merchant_fee_amount": 0.25,
"target_currency_amount_paid": 0,
"target_currency_amount_left": 100.00,
"expiration": 3540,
"created": "2026-02-08T12:52:47.006606+00:00",
"updated": "2026-02-08T12:52:47.006606+00:00",
"currency_settings": [
{
"enabled": true,
"currency_id": "BTC",
"network": "BITCOIN",
"is_converted": true,
"required_confirmations": 3,
"is_preferred": true,
"type": "Crypto",
"currency_metadata": null
},
{
"enabled": true,
"currency_id": "USDT",
"network": "ETHEREUM",
"is_converted": true,
"required_confirmations": 12,
"is_preferred": false,
"type": "Crypto",
"currency_metadata": {
"contract_address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
"is_native_token": false,
"is_erc20": true
}
}
],
"type": "Invoice",
"pricing_mode": "Long Lived Quote",
"is_fees_in_price_quote": true,
"merchant_order_description": "Premium Subscription",
"fees_values": [
{ "currency_id": "BTC", "deposit_fee": 0.25, "deposit_flat_fee": 0.0001 }
],
"passthrough_fees": false
},
"widget_setting": "{\"primary_color\":\"#E4C030\"}",
"widget_languages": [
{ "id": "en", "name": "English" }
],
"transactions": [
{
"source_currency_id": "BTC",
"source_currency_network": "BITCOIN",
"source_amount_received": 0.0025,
"estimated_target_amount": 100.00,
"confirmations": 1,
"crypto_transaction_id": "8dca4a17...089ffb",
"sub_merchant_fee_amount": null,
"is_overpayment": false,
"status": "Detected"
}
]
}
Top-level fields
| Field | Type | Description |
|---|---|---|
success / message | boolean / string | Envelope status and message. |
payment | object | The payment details (see below). |
widget_setting | string | JSON string of the merchant's widget customization. |
widget_languages | array | Available widget languages, each { id, name }. |
transactions | array | Transactions received against this payment so far. |
payment object
| Field | Type | Description |
|---|---|---|
id | UUID | Payment identifier. |
client_status | string | Simplified client-facing status: New, Pending, Processing, PartiallyCompleted, Completed, Expired or Rejected. |
currency_id / currency_network | string | Invoice currency and its network (DEFAULT for fiat). |
currency_type | string | Crypto or Fiat. null for open deposits. |
target_currency_amount_required / _paid / _left | decimal | Amount to collect, amount paid, and remaining (invoice only). |
total_fee_amount / merchant_fee_amount / parent_merchant_fee_amount | decimal | Fee breakdown. All 0 when passthrough_fees is enabled. |
expiration | integer | Seconds remaining until expiry. |
created / updated | datetime | Timestamps. |
currency_settings | array | Crypto currencies the customer may pay with (see below). |
type | string | Invoice or Open Deposit. |
pricing_mode | string | Long Lived Quote or Convert On Arrival. |
is_fees_in_price_quote | boolean | Whether fees are included in the quoted amount. |
merchant_order_description | string | Description to show the customer. |
fees_values | array | Per-currency fees: currency_id, deposit_fee (progressive), deposit_flat_fee (flat). Empty when passthrough_fees is enabled. |
passthrough_fees | boolean | Whether network fees are passed to the customer. |
currency_settings[] and transactions[]
| Object | Fields |
|---|---|
currency_settings[] | enabled, currency_id, network, is_converted, required_confirmations, is_preferred, type, and currency_metadata (contract_address, is_native_token, is_erc20; null for native coins). |
transactions[] | source_currency_id, source_currency_network, source_amount_received, estimated_target_amount, confirmations, crypto_transaction_id, sub_merchant_fee_amount, is_overpayment, and status (Detected, Completed or Rejected). |
Once the customer chooses a currency, create a quote:
curl -X POST "https://crypto-pay-service.cryptosrvc.com/v1/client/quote" \ -H "Content-Type: application/json" \ -d '{ "payment_id": "857e94e4-f7f0-490d-9a53-9c0c99b47978", "currency_id": "BTC", "network": "BITCOIN" }'
| Field | Type | Req. | Description |
|---|---|---|---|
payment_id | UUID | yes | The payment to quote. |
currency_id | string | yes | Currency the customer will pay in. |
network | string | yes | Blockchain network (upper-cased server-side). |
Response:
{
"success": true,
"quote": {
"quote_id": "7b0d...c31a",
"source_currency_id": "BTC",
"source_currency_amount": 0.0025,
"price": 40000.00,
"instrument": "BTC-USD",
"expiration": 120,
"fees_in_price": true
}
}
Calling /v1/client/quote for an OpenDeposit payment returns
"Quotes are not supported." Skip straight to address generation.
For ConvertOnArrival payments, show an indicative rate instead of a quote:
GET /v1/client/market-price/{paymentId}?targetCurrencyId=BTC&sourceCurrencyAmount=100.
Generate the payment address
Create the deposit address the customer will send funds to. Pass the quote_id from step 3.
For the preferred currency you may omit the quote and an address is generated directly.
curl -X POST "https://crypto-pay-service.cryptosrvc.com/v1/client/address" \ -H "Content-Type: application/json" \ -d '{ "payment_id": "857e94e4-f7f0-490d-9a53-9c0c99b47978", "quote_id": "7b0d...c31a", "currency": "BTC", "currency_network": "BITCOIN" }'
| Field | Type | Req. | Description |
|---|---|---|---|
payment_id | UUID | yes | The payment to attach the address to. |
quote_id | UUID | cond. | Quote from step 3. Optional when generating a preferred-currency address without a quote. |
currency | string | yes | Currency symbol for the address. |
currency_network | string | yes | Blockchain network (upper-cased server-side). |
Response, the payment now moves to Pending:
{
"success": true,
"address_id": "a1b2c3d4-...",
"address": "bc1qhll33g6azrpfwx7pyl6fannjahcrnutk66hk2p",
"address_currency_id": "BTC",
"address_currency_network": "BITCOIN",
"tag_type": null,
"tag_value": null
}
For tag/memo networks (XRP, XLM, etc.) the response includes tag_type and
tag_value, which the customer must include when sending funds.
Track the transaction & confirmations
After the customer sends funds, poll the payment's addresses to reflect progress in your UI. The
payment advances to Processing when a transaction is detected and to a completed status once
the required confirmations are received.
| Stage | Typical timing |
|---|---|
| Address generation | < 1 second |
| Transaction detection | 10 to 60 seconds |
| Blockchain confirmations | Varies by network |
Polling is fine for reflecting live progress in the widget UI, but to avoid hitting request limits we recommend using webhooks as the primary way to track deposit progress, and treating polling only as a lightweight fallback.
Receive the result via webhooks
Register a webhook once, and TDME Pay will push payment and transaction events to your server as they happen. Do not rely on webhooks alone; keep polling as a fallback.
curl -X POST "https://crypto-pay-service.cryptosrvc.com/v1/webhook" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yoursite.com/webhooks/payments", "label": "Payment Notifications" }'
Manage webhooks with GET /v1/webhook, PUT /v1/webhook/{id} (enable/disable)
and DELETE /v1/webhook/{id}. You may have up to 5 enabled at once.
Event payloads
Completed transaction event (a confirmed transaction against a payment):
{
"payment_id": "550e8400-e29b-41d4-a716-446655440000",
"payment_status": "Pending",
"transaction_status": "Completed",
"crypto_transaction_id": "8dca4a17...089ffb",
"confirmations": 3,
"address": "bc1qhll33g6azrpfwx7pyl6fannjahcrnutk66hk2p",
"currency": "BTC",
"currency_amount_paid": 0.0025,
"is_converted": true,
"preferred_currency_amount_received": 100,
"preferred_currency_id": "USDT",
"merchant_order_id": "ORDER-12345",
"merchant_order_metadata": "{ \"custom-property\": \"value\" }"
}
Payment status event (the payment itself changed state, e.g. Completed or
PartiallyCompleted):
{
"payment_id": "550e8400-e29b-41d4-a716-446655440000",
"payment_status": "Completed",
"address": "bc1qhll33g6azrpfwx7pyl6fannjahcrnutk66hk2p",
"confirmations": 3,
"currency": "USD",
"currency_amount_paid": 0.0025,
"merchant_order_id": "ORDER-12345",
"merchant_order_metadata": "{ \"custom-property\": \"value\" }"
}
Verifying signatures
Every webhook is signed. The signature is the SHA-256 hash of your webhook id
concatenated with the raw JSON body, sent in the x-webhook-signature header.
const crypto = require("crypto"); function verify(payload, signature, webhookId) { const stringToSign = webhookId + JSON.stringify(payload); const expected = crypto.createHash("sha256") .update(stringToSign, "utf8").digest("hex"); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected)); }
Retry policy
If your endpoint does not return a 2xx status, TDME Pay retries with increasing delays. Always return
200 promptly and process asynchronously; make handling idempotent.
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 2 seconds |
| 3 | 3 seconds |
| 4 | 4 seconds |
Payout: Withdrawing Funds
A payout withdraws funds from your TDME Pay account to an external wallet.
Payouts use a two-step create-then-approve model: creating a payout leaves it pending, and it only executes once approved with an MFA code.
Payouts require both approved KYC and enabled MFA. Without them the create call returns "You should have approved KYC and enabled MFA to create the payout."
A pending payout can instead be Rejected, which stops it from executing.
Create the payout
curl -X POST "https://crypto-pay-service.cryptosrvc.com/v1/payout" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "currency_id": "BTC", "network": "BITCOIN", "amount": 0.05, "destination_address": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", "note": "Refunding" }'
| Parameter | Type | Req. | Description |
|---|---|---|---|
currency_id | string | yes | Currency to withdraw. |
network | string | yes | Blockchain network. |
amount | decimal | yes | Amount to withdraw. Must be > 0. |
destination_address | string | yes | External wallet address. |
note | string | no | Informational note (max 256 chars). |
destination_address_tag_type | string | no | Tag/memo type (XRP, XLM, ...). |
destination_address_tag_value | string | no | Tag/memo value (XRP, XLM, ...). |
passthrough_fees | boolean | no | Deduct network fees from the payout amount. |
Response, the payout is created with approval_status: Pending and
execution_status: New:
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "BTC",
"network": "BITCOIN",
"amount": 0.05,
"fee": 0.0001,
"own_merchant_fee": 0,
"parent_merchant_fee": 0,
"final_amount": 0.0499,
"destination_address": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
"tag_type": null,
"tag_value": null,
"blockchain_hash": null,
"note": "Refunding",
"approval_status": "Pending",
"execution_status": "New",
"payout_passthrough_fees": false,
"is_refund": false,
"created_at": "2026-02-18T09:49:09Z",
"updated_at": "2026-02-18T09:49:09Z"
}
For XRP/XLM-style networks, include destination_address_tag_type and
destination_address_tag_value in the request.
Approve the payout (with MFA)
Approving moves the payout to approval_status: Approved and
execution_status: Processing. It requires a valid MFA code and the source wallet id.
curl -X PATCH "https://crypto-pay-service.cryptosrvc.com/v1/payout/3fa85f64-.../approve" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "mfa_code": "123456", "wallet_id": "wallet_abc123" }'
| Field | Type | Req. | Description |
|---|---|---|---|
mfa_code | string | yes | 6-digit code from your authenticator. |
wallet_id | string | yes | Source wallet id to draw funds from. |
An incorrect code returns 400 with "Invalid MFA code". The payout stays pending
and can be retried.
Reject a payout
Reject a still-pending payout so it never executes. No MFA is required to reject.
curl -X PATCH "https://crypto-pay-service.cryptosrvc.com/v1/payout/3fa85f64-.../reject" \ -H "Authorization: Bearer YOUR_JWT_TOKEN"
The payout returns with approval_status: Rejected and
execution_status: Rejected.
Listing payouts & helper endpoints
Retrieve a paginated, filterable list of payouts.
curl -X GET "https://crypto-pay-service.cryptosrvc.com/v1/payout/list?approvalStatus=Pending&pageSize=20" \ -H "Authorization: Bearer YOUR_JWT_TOKEN"
| Query param | Description |
|---|---|
currency | Filter by currency. |
approvalStatus | Pending, Approved or Rejected. |
executionStatus | New, Processing, Completed or Rejected. |
startDate / endDate | Date range filter. |
search | Search by note or id. |
pageSize | Items per page. Default 10. |
pageIndex | Page number (1-based). Default 1. |
Other payout endpoints
Get a payout by id
Returns the full payout record (same shape as the create and approve responses).
curl -X GET "https://crypto-pay-service.cryptosrvc.com/v1/payout/3fa85f64-5717-4562-b3fc-2c963f66afa6" \ -H "Authorization: Bearer YOUR_JWT_TOKEN"
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "BTC",
"network": "BITCOIN",
"amount": 0.05,
"fee": 0.0001,
"parent_merchant_fee": 0,
"own_merchant_fee": 0,
"final_amount": 0.0499,
"destination_address": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
"tag_type": null,
"tag_value": null,
"exchange_payment_id": "a7f3c1b2-9d84-4e21-8c0a-1f2e3d4c5b6a",
"blockchain_hash": "a3f19ec1f6bf2fb8b64a9392e32dac433e05bc5b288468f01b4277b1c82e893b",
"note": "Refunding",
"approval_status": "Approved",
"execution_status": "Completed",
"created_at": "2026-02-18T09:49:09.379Z",
"updated_at": "2026-02-18T09:51:12.500Z",
"kyt": null,
"payout_passthrough_fees": false,
"is_refund": false,
"manual_refund": null
}
| Field | Type | Description |
|---|---|---|
fee | decimal | Exchange (network) fee. |
parent_merchant_fee / own_merchant_fee | decimal | Merchant fee breakdown. |
final_amount | decimal | Amount delivered after fees. |
tag_type / tag_value | string | Destination tag/memo (XRP, XLM, etc.), otherwise null. |
exchange_payment_id | string | Underlying exchange transaction reference. |
blockchain_hash | string | On-chain transaction hash once broadcast. |
approval_status / execution_status | string | See the status reference above. |
kyt | object | Know-Your-Transaction screening result (status, risk score, decision reason, analysed timestamp). null until screening runs. |
payout_passthrough_fees / is_refund | boolean | Fee-passthrough and refund flags. |
manual_refund | object | Present only for manual refunds: transaction_id, payment_id, reason. Otherwise null. |
Estimate the payout fee
Preview the fee and net amount before creating a payout. Optional query passthroughFees
(true/false) mirrors the create-payout flag.
curl -X GET "https://crypto-pay-service.cryptosrvc.com/v1/payout/fee-estimation/BTC/BITCOIN/0.05" \ -H "Authorization: Bearer YOUR_JWT_TOKEN"
{
"fee_estimation": 0.0001,
"final_amount": 0.0499,
"fees": {
"parent_merchant_fee": 0,
"merchant_fee": 0,
"exchange_fee": 0.0001
}
}
| Field | Type | Description |
|---|---|---|
fee_estimation | decimal | Total estimated fee for the payout. |
final_amount | decimal | Net amount delivered after fees. |
fees | object | Breakdown: parent_merchant_fee, merchant_fee, exchange_fee. |
Get withdrawal info for a currency
Returns the account balance, payout limits and pending payout total for a currency. Optional query
walletId targets a specific source wallet.
curl -X GET "https://crypto-pay-service.cryptosrvc.com/v1/payout/info/BTC" \ -H "Authorization: Bearer YOUR_JWT_TOKEN"
{
"account_balance": {
"free_balance": 1.25,
"total_balance": 1.30,
"exposed_balance": 0.05
},
"payment_limit": {
"currency": "BTC",
"payout_minimal_amount": 0.0005,
"payout_daily_limit": 5.0,
"payout_weekly_limit": 20.0,
"payout_monthly_limit": 50.0
},
"pending_payouts_amount": 0.05
}
| Field | Type | Description |
|---|---|---|
account_balance | object | free_balance, total_balance, exposed_balance. |
payment_limit | object | currency, payout_minimal_amount, and remaining payout_daily_limit / payout_weekly_limit / payout_monthly_limit. |
pending_payouts_amount | decimal | Total of payouts awaiting execution. |
Get payout address history
Previously used destination addresses, so customers can reuse them.
curl -X GET "https://crypto-pay-service.cryptosrvc.com/v1/payout/addresses-history" \ -H "Authorization: Bearer YOUR_JWT_TOKEN"
{
"addresses": [
{
"destination_address": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
"network": "BITCOIN",
"currency": "BTC"
},
{
"destination_address": "rN7n3473SaZBCG4dFL83w7a1RXtXtbk2D9",
"network": "RIPPLE",
"currency": "XRP",
"address_tag_type": "tag",
"address_tag_value": "324234"
}
]
}
Each entry has destination_address, network and currency.
address_tag_type and address_tag_value are only present for tag/memo networks.
Get payout payment routes
The currency and network combinations available for payouts.
curl -X GET "https://crypto-pay-service.cryptosrvc.com/v1/payout/payment-routes" \ -H "Authorization: Bearer YOUR_JWT_TOKEN"
{
"payment_routes": [
{ "currency": "BTC", "crypto_network": "BITCOIN" },
{ "currency": "XRP", "crypto_network": "RIPPLE", "crypto_tag_type": "tag" }
]
}
Each route has currency and crypto_network. crypto_tag_type is only
present for tag/memo networks.
Status reference
| approval_status | execution_status | Meaning |
|---|---|---|
| Pending | New | Created, waiting for approval. |
| Approved | Processing | Approved with MFA; funds are being sent. |
| Approved | Completed | Broadcast on-chain; a blockchain_hash is present. |
| Rejected | Rejected | Rejected before execution. |