Bank card payments
The card payment flow does not require creating a contract first. Create an invoice and redirect the user to the returned payment URL.
5.1 Create invoice (card)
JSON API v3
POST /api/v3/projects/{projectId}/invoices
JSON API v3
{
"client_ip": "10.20.30.40",
"customer": {
"type": "undefined_bank_card",
"source": "my_app",
"country": "RU",
"merchant_customer_id": "client_12345",
"email": "test@test.com"
},
"order": {
"merchant_order_id": "order_123",
"price": {
"amount": "20.00",
"currency": "RUB",
"vat_included": true
}
}
}JSON API v3
| Field | Type | Required | Description |
|---|---|---|---|
customer. | string | Yes | "undefined_bank_card" — the payer enters the details on the payment page |
customer. | string (max 64) | Yes | Unique identifier of the merchant resource / brand. Guarantees global uniqueness of the source + merchant_customer_id pair |
customer. | string (ISO 3166) | Yes | Client country |
customer. | string (max 128) | Yes | Unique client ID in the merchant system |
customer. | string | No | Client email |
undefined_bank_card and bank_card are different scenarios. undefined_bank_card means “the payer enters the card on the payment page” and covers everything described in §5.1 and §5.2; contracts are not created with this type — a contract block sent alongside it is rejected with 400 BAD_REQUEST. Per the specification bank_card means accepting the card on the merchant’s own form (the PCI DSS contour, §5.3). For new payment-page integrations use undefined_bank_card: bank_card stays a deprecated alias and will be removed in 3.0.0.
JSON API v3
{
"success": true,
"data": {
"invoice_id": "1aa2b436-0031-0028-1a54-8abd8eb88c40",
"status": "Created",
"payment_status": "NOT_PAID",
"authorization": {
"type": "AUTH_PAGE",
"is_authorized": false,
"url": "https://pay.callisto.pay/checkout/1aa2b436003100281a548abd8eb88c40"
},
"price": {
"amount": "20.00",
"currency": "RUB",
"vat_included": true
},
"merchant_order_id": "order_123",
"created_at": "2025-10-04T08:46:45Z",
"expires_at": "2025-10-04T10:46:45Z",
"billing_channel": "BankRtp",
"customer": {
"type": "undefined_bank_card",
"source": "my_app",
"country": "RU",
"merchant_customer_id": "client_12345",
"email": "test@test.com"
}
}
}After receiving the response, redirect the client to the address from the response: authorization. in JSON, <AuthorizationUrl> in XML.
The 3-D Secure challenge is not a constant. Whether a confirmation page follows the card form depends on the acquirer, the integration type and fraud rules, so the integration has to work in both modes. The presence of the challenge does not affect the response payload of this flow: the redirect address always arrives. Both modes can be exercised in the sandbox, where 3-D Secure is a project setting — see Flow: 3-D Secure.
Returning the customer to your site
Once the payment finishes — successfully or not — the customer is sent back to the return address configured for your project. The platform appends the result parameters to it:
| Parameter | Value |
|---|---|
invoiceId | Invoice identifier in the platform |
merchantOrderId | Order identifier in your system |
success | true — the payment went through; false — an error or a cancellation |
https://your-site.com/payment/result?invoiceId=1aa2b436-0031-0028-1a54-8abd8eb88c40&merchantOrderId=order_123&success=truesuccess is good enough for a result screen, but not for releasing goods. The parameters come from the customer’s browser and carry no signature, and the customer may never reach your site at all — a closed tab, a lost connection. The source of payment state is the webhook and GET /invoices/{invoice_id}.
JSON API v3
POST /invoices — card, with redirect
curl
RESPONSE=$(curl -sS -X POST "$BASE/api/v3/projects/$PROJECT_ID/invoices" \
-H "Content-Type: application/json" \
-H "X-Login: $LOGIN" \
-H "X-Timestamp: $TS" \
-H "X-Signature-Version: 3" \
-H "X-Signature: $SIG" \
-d '{
"client_ip": "10.20.30.40",
"customer": {
"type": "undefined_bank_card",
"source": "my_app",
"country": "RU",
"merchant_customer_id": "client_12345"
},
"order": {
"merchant_order_id": "order_123",
"price": { "amount": "20.00", "currency": "RUB", "vat_included": true }
}
}')
echo "$RESPONSE" | jq -r '.data.authorization.url'5.2 SBP and SberPay
JSON API v3
SBP and SberPay have no request of their own. The invoice is created exactly as in §5.1, and the payment method is chosen with query parameters on the address from the response (authorization. in JSON, <AuthorizationUrl> in XML) — the platform moved to this scheme in v2.78.0.
| Parameters on the payment address | What opens |
|---|---|
| (no parameters) | The bank card form |
?useSbp=true | SBP — QR code |
?payType=Sber&deviceType=Desktop | SberPay for desktop |
?payType=Sber&deviceType=Mobile | SberPay for mobile devices |
https://pay.callisto.pay/checkout/1aa2b436003100281a548abd8eb88c40?useSbp=trueThe payment_method field in the request body is deprecated. The payment-method selector came from the May 2026 revision of the specification and has been dropped by the platform. The field is accepted for one more minor version and goes away in 3.0.0; it does not change the response — you get the same authorization., only with the parameters from the table above already appended.
One invoice can be opened any way until it is paid. The payer can come back to the payment address and pick a different method — there is no need to create a separate invoice per method.
SBP bindings are not available yet. Repeat charges over SBP through a contract are on the roadmap; the protocol has no such flow today. Two paths are available for charges without re-confirmation: a contract on the phone number confirmed with a PIN code (DCB) and card binding (§5.3).
5.3 Card binding (card-on-file)
PCI DSS certification is required. In this scenario card data passes through the merchant, and the spec makes that a hard condition.
Without certification, use the “Payment via Bank Payment Form” scenario — §5.1 above: the invoice is created without card data, and the customer enters it on the platform side after being redirected to the payment URL from the response.
Card data is entered on the merchant side and passed as a <BankCard> block inside <BankCardCustomer> (XML) or as the customer. object with customer.type = "bank_card" (JSON). The card is bound via a CreateCustomerContract / POST /contracts contract (the response returns AuthorizationType: SKIP, no PIN confirmation), and subsequent charges go through the regular invoice creation referencing the contract, without re-entering the card details. The presence of the card-data block in requests is governed by the integration type and the individual requirements of the acquirer — it is enabled per partner.
customer. field | Required | Description |
|---|---|---|
pan | Yes | Card number, 16 digits |
expiration_month | Yes | Expiry month, 1–12 |
expiration_year | Yes | Expiry year, 2 or 4 digits |
security_code | Yes | CVV/CVC, 3–4 digits. Never stored, in any form |
cardholder | No | Cardholder name, up to 128 characters |
The block is only allowed together with contract. — the contour always works through contracts. The card is returned in no response and is not stored: only the last 4 digits of the number remain, and the request log receives the body already masked.
Card data goes to a dedicated host. The specification reserves a separate address for the PCI DSS contour: only invoice creation and contract creation there accept customer., while everything else — authorization, cancellation, refunds, invoice lookups — is served by the main host, which rejects a card sent to it. Ask the platform team for the contour address.
A step-by-step walkthrough with sandbox emulation rules — Flow: card binding; the method format — Create contract.