Orders
An order is one payment by one customer. Your server creates it, the customer pays it, and we close it with a callback.
Create an order
POST /gw/v1/orders
| Field | Type | Required | Meaning |
|---|---|---|---|
amount | number | yes | What the customer pays, in currency. Must be greater than zero; two decimals are kept. |
currency | string | no | ISO 4217 code. Default RUB. Must be enabled on the platform, priceable, and allowed on the cash desk. |
rail | string | depends | Sbp, C2c or Phone. May be omitted when the cash desk allows exactly one rail; required when it allows several. |
orderRef | string | no | Your own identifier, up to 255 characters. Makes the call idempotent for one hour. |
notifyUrl | string | no | Callback URL for this order. Falls back to the cash desk's URL; one of the two is required. Validated: see below. |
successUrl | string | no | Where the payment page sends the customer after a successful payment. |
failUrl | string | no | Where the payment page sends the customer after a cancelled or expired order. |
backUrl | string | no | The "back to the shop" link used when the two above are not set. |
lifetimeMinutes | integer | no | How long the payment details stay valid, 1 to 1440. Default 15. |
operator | string | no | For the Phone rail only: MTS, Beeline, Megafon, Tele2, Yota. |
targetBank | string | no | Reserved. Accepted and ignored by the current routing. |
What we validate, in order
- The merchant account is active and not blocked, else
MERCHANT_INACTIVE. - The cash desk is enabled by you, else
SHOP_INACTIVE; and not stopped by the platform, elseSHOP_STOPPED. - A callback URL exists, else
CALLBACK_URL_REQUIRED; and it passes the URL rules, elseINVALID_CALLBACK_URL. - The rail is resolvable:
RAIL_REQUIRED,INVALID_RAILorRAIL_NOT_ENABLEDotherwise. - The currency exists and is enabled, has a rate source, and is allowed on the cash desk:
INVALID_CURRENCY,CURRENCY_NOT_PAYABLE,CURRENCY_NOT_ENABLED. - A rate is available now, else
FX_RATE_UNAVAILABLE. The USD amount is computed. - The USD amount is within the cash desk limits, else
AMOUNT_BELOW_MINorAMOUNT_ABOVE_MAX. - No turnover limit is reached, else
TRAFFIC_LIMIT_REACHED. - If
orderRefwas seen for this cash desk in the last hour, the existing order is returned and nothing below runs. - The cascade looks for a provider. None found:
503 NO_AVAILABLE_METHOD, or400 MERCHANT_NOT_APPROVEDfor an account still under review.
Callback URL rules
The address must be an absolute https:// or http:// URL to a public host. We refuse
localhost, loopback, link-local, private ranges (10/8, 172.16/12, 192.168/16,
100.64/10), cloud metadata hosts, and any hostname that resolves to one of those. The check
runs when the order is created and again before every delivery, so a name that later points
inside a private network stops receiving callbacks.
Response
201 Created:
{
"status": "ok",
"result": {
"id": "ord_01M1P932FS0FQPNM3T63KM2XAT",
"orderRef": null,
"amount": "5000.00",
"currency": "RUB",
"settled": { "amount": "54.71000000", "currency": "USD", "rate": "91.40000000" },
"method": "Classic",
"rail": "Sbp",
"direction": "In",
"status": "Created",
"payment": {
"url": null,
"card": null,
"phone": "+79001234567",
"account": null,
"iban": null,
"upiId": null,
"holder": "I. IVANOV",
"bank": "Sber"
},
"createdAt": "2026-09-04T15:05:00Z",
"expiresAt": "2026-09-04T15:20:00Z"
},
"traceId": "0HN7A9K2M4P1X"
}
payment holds what the customer needs, keyed by what the value is: phone for SBP and phone
transfers, card for card transfers (C2c, Kaspi, Uzcard, Humo), account for a Korean
bank account, iban for Turkey (Iban, Havale), upiId for India, url when the provider
hosts its own page. On Kaspi both card and phone are filled and belong to the same
recipient. Every key is always present; the ones the rail does not use are null. Show them
yourself, or send the customer to the payment page.
For Upi, the customer's bank gives them a UTR after the transfer. Our payment page asks for it
and stores it on the order; GET /gw/v1/orders/{id} returns it as payerReference. It is a
hint for matching the transfer, not a status: only the provider's confirmation completes an
order.
The rate is captured here
settled.rate is the number of units of currency per one USD, spread included, at the moment
of creation. It is never recalculated: however long the customer takes, the order settles at
this rate, and you are credited settled.amount minus the fee.
orderRef and idempotency
orderRef is your own identifier, up to 255 characters. Send it on every order and store our
id next to it.
For one hour per cash desk it also makes the call idempotent:
- a repeat while the order is open or paid returns the same order,
201, with its current status, so a retried request never creates a second payment; - a repeat once the order is cancelled or expired creates a new order, so a customer who comes back for the same purchase gets fresh payment details instead of the closed order;
- a repeat with a different amount or currency is refused with
409 ORDER_REF_CONFLICT: the reference already means something else.
After the hour a repeat creates a new order.
Get an order
GET /gw/v1/orders/{id}
{id} is our ord_… id or your orderRef. When several orders share a reference, the newest
is returned.
{
"status": "ok",
"result": {
"id": "ord_01M1P932FS0FQPNM3T63KM2XAT",
"orderRef": "A-17",
"amount": "5000.00",
"currency": "RUB",
"settled": { "amount": "54.71000000", "currency": "USD", "rate": "91.40000000" },
"method": "Classic",
"rail": "Sbp",
"direction": "In",
"status": "Completed",
"subStatus": "SuccessfullyPaid",
"payment": { "url": null, "card": null, "phone": "+79001234567", "account": null, "iban": null, "upiId": null, "holder": "I. IVANOV", "bank": "Sber" },
"payerReference": null,
"commission": { "rate": "3.5000", "amount": "1.91485000", "currency": "USD" },
"credited": { "amount": "52.79515000", "currency": "USD" },
"cancelReason": null,
"createdAt": "2026-09-04T15:05:00Z",
"expiresAt": "2026-09-04T15:20:00Z",
"finishedAt": "2026-09-04T15:07:41Z"
},
"traceId": "0HN7A9K2M4P1X"
}
commission and credited are filled once the order completes; before that they are zero.
You do not need to poll: the callback arrives on its own. GET is for
reconciliation and for support.
Cancel an order
POST /gw/v1/orders/{id}/cancel
{ "reason": "customer changed their mind" }
The body is optional; reason is stored on the order and shown in the cabinet. Only unpaid
orders can be cancelled: a Completed order answers 409 ORDER_ALREADY_COMPLETED. Cancelling an
already cancelled order is a no-op. A cancellation sends order.cancelled to your callback URL.
Statuses
status | Meaning |
|---|---|
Created | Accepted; payment details issued; waiting for the customer. |
Payed | The provider reports a payment; the platform is confirming that the money landed. Not credited yet. |
Completed | Confirmed and credited to your balance. Terminal. |
Canceled | Closed without payment: expired, cancelled by you, or rejected. Terminal unless an appeal reopens it. |
Dispute | An appeal is open on this order. |
CompletedOnDispute | Reserved for an appeal outcome; today an accepted appeal completes the order as Completed. |
WaitingPayment | Reserved; not currently assigned. Treat like Created. |
subStatus adds detail: WaitingForPayment, WaitingAdminConfirmation, SuccessfullyPaid,
Cancelled, Expired. Branch on status; use subStatus for display.
Expiry
An order expires at expiresAt, lifetimeMinutes after creation. A worker checks every
200 milliseconds and cancels expired orders with subStatus: "Expired" and cancelReason: "expired", sending order.cancelled. A payment that arrives after expiry is not lost: the
provider's confirmation reaches us, support reconciles it, and the order completes with the
usual callback. If the customer says they paid after the timer ran out, open an
appeal.
Why an order sits in Payed
Payed means the provider said the customer paid, but the platform has not yet confirmed that
the funds arrived on its side. Live orders wait for that confirmation before the credit; the
callback comes with Completed. Sandbox orders skip the wait.