Skip to content

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 ​

mermaid
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
    end

2. Create Payout Order ​

POST /api/v1/withdrawals

Headers ​

Standard HMAC authentication headers and Idempotency-Key.

Body Parameters ​

FieldTypeRequiredDescription
merchantOrderNostringYesUnique merchant order ID (1–64 characters).
directionstringNoFixed 'WITHDRAW'. Defaults to 'WITHDRAW'.
fiatAmountstringOptional*Target fiat payout amount (CNY, exactly 2 decimal places, e.g., "1500.00").
usdAmountstringOptional*Target crypto debit amount (exactly 2 decimal places, e.g., "206.89").
fiatCurrencystringNoFixed 'CNY'.
recipientobjectYesRecipient bank account details (see schema below).

recipient Schema ​

FieldTypeRequiredDescription
holderNamestringYesCardholder full name (1–64 characters).
bankAccountstringYesBank card number (digits only, 8–32 characters).
bankNamestringYesBank 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 ​

bash
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"
    }
  }'
typescript
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
json
{
  "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 ​

ParameterTypeRequiredDefaultDescription
pagenumberNo1Page number, starting from 1
pageSizenumberNo20Page size, maximum 100
orderNostringNo-Filter by platform payout order number (e.g., WD20261011000088)
merchantOrderNostringNo-Filter by merchant external order number (e.g., MCH_PAYOUT_20261011_888)
statusstringNo-Filter by status; comma-separated for multiple statuses (e.g., COMPLETED or PENDING_MATCH,PENDING_PAYMENT)
fromstringNo-Creation start timestamp (ISO 8601, e.g., 2026-10-01T00:00:00.000Z)
tostringNo-Creation end timestamp (ISO 8601, e.g., 2026-10-11T23:59:59.000Z)

Request Examples ​

bash
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"
typescript
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
json
{
  "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
json
{
  "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 ​

FieldTypeRequiredDescription
decisionstringYesDecision action: "CONFIRM" (release funds) or "DISPUTE" (report issue).
reasonstringConditionalDispute reason (required if decision === "DISPUTE", 1–500 characters).

Request Example ​

bash
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"}'
bash
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 ​

StatusNameDescriptionTerminal?
PENDING_MATCHMatching TraderMerchant balance frozen; dispatching to liquidity providersNo
PENDING_PAYMENTTrader TransferringTrader accepted task and is wiring fiat to recipient cardNo
PAYMENT_OVERDUEPayment OverdueTrader exceeded SLA; task escalatedNo
PAYMENT_SUBMITTEDProof SubmittedProof uploaded; awaiting merchant verification & decisionNo
COMPLETEDCompletedMerchant confirmed; crypto released to traderYes
DISPUTEDDisputedDispute filed; escalated to operator arbitrationNo
CANCELLEDCancelledCancelled; frozen assets unlocked back to available balanceYes