Sandbox knobs: test_info
XML: <TestInfo> inside CreateInvoiceRequest · JSON: the test_info object in the request body.
Every test knob lives in a single block, so when you move to production you just drop the whole block — the rest of the request stays the same. Production has no such block.
| JSON | XML | Values | Effect |
|---|---|---|---|
expected_billing_result | ExpectedBillingResult | Success, Failed, SuccessSequencePayment, FailedSequencePayment | Billing outcome. With Failed* the callback arrives with PaymentStatus=FAILED / payment_status=NOT_PAID and code 210 PAYMENT_CHANNEL_NOT_ENOUGH_MONEY |
callback_delay_seconds | CallbackDelaySeconds | integer 0–60, default 10 | Delay before the first callback delivery. 0 means the next worker cycle (handy for demos), a large value helps debugging retries |
callback_url | — | https://… | Callback address instead of the one configured for the project. Only for partners with an explicit permission — see below |
An invalid value never breaks the request: the mock logs a warning and falls back to the default.
Invoice state
GET /invoices/{invoice_id} and XML GetInvoice return the state of the stored invoice. The XML flow differs in one thing: the deferred billing outcome arrives not after callback_delay_seconds but 6 seconds after the first GetInvoice. Resolved in this order:
| When | status | payment_status |
|---|---|---|
| Right after creation (MSISDN) | Created | NOT_PAID |
| Card or SBP — before payment | WaitPaymentConfirmation | NOT_PAID |
After callback_delay_seconds, outcome Success* | Processed | PAID |
After callback_delay_seconds, outcome Failed* | Failed | NOT_PAID + result_code=210 |
| After paying on the checkout page | Processed | PAID |
| Card error (reserved last 4 digits) | Cancelled | NOT_PAID |
After cancel / refund | Cancelled / Refund | NOT_PAID / PAID |
An unknown invoice_id returns 404 with error.code = INVOICE_NOT_FOUND. Lookup by merchant_order_id is scoped to your project.
Your own callback address
The callback_url field is enabled on request and does not work with the demo credentials from “Access and test data”: those login and secret are public, and the field makes the sandbox send a request to an arbitrary address. For non-public credentials the flag is set in the Admin UI; otherwise the response is 400 TEST_CALLBACK_URL_FORBIDDEN.
The address is validated twice — when the request is accepted and again right before delivery (DNS may have changed in between):
| Requirement | Otherwise |
|---|---|
https scheme only | 400 TEST_CALLBACK_URL_REJECTED |
Host resolves and the resulting IP is public — private and reserved ranges (10/8, 172.16/12, 192.168/16, 127/8, 169.254/16, ::1, fc00::/7) are rejected | 400 TEST_CALLBACK_URL_REJECTED |
| At most 30 callbacks to a request-supplied address per minute per project | TEST_CALLBACK_URL_RATE_LIMITED |
| Redirects are not followed, delivery timeout is 5 seconds | delivery counts as failed |
The rejection happens before the invoice is created: if the request was rejected, no invoice exists and you can retry it as is.
JSON API v3
{
"client_ip": "10.20.30.40",
"customer": { "type": "mobile_phone", "msisdn": "79600000000" },
"order": {
"merchant_order_id": "ORDER-001",
"price": { "amount": "99.00", "currency": "RUB", "vat_included": true }
},
"test_info": {
"expected_billing_result": "Failed",
"callback_delay_seconds": 0,
"callback_url": "https://dev-42.example.com/callbacks"
}
}HTTP/1.1 400 Bad Request
{
"success": false,
"error": {
"code": "TEST_CALLBACK_URL_REJECTED",
"message": "test_info.callback_url rejected: address 10.0.0.5 belongs to a private or reserved range"
}
}