Skip to main content

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
FieldTypeRequiredMeaning
amountnumberyesWhat the customer pays, in currency. Must be greater than zero; two decimals are kept.
currencystringnoISO 4217 code. Default RUB. Must be enabled on the platform, priceable, and allowed on the cash desk.
railstringdependsSbp, C2c or Phone. May be omitted when the cash desk allows exactly one rail; required when it allows several.
orderRefstringnoYour own identifier, up to 255 characters. Makes the call idempotent for one hour.
notifyUrlstringnoCallback URL for this order. Falls back to the cash desk's URL; one of the two is required. Validated: see below.
successUrlstringnoWhere the payment page sends the customer after a successful payment.
failUrlstringnoWhere the payment page sends the customer after a cancelled or expired order.
backUrlstringnoThe "back to the shop" link used when the two above are not set.
lifetimeMinutesintegernoHow long the payment details stay valid, 1 to 1440. Default 15.
operatorstringnoFor the Phone rail only: MTS, Beeline, Megafon, Tele2, Yota.
targetBankstringnoReserved. Accepted and ignored by the current routing.

What we validate, in order

  1. The merchant account is active and not blocked, else MERCHANT_INACTIVE.
  2. The cash desk is enabled by you, else SHOP_INACTIVE; and not stopped by the platform, else SHOP_STOPPED.
  3. A callback URL exists, else CALLBACK_URL_REQUIRED; and it passes the URL rules, else INVALID_CALLBACK_URL.
  4. The rail is resolvable: RAIL_REQUIRED, INVALID_RAIL or RAIL_NOT_ENABLED otherwise.
  5. 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.
  6. A rate is available now, else FX_RATE_UNAVAILABLE. The USD amount is computed.
  7. The USD amount is within the cash desk limits, else AMOUNT_BELOW_MIN or AMOUNT_ABOVE_MAX.
  8. No turnover limit is reached, else TRAFFIC_LIMIT_REACHED.
  9. If orderRef was seen for this cash desk in the last hour, the existing order is returned and nothing below runs.
  10. The cascade looks for a provider. None found: 503 NO_AVAILABLE_METHOD, or 400 MERCHANT_NOT_APPROVED for 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

statusMeaning
CreatedAccepted; payment details issued; waiting for the customer.
PayedThe provider reports a payment; the platform is confirming that the money landed. Not credited yet.
CompletedConfirmed and credited to your balance. Terminal.
CanceledClosed without payment: expired, cancelled by you, or rejected. Terminal unless an appeal reopens it.
DisputeAn appeal is open on this order.
CompletedOnDisputeReserved for an appeal outcome; today an accepted appeal completes the order as Completed.
WaitingPaymentReserved; 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.