Withdrawal Overview
This section explains how your merchant system creates a withdrawal request (payout) out of your wallet through UnknownPay, paying a destination (e.g. your end customer) into a bank account over a Server-to-Server (S2S) connection.
withdrawal.success webhook to confirm the payout succeeded — never treat the request as successful just because creation returned 201 Created."100.50" (not a number, not satang) — in both request and response. Fields such as amount, fee, and net_payout must always be sent/read as baht strings.Endpoints
| Operation | Method | Path |
|---|---|---|
| Create a withdrawal | POST | /v1/withdrawals |
| Get one withdrawal (single) | GET | /v1/withdrawals/:id |
| List your own withdrawals | GET | /v1/withdrawals |
Every endpoint lives on the /v1 gateway surface and requires S2S authentication (API key + HMAC) just like the other merchant endpoints — see Authentication.
State machine
The live state flow:
PENDING ──(team approves as a batch)──▶ PROCESSING ──▶ [IN_PROGRESS] ──▶ SUCCESS
│ │ │
│ └──────────────┬───────────────┘
│ ▼
└──(team rejects)──▶ REJECTED FAILED
(bank/bot reports failure → refund)
| Status | Meaning | What the merchant should do |
|---|---|---|
PENDING | Request created, gross already debited from the wallet, awaiting team approval | Wait — not paid out yet |
APPROVED | Approved (mainly a sandbox/test resting state — live skips it) | Wait |
PROCESSING | Team approved + the pay instruction was sent to the payment system/bank (money is going out) | Wait — can no longer be cancelled |
IN_PROGRESS | In progress at the bank (intermediate state, may be skipped) | Wait |
SUCCESS | Payout succeeded (terminal) | ✅ Treat as success — withdrawal.success webhook |
FAILED | Payout failed (terminal) — system refunds the gross back to the wallet | ❌ withdrawal.failed + withdrawal.refunded webhooks |
REJECTED | Team rejected the request (terminal) — system refunds the gross back to the wallet | ❌ withdrawal.refunded webhook |
Technical notes that match the code:
- The live approval path goes straight from
PENDING→PROCESSING(approval happens as a "batch") —APPROVEDis not a resting state in live (it is used mainly in sandbox/test mode). IN_PROGRESSis optional — the bank may reportSUCCESSdirectly fromPROCESSING. Do not design your system to requireIN_PROGRESSas a mandatory condition.SUCCESS,FAILED, andREJECTEDare terminal (they never change again).
withdrawal.success(paid out successfully — money has really left)withdrawal.failed(payout failed — money refunded to the wallet)withdrawal.refunded(money refunded to the wallet; fires for bothREJECTEDandFAILED)
withdrawal.rejected (team rejected) always comes paired with withdrawal.refunded.The exact payload of each event and how to verify the signature are covered in the Webhooks section.
Debit at create + refund (matters for reconciliation)
- Debit at create (debit-at-request): when the request is created successfully (
201, statusPENDING), the system immediately debits gross =amount+feefrom your wallet — that amount is "reserved" from this moment, not at approval. - Rejected → refund: if the team rejects the request (while still
PENDINGorAPPROVED), the system refunds the full gross back to the wallet and sends thewithdrawal.refundedwebhook (statusREJECTED). - Failed → refund: if the payout was attempted and failed (
FAILED), the system likewise refunds the gross back to the wallet (withdrawal.failed+withdrawal.refundedwebhooks). - Once
PROCESSING, it cannot be cancelled: once approved and dispatched (PROCESSING), the team can no longer reject it (it returns409) — from this point onlySUCCESSorFAILEDwill settle the money.
Next steps
- Create a withdrawal —
POST /v1/withdrawals - Get & list withdrawals —
GET /v1/withdrawals/:idandGET /v1/withdrawals
Hosted Cashier Page
cashier_url — an optional payment page UnknownPay hosts for a single deposit, so you can redirect the customer instead of building your own payment screen
Create a Withdrawal
POST /v1/withdrawals — create a payout request; gross (amount + fee) is debited immediately and the request starts in PENDING awaiting manual approval
