How to use P2P
The short version: call the same APIs, let the platform pick the rail, then wait for the webhook
P2P is not a new API. It is a relief rail the platform uses to take load off the main bank account: you call the same endpoints and write the same code, and the platform decides which rail each transaction takes.
- Deposits normally land in the main bank account. When that account is unavailable the platform routes the deposit through P2P, and if no withdrawal is there to match it, the money goes straight to a bank account within the same request — you still get one
PAYMENT_PAID. - A withdrawal with no match, or one that is only partly filled, is topped up by the platform itself within 15 minutes. Nothing is left hanging.
- Only two things are actually different: the withdrawal has to go to PromptPay, and you close the order from the webhook — never when the create call succeeds.
1. The flow in four steps
- 1
Get the account ready
Register the webhook URL and top up THB_P2P credit — fees and central-account transfers are deducted from it.
- 2
Deposit: create a slip session
Call Create Slip Payment and send the depositor to the redirect_url. They transfer within 10 minutes and attach the slip on that page; the system checks it, then PAYMENT_PAID arrives.
- 3
Withdrawal: PromptPay only
Create the withdrawal with receiver_bank "PromptPay". The system fills it leg by leg from deposits, and tops up whatever is still short within 15 minutes.
- 4
Settle from the webhook
Close the order only when the terminal event arrives, and poll the stretches that stay silent.
2. Create a deposit or a withdrawal
Both sides start with one API call. A deposit creates a slip session and hands the depositor a hosted page; a withdrawal is the normal THB withdrawal endpoint locked to PromptPay. You never check for a match first — the platform decides inside that one request, and everything after that arrives through the webhook.
Deposit: Create Slip Payment
The depositor transfers and attaches the slip on our page — matched or not, you get the same result back
Call /v1/p2p/public/session/create with role "DEPOSITOR", the amount, and the bank account the customer will transfer from. The response carries a redirect_url — send the depositor there. That page shows the destination account, takes the slip, and checks it; you just wait for the result.
https://testnet.trustsig.xyz/v1/p2p/public/session/createPOST /v1/p2p/public/session/create
Content-Type: application/json
X-Idempotency-Key: <UUID>
{
"role": "DEPOSITOR",
"ttl_seconds": 1200,
"profile": {
"depositor": {
"sender_bank": "SCB",
"sender_account": "1234567890"
}
},
"amount": 2500,
"order_id": "ACME-D-2026-000123",
"order_user_reference": "USER134",
"return_url": "https://merchant.example.com/deposit/done"
}- Redirect the depositor to
redirect_urlas soon as you get it. The transfer, the slip upload, and the slip check all happen on that page — there is no separate slip API to call on the P2P rail. ttl_secondsis how long the payment link lives. The 10-minute transfer window is a different clock — it starts once the depositor is matched and sees the destination account.- The depositor has to transfer from the exact
sender_bank/sender_accountyou sent — a slip from another account will not clear. - No withdrawal to match means the platform switches to a direct bank transfer inside the same request.
deposit_typecomes back asCLASSIC, butpayment.idandPAYMENT_PAIDare the same as ever — do not tie your logic to the rail. - Do not credit the customer when the session is created or when the page returns to
return_url.PAYMENT_PAIDfrom the webhook is what actually closes the deposit; a reused slip comes back asPAYMENT_FAILED. - A rejected slip does not kill the order — the depositor can attach another one from the same page until the window closes.
Withdrawal: PromptPay only
Same endpoint as a THB withdrawal, but the destination has to be PromptPay
P2P withdrawals go out over PromptPay and nothing else. Use the same createRequest/fiat endpoint as a normal THB withdrawal, but set receiver_bank to "PromptPay" and put the phone number or national ID in withdrawal_address.
https://testnet.trustsig.xyz/v1/withdrawal/createRequest/fiatPOST /v1/withdrawal/createRequest/fiat
Content-Type: application/json
{
"amount": 10000,
"currency": "THB",
"receiver_bank": "PromptPay",
"withdrawal_address": "0812345678",
"receiver_name": "MR. John Snow",
"order_id": "ACME-W-2026-000456",
"order_user_reference": "USER900"
}- Bank accounts are not accepted on the P2P rail — a request with any other
receiver_bankwill not go through as P2P. - PromptPay is required because the depositor has to scan and pay within the window, and the destination can be matched more precisely.
- Keep the returned
id—GET /v1/withdrawal/detail/:idis the only way to read the legs back. - No match, or still short after 15 minutes, and the platform transfers the remainder itself. The withdrawal still ends as
WITHDRAWAL_COMPLETED; that top-up comes out of your THB_P2P credit.
3. Set up the webhook
Webhooks are how you find out anything finished. Register a URL, turn on HMAC, and verify the signature against JSON.stringify(body.data) — not the raw body. Answer 2xx within 10 seconds and do the real work asynchronously.
Two traps worth knowing up front: with no webhook URL registered you get no events and no error either, and changing the URL blocks new withdrawals for 24 hours.
