Skip to content

SBP payments

The payer sends money by phone number through the Faster Payments System (SBP) in their bank app. You create a payment, the API issues the recipient's phone number and name, you show them to the payer and wait for the result.

  1. Your server → APIPOST /api/v1/payments with payment_method=sbp
  2. APIPicks requisites, usually within 15 seconds
  3. API ⇢ Your server201: status=processing, requisite=+7…, holder_name
  4. Your server → PayerPhone number, recipient, amount and deadline
  5. PayerTransfers via SBP in their bank app
  6. API → Your serverCallback: completed or canceled
  7. Your server ⇢ APIYou reply 2xx

Create a payment

POST /api/v1/payments — a signed request with a JSON body.

POST {{base_url}}/api/v1/payments HTTP/1.1
Authorization: Bearer nl_test_...
Content-Type: application/json
Content-Digest: sha-256=:…:
X-Request-ID: 0b6d2c4e-8f1a-4d3b-9e7c-5a2f1b3c4d5e
X-Request-Nonce: 9f86d081884c7d659a2feaa0c55ad015
Signature-Input: sig1=(…);created=…;expires=…;keyid="ed25519-…";alg="ed25519"
Signature: sig1=:…:

{"merchant_payment_id":"order-42","amount":3400,"payment_method":"sbp","bank_code":"sber","callback_url":"https://merchant.example/callbacks"}

Request fields

Field Type Req. Rules
merchant_payment_id string yes Your order id. 1–64 characters A-Za-z0-9._:-. Unique per project: it is the idempotency key
amount number or string yes Amount in major currency units: 3400 or "3400.50". Positive, at most 6 decimals. A string is safer than a number: no rounding errors
payment_method string yes sbp
currency string no Currency code. Default RUB
geo_code string no Country, two letters. Default RU
bank_code string no The payer's bank — a lower-case code from our bank catalog: sber, tinkoff, ozon. Not a BIC. Empty means any bank
is_intrabank boolean no true for a transfer within one bank. Default false
callback_url string no Where to send this payment's callback. Absolute https://, up to 2048 characters, no credentials, public host. Otherwise the project URL is used

No other fields are accepted: an unknown field returns 400 bad_request. Test mode is a project setting, not a request field. A request without currency and geo_code and the same request with "RUB" and "RU" are the same request.

There is no deadline in the request: how long a payment lives is a project setting, 15 minutes from creation by default. Your manager can change it.

Response

201 Created for a new payment. 200 OK for a repeat with the same merchant_payment_id and the same body: the existing payment is returned, no new one is created.

{
  "id": "3f1c2b9e-6a8d-4c4b-9a71-2c7f5d1e0a42",
  "merchant_payment_id": "order-42",
  "flow": "runtime_live",
  "status": "processing",
  "amount": 3400,
  "initial_amount": 3400,
  "currency": "RUB",
  "geo_code": "RU",
  "payment_method": "sbp",
  "bank_code": "sber",
  "is_intrabank": false,
  "callback_url": "https://merchant.example/callbacks",
  "is_test": false,
  "merchant_information": {
    "course": 100.00,
    "rate": 11.00,
    "amount_usdt": "34.0000",
    "amount_rate": "3026.0000",
    "amount_usdt_rate": "30.2600"
  },
  "requisite": "+79991234567",
  "holder_name": "Иван Петров",
  "created_at": "2026-09-29T09:30:00Z",
  "updated_at": "2026-09-29T09:30:01Z"
}
Field Meaning
id Payment id. Store it next to the order
merchant_payment_id Your order id
flow runtime_live — a regular payment, project_test — a project test payment
status Current status, see below
amount Amount to pay, in major currency units. May change after a payment re-check
initial_amount Amount from the request
currency, geo_code, payment_method, is_intrabank As in the request, with defaults applied
bank_code The payer's bank from the request, or null. Not the requisite's bank
callback_url Callback URL from the request, or null
is_test true for a project test payment
merchant_information Settlement in USDT; null until requisites are issued
requisite Recipient phone number for the SBP transfer; null until issued
holder_name Recipient name as the payer's bank will show it, or null
qrcode_link, deeplink_url Only when the requisites have them: you can show a QR code or an "Open bank app" button
error_code, error_comment Only for a payment in error: why no requisites were issued
created_at, updated_at Creation and last change time, UTC

bank_code, callback_url, merchant_information, requisite and holder_name are always present; the value may be null. New fields may be added without a new API version: ignore unknown fields.

Settlement merchant_information

What you receive for the payment. The exchange rate and fee are fixed when requisites are issued and do not change afterwards: settlement is not recalculated at current rates. After a payment re-check, settlement uses the new amount with the same rate and fee. These are the same figures that are credited to the project balance.

Field Meaning Type, decimals
course Rate: units of the payment currency per 1 USDT number, 2
rate Project fee, % number, 2
amount_usdt Payment amount in USDT string, 4
amount_rate Amount after the fee, in the payment currency string, 4
amount_usdt_rate Amount after the fee in USDT — credited to the project balance string, 4

course and rate are numbers with exactly two decimals (100.00); amounts are decimal strings. Extra digits are truncated, never rounded up; the fee is rounded down. In the example above: 3400 RUB at a rate of 100 and an 11% fee is 34 USDT, a fee of 374 RUB (3.74 USDT), 30.26 USDT credited.

Parse course and rate as decimals

Use a decimal type (Decimal, BigDecimal, json.Number), not a float, to keep the precision.

Do not recompute amount_usdt

The rate may be stored with more than two decimals, so amount / course can differ from amount_usdt in the last digit. amount_usdt is authoritative.

What to show the payer

Show the requisites only when the status is processing and requisite is present. The payer transfers exactly amount to requisite.

  • Phone number from requisite — large, with a "Copy" button.
  • Recipient from holder_name: the payer checks the name in the bank app before sending.
  • Amount — exactly amount, to the kopeck. Warn that a different amount may not be credited automatically.
  • Deadline — a countdown from created_at over your project's payment lifetime (15 minutes by default). After it, the requisites must not be used.
  • Next step — "Come back to this page after the transfer". Update the status from the callback or by polling.

Do not cache requisites

Requisites are issued for one payment. For a new order or another attempt, create a new payment with a new merchant_payment_id.

Statuses

final statusesrequisites issuedexpired / canceled /not confirmedno requisites issuedpayment re-checkre-check outcomecreatedprocessingcompletedcancelederrorappeal
Status Meaning What to do
created Accepted, no requisites yet Wait. The response usually already says processing
processing Requisites issued, waiting for the transfer Show the requisites to the payer
completed Payment confirmed Deliver the goods or service
canceled Not paid: the lifetime ran out, it was canceled, or the payment could not be confirmed Offer to pay again with a new payment
error No requisites were issued, reason in error_code Offer to pay again with a new payment
appeal A closed payment is being re-checked Wait for the resolution: a callback follows if it ends in completed or canceled

error happens only before requisites are issued. From processing a payment goes only to completed or canceled: if the payment cannot be confirmed after requisites were issued, it is also canceled, with no error_code. completed, canceled and error are final. Only a payment re-check or a support decision can change them. All error codes are in Statuses and errors.

Getting the result

Callbacks are the primary channel. When a payment becomes completed or canceled, the API sends a POST to your URL. Verification and handling are in Callbacks. There is no callback for error: it happens only before requisites are issued and is visible in the create response and when you read the payment. Once the payer has seen requisites, the result arrives as a callback.

Polling is the safety net. If no callback arrived, read the payment:

curl -sS "{{base_url}}/api/v1/payments/3f1c2b9e-6a8d-4c4b-9a71-2c7f5d1e0a42" \
  -H "Authorization: Bearer $API_TOKEN"

The response has the same shape as on create. If the project policy has signature_required, sign the GET too (empty body).

Recommended:

  • The payer's checkout page polls your server, not the API.
  • Your server reads the payment from the API only when a callback is missing, for example once a minute for processing payments past the payment lifetime.
  • Do not poll a payment more often than every 5–10 seconds: the project shares one rate limit.

Edge cases

Response with status error right away. 201 with status: "error" means the payment was saved but no requisites were issued. The reason is in error_code (provider_no_requisites, provider_result_ambiguous). No callback follows for this payment. There is nothing to show the payer — offer to try later with a new payment.

422 on create. The payment cannot be processed: payment_not_covered (method or amount is outside your contract), payment_rate_unavailable (no exchange rate), payment_not_routable (the payment cannot be accepted with this method right now). If the payment was saved, the body carries payment_id: the payment stays in error, the id is taken, and repeating the same request returns it (200). A new attempt needs a new id. Without payment_id no payment was created and the id is free.

Timeout or dropped connection. You do not know whether the payment exists. Repeat the same request with the same merchant_payment_id and a fresh nonce and signature: if the payment exists you get it back (200), otherwise it is created. No duplicate is possible.

The payer sent a different amount. The payment may be re-checked and the actual amount confirmed. You then receive a completed callback with the new amount: amount changes, initial_amount stays. Fulfil the order by the callback amount.

Deadline passed. A payment not paid within its lifetime becomes canceled, and a callback is sent. If the payer paid after the deadline, the payment is reviewed manually: a confirmed payment brings a new completed callback.

The payment could not be confirmed after requisites were issued. The payment then becomes canceled with no error_code, and a callback is sent. If the money did arrive, the payment is reviewed manually and a new completed callback follows.

Slow response. Requisites are picked during your request. Keep the client timeout at 15 seconds or more.

See also