Skip to content

Live P2P API — implemented profile ​

Status: implementation 0092 — validation and deployment pending

This page describes implemented code. It does not confirm availability on your production deployment. Have your provider confirm the deployed version and Merchant configuration before sending real payments. Sections marked Target contract describe a broader scope that this profile does not fully enable.

Use https://api.<White Label domain> with a Live key and the existing HMAC authentication (PG-Version: 2026-08-01). The hostname and credential determine the environment; request fields or headers cannot switch Live/Test mode.

Operations in this profile ​

Method and pathKey scopeResult
POST /v1/pay/withdrawalswithdrawals:writeCreate a P2P payout and reserve funds, pending two approvals
GET /v1/pay/withdrawals/{id}withdrawals:readFull current withdrawal state and amounts
GET /v1/pay/withdrawalswithdrawals:readThis Merchant's withdrawal list
GET /v1/pay/depositsdeposits:readDeposit summaries without destination accounts, QR or payment instructions
GET /v1/pay/balancewallet:readAvailable, pending clearing and reserved amounts
GET /v1/pay/eventswallet:readContinue reading business events by sequence

Existing deposit creation and individual reads retain their established flow. This profile does not add a Customer API, beneficiary registration API, inline beneficiary, Merchant quote, or withdrawal cancellation command.

Prepare the key and beneficiary ​

New Live keys may explicitly select deposits:read, deposits:write, withdrawals:read, withdrawals:write and wallet:read as needed. Existing keys are not expanded automatically. Rotation preserves the old key's actual approved scopes. sandbox:write is Test-only; see the supported sandbox API.

Have your provider prepare an existing beneficiary owned by the same Merchant in Live mode. It must be verified, accept new orders, and satisfy current account and receiving-bank eligibility checks. Its public ID is ben_ followed by the UUID with hyphens removed: exactly 32 lowercase hexadecimal characters, for example ben_92000000000040008000000000000423. Do not send a raw UUID, bank account number, or inline beneficiary object. See destination accounts and create a withdrawal.

Create a withdrawal ​

Example body for a signed request:

json
{
  "amount_minor": 100000,
  "currency": "THB",
  "merchant_reference": "WD-20261017-0001",
  "end_user_ref": "customer-001",
  "beneficiary_id": "ben_92000000000040008000000000000423",
  "metadata": { "order": "WD-20261017-0001" }
}
  • amount_minor is a positive safe integer in satang, at most 9007199254740991, and must meet the current P2P minimum. The example amount must still satisfy that policy.
  • merchant_reference and end_user_ref are 1–64 characters without control characters. The reference is unique per Merchant. end_user_ref is your reference, not a Customer creation request.
  • metadata accepts at most 20 keys, key lengths 1–40, and string values up to 500 characters. Optional client accepts only validated device_fingerprint, ip and user_agent hints; those hints are not stored in payment metadata or returned in receipts.
  • Retry with the same 8–200 character Idempotency-Key and exact body. Generate a fresh nonce and signature on every attempt. An exact replay returns the original receipt with Idempotent-Replay: true.

A 201 response starts at status: "pending_approval", requires_approval: true, version: 0. It immediately reserves total_locked_minor = amount_total_minor + fee_total_minor. The established two-employee approval flow must finish before matching begins. Creation is not a completed bank transfer. This is separate from Agent commission withdrawals.

The Merchant's Payout sale matrix for all four sources is pinned at creation. This P2P profile uses the wl_pool fee basis. Read fee_total_minor and total_locked_minor from the response; do not assume the reserved amount equals principal alone. 402 insufficient_balance means available funds cannot cover principal plus fee and no order is created. An ineligible beneficiary returns 409 beneficiary_not_verified; an amount below policy returns 422 amount_below_p2p_minimum.

Read orders and balances ​

Withdrawal IDs are wdr_ plus 32 lowercase UUID hexadecimal characters. Use GET /v1/pay/withdrawals/{id} for the full current state. Public statuses are pending_approval, open, partially_filled, settled, expired, canceled and rejected.

Principal is split into four counters, with A = amount_total_minor: A = filled_minor + reserved_minor + remaining_minor + reversed_minor. reserved_minor is allocated to a payer whose transfer has not yet been confirmed. An open order can therefore have remaining_minor: 0, reserved_minor: A, and filled_minor: 0. Zero remaining does not mean paid. Check the current status and confirmed filled amount. A fully paid order without reversals is settled, has filled_minor = A, and has reserved_minor = remaining_minor = reversed_minor = 0.

Deposit and withdrawal lists accept limit 1–100 (default 20), status, a UTC updated_after such as 2026-10-17T00:00:00Z, and the returned next_cursor as cursor. Cursors have the form cur_<epochmicro>_<uuidhex>; return them unchanged. Results sort by descending created_at, using ID to break ties. Start a new first page when changing filters. has_more: false returns next_cursor: null. Pagination reads current data; it does not freeze order status across pages.

The balance response keeps pending clearing separate from withdrawable funds:

json
{
  "livemode": true,
  "available_minor": 250000,
  "pending_clearing_minor": 10000,
  "reserved_minor": 5000,
  "currency": "THB",
  "as_of": "2026-10-17T00:00:00.000Z"
}

Poll events without changing the webhook contract ​

Call GET /v1/pay/events?since_sequence=0&limit=20. Persist next_sequence as a decimal string and pass it back as since_sequence to continue. Each event's sequence is also a string and increases per Merchant.

This profile includes only the business snapshots for withdrawal.created, withdrawal.partial_fill, withdrawal.terminal and deposit.settled. data.object retains that event's existing business snapshot; it is not a full Withdrawal object. GET the withdrawal for current state and amounts. When a snapshot contains a UUID withdrawal_id, construct its public ID as wdr_ plus the UUID without hyphens.

The signed webhook envelope retains its seven existing fields: id, object, type, api_version, livemode, created_at, data. sequence and account are added only as polling response metadata. Verify Ed25519 against the original raw webhook bytes as described in signature verification; reconstructed polling JSON is not the signed webhook body.

SQL validation, deployment and provider host/key/policy configuration remain required before enabling real payments.

เอกสารชุดนี้อิงสัญญา API เวอร์ชัน 2026-08-01