Hosted Cashier Page
cashier_url — optional field on the deposit response
A deposit response can carry a cashier_url: a payment page we host for that one deposit. It shows the PromptPay QR or the destination account number, the exact expected_amount, a countdown, and refreshes the status by itself. Instead of building a payment screen, you can simply send the customer there.
pay_to / qr_payload, ignore the field — nothing else changes, and the same deposit.success webhook still decides the outcome.Two ways to show the payment instructions
merchant creates the deposit (POST /v1/deposits)
|-- (a) merchant renders its own page from pay_to / qr_payload <- unchanged
\-- (b) merchant redirects the customer to cashier_url <- optional, nothing to build
|
v
wait for the deposit.success webhook (identical either way)
(a) is the classic S2S integration: take pay_to / qr_payload + expected_amount from the response and draw your own payment screen.
(b) uses cashier_url — redirect the customer to that URL and build nothing.
Both branches end the same way: the authoritative result arrives on the deposit.success / deposit.expired webhook. See Webhook Events.
Where cashier_url comes from
| Endpoint | Returns cashier_url? |
|---|---|
POST /v1/deposits | On creation, when the hosted page is available for your account. |
GET /v1/deposits/:id | Only while status is PENDING, and it is exactly the same link creation returned — use it to recover the page for a customer who closed the tab. |
The link has the shape <cashier-origin>/c/<deposit_id>/<cashier_token>, where cashier_token is a random 128-bit value (32 hex characters) minted per deposit:
https://pay.example.com/c/8f2b1c4e-7a90-4d2f-9b3a-1c2d3e4f5a6b/9f2c1d4e7a3b5c8d0e1f2a3b4c5d6e7f
The host above is only an example. The cashier origin is a separate domain from the API — never assume it shares api.unkpay.co, its cookies, or its CORS policy — and it is configured server-side and may change. Always redirect to the cashier_url value exactly as returned; never hardcode the host or rebuild the URL yourself.
cashier_url is optional and may be absent entirely (the key is omitted, not null) — for example when the hosted page is not available for your account, when the deposit is already terminal, or on deposits created before this feature shipped. Always code for its absence: if it is missing, render your own page from pay_to / qr_payload as before. Never treat a missing cashier_url as an error.Treat the link as a per-customer secret
- The token is deliberately separate from
deposit_id, becausedeposit_idtravels widely (webhooks, your back office, logs, support tickets) and leaking it must never open a customer's payment page. - Give the link only to the customer who owns the order (redirect / their SMS / their email).
- Never write it to shared logs, analytics events, APM or error trackers, or a group chat.
- It does not expire on its own — it lives as long as the deposit and cannot be revoked.
What the page shows in each state
- While
PENDING: the QR / destination account number, the exactexpected_amount, and a countdown. The page refreshes the status by polling on its own — there is no push channel, so do not build latency guarantees on it. - Once the deposit is
CREDITED,EXPIREDorCANCELLED: the page still opens, but shows only the result — no QR, no account number, nothing payable. - Cancelling a deposit does not revoke the link. It keeps opening and simply reports the cancellation, so stop showing or sending it after you cancel.
- A deposit can still read
PENDINGfor a short moment aftermatch_window_untilhas passed (expiry is swept asynchronously). Do not redirect a customer whenmatch_window_untilis already in the past — create a new deposit instead.
The customer slip-upload box
Some deposits are allocated to a destination account that accepts transfer slips. On those, the cashier page adds a box where the customer uploads their transfer slip; the system verifies it with the bank and credits the deposit.
You do nothing extra: no field in the deposit response tells you which deposits get the box, you cannot request or disable it, and you still wait for the same deposit.success webhook with the same payload.
EXPIRED or CANCELLED the page refuses the upload — create a new deposit instead.Test mode (sandbox)
Deposits created with a unk_test_... key do not carry cashier_url — neither from POST /v1/deposits nor from GET /v1/deposits/:id. The sandbox rehearses create → Simulate Transfer → deposit.success, which is the flow your server code depends on. Since the field is optional anyway, the correct sandbox behavior is the same as the live fallback: when cashier_url is missing, render your own page.
