Fiat Payout (Withdrawal)
The Fiat Payout (Withdrawal) service enables merchants to disburse fiat currency (CNY) directly to end users' bank cards. Upon receiving a payout request, the gateway freezes equivalent crypto funds from the merchant's available balance and dispatches the task to a liquidity provider. The trader transfers fiat to the recipient and uploads a bank transfer proof image. The merchant then verifies receipt and submits a decision to release the funds or initiate a dispute.
1. Sequence Flow
sequenceDiagram
autonumber
participant Merchant as Merchant System
participant Gateway as SureLink Gateway
actor Trader as Trader / Acceptor
actor User as Recipient (Bank Account)
Merchant->>Gateway: 1. POST /api/v1/withdrawals (Submit payout request)
Gateway->>Gateway: 2. Lock merchant crypto balance
Gateway->>Trader: 3. Assign payout task to trader
Trader->>User: 4. Transfer fiat to recipient bank account
Trader->>Gateway: 5. Upload bank transfer proof receipt (1–3 images)
Gateway->>Merchant: 6. Webhook dispatches payout.proof_submitted
alt Merchant verifies payment received
Merchant->>Gateway: 7a. POST /api/v1/withdrawals/:orderNo/decision (CONFIRM)
Gateway->>Gateway: 8a. Deduct frozen balance, release crypto to trader
Gateway->>Merchant: 9a. Webhook dispatches payout.completed (Terminal)
else Payment discrepancy or not received
Merchant->>Gateway: 7b. POST /api/v1/withdrawals/:orderNo/decision (DISPUTE)
Gateway->>Gateway: 8b. Order transitions to DISPUTED for manual arbitration
end2. Create Payout Order
POST /api/v1/withdrawals
Headers
Standard HMAC authentication headers and Idempotency-Key.
Body Parameters
| Field | Type | Required | Description |
|---|---|---|---|
merchantOrderNo | string | Yes | Unique merchant order ID (1–64 characters). |
direction | string | No | Fixed 'WITHDRAW'. Defaults to 'WITHDRAW'. |
fiatAmount | string | Optional* | Target fiat payout amount (CNY, exactly 2 decimal places, e.g., "1500.00"). |
usdAmount | string | Optional* | Target crypto debit amount (exactly 2 decimal places, e.g., "206.89"). |
fiatCurrency | string | No | Fixed 'CNY'. |
recipient | object | Yes | Recipient bank account details (see schema below). |
recipient Schema
| Field | Type | Required | Description |
|---|---|---|---|
holderName | string | Yes | Cardholder full name (1–64 characters). |
bankAccount | string | Yes | Bank card number (digits only, 8–32 characters). |
bankName | string | Yes | Bank institution name (e.g., "China Merchants Bank"). |
PII & Compliance Protection
Either fiatAmount or usdAmount must be provided. Recipient bank account numbers are encrypted at rest via AES-256-GCM. To comply with privacy standards, plain card numbers are never returned in subsequent query responses, logs, or Webhooks.
Request Example
curl -X POST "https://api-sandbox.surelink.io/api/v1/withdrawals" \
-H "Content-Type: application/json" \
-H "X-Api-Key: mch_key_your_api_key" \
-H "X-Timestamp: 1715000000000" \
-H "X-Nonce: 9f8e7d6c5b4a392817263544a1b2c3d4" \
-H "X-Signature: c8b9...f01" \
-H "Idempotency-Key: MCH_PAYOUT_20261011_888" \
-d '{
"merchantOrderNo": "MCH_PAYOUT_20261011_888",
"direction": "WITHDRAW",
"fiatAmount": "1000.00",
"fiatCurrency": "CNY",
"recipient": {
"holderName": "Zhang San",
"bankAccount": "6222021234567890123",
"bankName": "Industrial and Commercial Bank of China"
}
}'import { sendSignedRequest } from './signer';
const result = await sendSignedRequest({
apiKey: 'mch_key_...',
apiSecret: 'sec_...',
method: 'POST',
url: 'https://api-sandbox.surelink.io/api/v1/withdrawals',
body: {
merchantOrderNo: 'MCH_PAYOUT_20261011_888',
direction: 'WITHDRAW',
fiatAmount: '1000.00',
recipient: {
holderName: 'Zhang San',
bankAccount: '6222021234567890123',
bankName: 'Industrial and Commercial Bank of China'
}
}
});Response
- Status Code:
201 Created
{
"orderId": "3b2e5a7d-8f90-4c12-9e34-5a6b7c8d9e0f",
"orderNo": "WD20261011000088",
"status": "PENDING_MATCH",
"direction": "WITHDRAW",
"fiatAmount": "1000.00",
"fiatCurrency": "CNY",
"usdAmount": "138.50",
"usdFeeAmount": "1.00",
"createdAt": "2026-10-11T09:00:00.000Z"
}3. List Payout Orders
GET /api/v1/withdrawals
Retrieve a paginated list of payout orders created by the authenticated merchant, with multi-dimensional filtering by platform order number, merchant order number, order status, and time range.
Request Headers
Include standard HMAC authentication headers (X-Api-Key, X-Timestamp, X-Nonce, X-Signature).
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
page | number | No | 1 | Page number, starting from 1 |
pageSize | number | No | 20 | Page size, maximum 100 |
orderNo | string | No | - | Filter by platform payout order number (e.g., WD20261011000088) |
merchantOrderNo | string | No | - | Filter by merchant external order number (e.g., MCH_PAYOUT_20261011_888) |
status | string | No | - | Filter by status; comma-separated for multiple statuses (e.g., COMPLETED or PENDING_MATCH,PENDING_PAYMENT) |
from | string | No | - | Creation start timestamp (ISO 8601, e.g., 2026-10-01T00:00:00.000Z) |
to | string | No | - | Creation end timestamp (ISO 8601, e.g., 2026-10-11T23:59:59.000Z) |
Request Examples
curl -X GET "https://api-sandbox.surelink.io/api/v1/withdrawals?page=1&pageSize=20&status=PAYMENT_SUBMITTED" \
-H "X-Api-Key: mch_key_your_api_key" \
-H "X-Timestamp: 1715000000000" \
-H "X-Nonce: 9f8e7d6c5b4a392817263544a1b2c3d4" \
-H "X-Signature: c8b9...f01"import { sendSignedRequest } from './signer';
const result = await sendSignedRequest({
apiKey: 'mch_key_...',
apiSecret: 'sec_...',
method: 'GET',
url: 'https://api-sandbox.surelink.io/api/v1/withdrawals?page=1&pageSize=20&status=PAYMENT_SUBMITTED'
});
console.log('Total Payout Orders:', result.total);
console.log('Payout Orders List:', result.items);Response
- Status Code:
200 OK
{
"items": [
{
"orderId": "3b2e5a7d-8f90-4c12-9e34-5a6b7c8d9e0f",
"orderNo": "WD20261011000088",
"merchantOrderNo": "MCH_PAYOUT_20261011_888",
"status": "PAYMENT_SUBMITTED",
"direction": "WITHDRAW",
"usdAmount": "138.50",
"usdFeeAmount": "1.00",
"feeBps": 50,
"fiatCurrency": "CNY",
"referenceFiatAmount": "1000.00",
"slippage": "0.005",
"lockedFiatAmount": "1000.00",
"traderId": "tra_67890",
"paymentDueAt": "2026-10-11T09:30:00.000Z",
"merchantActionDueAt": "2026-10-11T11:00:00.000Z",
"firstProofSubmittedAt": "2026-10-11T09:15:30.000Z",
"createdAt": "2026-10-11T09:00:00.000Z",
"updatedAt": "2026-10-11T09:15:30.000Z"
}
],
"page": 1,
"pageSize": 20,
"total": 1
}4. Query Payout Details
GET /api/v1/withdrawals/:orderNo
Retrieve details and status of a single payout order by either platform payout order number (orderNo) or merchant external order number (merchantOrderNo).
Route Parameters
orderNo(string): The platform payout order number (e.g.,WD20261011000088) or merchant external order number (e.g.,MCH_PAYOUT_20261011_888).
Response
- Status Code:
200 OK
{
"orderId": "3b2e5a7d-8f90-4c12-9e34-5a6b7c8d9e0f",
"orderNo": "WD20261011000088",
"merchantOrderNo": "MCH_PAYOUT_20261011_888",
"status": "PAYMENT_SUBMITTED",
"direction": "WITHDRAW",
"usdAmount": "138.50",
"usdFeeAmount": "1.00",
"feeBps": 50,
"fiatCurrency": "CNY",
"referenceFiatAmount": "1000.00",
"slippage": "0.005",
"lockedFiatAmount": "1000.00",
"traderId": "tra_67890",
"paymentDueAt": "2026-10-11T09:30:00.000Z",
"merchantActionDueAt": "2026-10-11T11:00:00.000Z",
"firstProofSubmittedAt": "2026-10-11T09:15:30.000Z",
"createdAt": "2026-10-11T09:00:00.000Z",
"updatedAt": "2026-10-11T09:15:30.000Z"
}Cardholder Privacy & Tenant Isolation
- To safeguard cardholder financial data, recipient bank card numbers are never returned in plain text via the API (card details are securely encrypted at rest).
- Querying an order belonging to another merchant returns
404 Not Found(ORDER_NOT_FOUND) to prevent order number probing.
5. Merchant Confirmation & Dispute Decision
POST /api/v1/withdrawals/:orderNo/decision
Once the trader uploads payment proof (order enters PAYMENT_SUBMITTED), the merchant must verify recipient receipt and submit a decision.
Body Parameters
| Field | Type | Required | Description |
|---|---|---|---|
decision | string | Yes | Decision action: "CONFIRM" (release funds) or "DISPUTE" (report issue). |
reason | string | Conditional | Dispute reason (required if decision === "DISPUTE", 1–500 characters). |
Request Example
curl -X POST "https://api-sandbox.surelink.io/api/v1/withdrawals/WD20261011000088/decision" \
-H "Content-Type: application/json" \
-H "X-Api-Key: mch_key_your_api_key" \
-H "X-Timestamp: 1715000000000" \
-H "X-Nonce: 9f8e7d6c5b4a392817263544a1b2c3d4" \
-H "X-Signature: c8b9...f01" \
-d '{"decision": "CONFIRM"}'curl -X POST "https://api-sandbox.surelink.io/api/v1/withdrawals/WD20261011000088/decision" \
-H "Content-Type: application/json" \
-H "X-Api-Key: mch_key_your_api_key" \
-H "X-Timestamp: 1715000000000" \
-H "X-Nonce: 9f8e7d6c5b4a392817263544a1b2c3d4" \
-H "X-Signature: c8b9...f01" \
-d '{
"decision": "DISPUTE",
"reason": "Bank statement audited; transfer not credited to recipient"
}'Response
- Status Code:
200 OK - Format:
{ "status": "COMPLETED" }or{ "status": "DISPUTED" }.
6. Lifecycle Status Matrix
| Status | Name | Description | Terminal? |
|---|---|---|---|
PENDING_MATCH | Matching Trader | Merchant balance frozen; dispatching to liquidity providers | No |
PENDING_PAYMENT | Trader Transferring | Trader accepted task and is wiring fiat to recipient card | No |
PAYMENT_OVERDUE | Payment Overdue | Trader exceeded SLA; task escalated | No |
PAYMENT_SUBMITTED | Proof Submitted | Proof uploaded; awaiting merchant verification & decision | No |
COMPLETED | Completed | Merchant confirmed; crypto released to trader | Yes |
DISPUTED | Disputed | Dispute filed; escalated to operator arbitration | No |
CANCELLED | Cancelled | Cancelled; frozen assets unlocked back to available balance | Yes |
