Callback notifications
When an order has a result, ST Pay sends a POST to the callback_url you sent when creating that order.
When Callbacks are sent
| Event | status | Sent? |
|---|---|---|
| Collection succeeded | success | Sent |
| Collection rejected (rejected manually by ST Pay, or rejected by the system when the 30 minutes expire) | rejected | Sent |
| Collection late credit | late_credited | Not sent. Confirm with an order query; see Order status |
| Payout paid | paid | Sent |
| Payout rejected | rejected | Sent |
Callback URL
- Send it as
callback_urleach time you create an order (collection, payout). You do not need to give it to ST Pay in advance. Each order can use a different URL, for example separate URLs for the test and production environments. - It cannot be changed after the order is created. Automatic Callbacks and manual resends by ST Pay both go to the URL sent when the order was created. If you later change your URL, notifications for older orders still go to the old URL. If the old URL can no longer receive them, use order query to confirm the results of those orders.
- Up to 500 characters. It is stored exactly as sent, without any conversion.
- Both
httpandhttpsare accepted.httpsis recommended, becausehttpsends the order numbers and amounts in plain text. Either way, always verifysignto reject forged or tampered notifications. - When you use
https, your server's TLS certificate must be issued by a public certificate authority, be within its validity period, match the host name in the URL, and be served with the complete certificate chain (including intermediate certificates). A self-signed, expired, or mismatched certificate, or a missing intermediate certificate, counts as a delivery failure and is not retried. - It must not contain credentials (
user:pass@),#, whitespace, or non-ASCII characters. Convert internationalized domain names to punycode (starting withxn--). A path and query string are allowed. - The host name may contain only English letters, digits,
., and-(no underscore_), or be an IPv6 address in[ ]. If a port is given, it must be 1 to 65535 (a bare:is not allowed). - The path and query must not contain
|,{,},^,",<,>,\, or backticks as-is, and%must be followed by two hexadecimal digits. URL-encode these characters first (for example, write%as%25). - A missing, empty, or whitespace-only value returns
missing_parameter. - It must not be a local or private network address, in both the test and production environments:
localhost, domains ending in.localhost,.local,.localdomain,.internal,.intranet,.lan,.corp,.home.arpa, or.private, and host names without a dot.- Private, loopback, link-local, CGNAT, reserved, documentation, and multicast IP addresses, such as
10.x.x.x,172.16.x.x–172.31.x.x,192.168.x.x,127.x.x.x,169.254.x.x, and100.64.x.x. For IPv6, only global unicast addresses (2000::/3) are accepted, excluding Teredo2001::/32, documentation2001:db8::/32, and 6to42002::/16. - IP addresses written as a single number, in hexadecimal or octal, or with parts omitted, such as
2130706433,0x7f.0.0.1,0177.0.0.1, and127.1. - A public IP address can be used directly.
- If these rules are not met, order creation returns
invalid_callback_url(orinvalid_parameterfor a malformed URL or one longer than 500 characters), and no order is created. - Before every send (including manual resends), ST Pay resolves the domain again. If resolution fails, or any resolved address is not a public address, the Callback is not sent and counts as a delivery failure. Make sure the domain resolves on the public internet and points only to public IP addresses.
Request format
- Method
POST,Content-Type: application/json. The body is a JSON object, and all values are strings. - Includes
sign. The signature rules are the same as for API requests and use your API secret key (see Key rotation). Callbacks do not includeapi_identityortimestamp. - ST Pay waits at most 5 seconds to connect and at most 10 seconds for a response, and does not follow redirects (3xx): a 3xx response counts as a delivery failure. Use the final URL directly; for example, an
httptohttpsredirect or a missing trailing/can cause a redirect. - The Callback body does not include
callback_url. - Requests always carry
User-Agent: QPay-Callback/1.0andAccept: */*. If your Callback URL is behind Cloudflare, a WAF, or another firewall, allow this User-Agent andPOSTrequests; otherwise they may be blocked with 403 and count as a delivery failure.
What you must respond with
Respond with HTTP 2xx and a response body of plain text ok (leading and trailing whitespace is ignored). Any other response, including a timeout, a non-2xx status, or a body other than ok, is treated as a delivery failure.
HTTP/1.1 200 OK
Content-Type: text/plain
ok
Callbacks are not retried automatically
Each order's Callback is sent automatically only once. If delivery fails, only ST Pay staff can resend it manually, and a resend always goes to the callback_url sent when the order was created. Therefore:
- Do not rely on Callbacks alone. For orders that have had no result for a long time, query them periodically to confirm.
- The same order may receive more than one Callback (manual resends). Process them idempotently using
order_noplusstatus, and respondokdirectly to notifications you have already processed. - A Callback delivery failure does not affect the transaction result; the order status and balance are not reverted because of it.
Fields
| Field | Collection succeeded | Collection rejected | Payout paid | Payout rejected | Description |
|---|---|---|---|---|---|
order_no | ✓ | ✓ | ✓ | ✓ | ST Pay order number |
merchant_order_no | ✓ | ✓ | ✓ | ✓ | Merchant order number |
status | success | rejected | paid | rejected | Order status |
received_amount | ✓ | Received amount | |||
fee | ✓ | ✓ | Fee | ||
settlement_amount | ✓ | Settlement amount | |||
payable_amount | ✓ | Payable amount (including the tail) | |||
amount | ✓ | ✓ | Payout amount | ||
finished_at | ✓ | ✓ | ✓ | ✓ | Completion time |
sign | ✓ | ✓ | ✓ | ✓ | Signature |
Callbacks do not include the rejection reason. If you need it, get it with an order query.
If your collection and payout orders use the same callback_url, the status of both can be rejected, and a collection order and a payout order can have the same merchant order number. Match Callbacks to orders using the order_no you received when creating the order. If you want to match by merchant order number, make sure your collection orders and payout orders use order numbers that never collide, for example by adding different prefixes.
Handling recommendations
- Parse the JSON and recompute the signature using all the fields you received (except
sign), then compare. Do not pick only the fields listed in this document, because fields may be added in the future. - If the signature does not match, discard the Callback and do not update the order.
- Find your order by
order_no. If you have already processed the same result, respondokdirectly. - Respond
okafter updating the order. Run time-consuming follow-up work (shipping, notifying the member, etc.) in the background so that you do not exceed 10 seconds. - For a successful collection, credit the order with
received_amount(the amount actually received, orsettlement_amountafter the fee), not the amount at order creation. If the received amount differs from the amount you expected, send it for manual review instead of crediting it automatically. - Once an order is in a final status (
success,paid,rejected), do not roll it back when a later notification with a different status arrives. Log it for manual review; when in doubt, the order query result prevails.
Examples
All signatures below are actual values computed with the sample key.
{
"order_no": "C202610131000051230000",
"merchant_order_no": "MO20261013-0001",
"status": "success",
"received_amount": "1000.37",
"fee": "25.00",
"settlement_amount": "975.37",
"finished_at": "2026-10-13 10:12:30",
"sign": "49d9d35c3b3a97a55fe60976a331d36913b0d3cd9b82e840e0315c445653d207"
}
{
"order_no": "C202610131001178820000",
"merchant_order_no": "MO20261013-0002",
"status": "rejected",
"payable_amount": "2000.58",
"finished_at": "2026-10-13 10:31:17",
"sign": "cd85bf732776bb2897f6e767dda873ce580330f979be3f812c390ec09af0d2f4"
}
{
"order_no": "P202610131005224410000",
"merchant_order_no": "PO20261013-0001",
"status": "paid",
"amount": "500.00",
"fee": "7.50",
"finished_at": "2026-10-13 10:20:41",
"sign": "64e3b813fb086b98c1a4e68c2689423ff347336219f182293e5344a3f6853562"
}
{
"order_no": "P202610131006403520000",
"merchant_order_no": "PO20261013-0002",
"status": "rejected",
"amount": "3000.00",
"finished_at": "2026-10-13 10:25:09",
"sign": "bdf0a8031ac0beff801a4b2591793ea6d6edbfd27dbdadce24ab871602f99094"
}
The string to sign for the collection success example:
fee=25.00&finished_at=2026-10-13 10:12:30&merchant_order_no=MO20261013-0001&order_no=C202610131000051230000&received_amount=1000.37&settlement_amount=975.37&status=success