Recurring payments overview
Save the payer's payment source once and charge it later without the payer
Recurring payments let you charge a payer again without their participation: subscriptions, auto top-ups, installments. The payer confirms a binding once — a payment source saved at the payment provider. After that you create charges by the binding ID; the payer does not open a payment page.
Before you start
Authorization
How to sign your requests with the API token.
Recurring payments are enabled per payment method. Ask your manager to enable them; today they are available for SBP payments in RUB. Orders without the recurring object work exactly as before.
How it works
Save the binding
Create the first payment with recurring.saveBinding: true, or create a binding without a payment. The binding starts as PENDING; store its id.
The payer confirms
The payer pays the first order (and agrees to the subscription in their banking app) or confirms the binding on the provider page. The binding becomes ACTIVE and you receive a RECURRING_BINDING_UPDATE webhook.
Charge the binding
Create an order with recurring.bindingId. It is charged right away, without a payment link; the result comes in the usual order webhook.
Delete the binding
When the subscription ends, delete the binding: further charges are rejected.
Save a binding with the first payment
A regular order creation request with the recurring object. Everything else — customer fields, payment link, H2H QR link, webhooks — is the same as for the payment method.
POST /v1/orders| Parameter | Type | Required | Description |
|---|---|---|---|
externalUserId | string | ✅ Yes | Payer ID in your system, up to 64 characters. The binding belongs to this payer |
recurring.saveBinding | boolean | ✅ Yes | true — save the payment source after a successful payment |
recurring.purpose | string | ❌ No | Subscription description shown in the banking app, up to 255 characters. Default — purpose |
recurring.externalId | string | ❌ No | Binding ID in your system, unique per merchant, up to 255 characters |
curl -X POST "https://api.1capital.capital/v1/orders" \
-H "Content-Type: application/json" \
-H "X-Api-Token: YOUR_API_TOKEN" \
-d '{
"amount": "990",
"currency": "RUB",
"serviceId": 12,
"purpose": "Subscription, October",
"externalId": "order_1234",
"externalUserId": "user_987",
"customer": { "phone": "+79991234567", "ip": "198.51.100.24" },
"recurring": {
"saveBinding": true,
"purpose": "Monthly subscription",
"externalId": "sub-42"
}
}'The response is the order object with the binding ID in recurringBindingId. The order is routed only to the payment methods of the service that support recurring payments.
For SBP the payer gets a subscription QR link (https://sub.nspk.ru/...) instead of a one-time one: by paying it they also agree to future charges. When the order is COMPLETED, the binding becomes ACTIVE. If the order ends as FAILED, EXPIRED or CANCELED, the binding becomes FAILED.
Create a binding without a payment
Saves the payment source without charging the payer, for example for a free trial.
POST /v1/recurring/bindings| Parameter | Type | Required | Description |
|---|---|---|---|
externalUserId | string | ✅ Yes | Payer ID in your system, up to 64 characters |
serviceId | integer / null | ❌ No | Service whose payment method saves the binding. Default — your default service |
purpose | string / null | ❌ No | Subscription description shown in the banking app |
externalId | string / null | ❌ No | Binding ID in your system, unique per merchant |
callbackUrl | string / null | ❌ No | Webhook URL for this binding |
curl -X POST "https://api.1capital.capital/v1/recurring/bindings" \
-H "Content-Type: application/json" \
-H "X-Api-Token: YOUR_API_TOKEN" \
-d '{
"externalUserId": "user_987",
"serviceId": 12,
"purpose": "Monthly subscription",
"externalId": "sub-43"
}'The response is the binding object with confirmationUrl: redirect the payer there to confirm the binding in their bank. The binding stays PENDING until it is confirmed and becomes FAILED if it is not confirmed within 24 hours.
Charge a binding
POST /v1/orders| Parameter | Type | Required | Description |
|---|---|---|---|
amount | string | ✅ Yes | Charge amount |
recurring.bindingId | string (UUID) | ✅ Yes | ID of an ACTIVE binding |
serviceId | integer / null | ❌ No | Default — the service the binding was created through. The service must include the binding's method |
externalUserId | string / null | ❌ No | If passed, must match the binding payer. Default — the binding payer |
externalId | string / null | ❌ No | Charge ID in your system. Recommended: protects from double charges |
purpose, currency, callbackUrl and the other order parameters work as usual.
curl -X POST "https://api.1capital.capital/v1/orders" \
-H "Content-Type: application/json" \
-H "X-Api-Token: YOUR_API_TOKEN" \
-d '{
"amount": "990",
"currency": "RUB",
"purpose": "Subscription, November",
"externalId": "order_1235",
"recurring": { "bindingId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b" }
}'The charge goes to the provider while the request is processed. The response is the order object:
PENDING— the charge is accepted, the result comes in anORDER_UPDATEwebhook;COMPLETED— the provider confirmed the charge at once;FAILED— the provider refused, for example for insufficient funds. Create a new charge later.
A charge has no payment page: the payment link and the H2H endpoints answer 400 for it. Fees, limits, the balance credit and the webhooks are the same as for any order.
Errors
| HTTP status | When |
|---|---|
400 | Invalid parameters: saveBinding together with bindingId, no externalUserId for saveBinding, externalUserId of another payer |
404 | The binding is not found or belongs to another merchant; no payment method of the service supports recurring payments; the amount is outside the payment method limits |
409 | The binding is not ACTIVE; the binding's payment method is disabled or not in the service; the externalId is taken |
Binding object
{
"id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"status": "ACTIVE",
"isTest": false,
"merchantId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5e",
"externalId": "sub-42",
"externalUserId": "user_987",
"purpose": "Monthly subscription",
"initialOrderId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5c",
"confirmationUrl": null,
"bankName": "Sberbank",
"bankLogoUrl": null,
"callbackUrl": null,
"createdAt": "2026-10-21T12:34:56.000Z",
"updatedAt": "2026-10-21T12:36:10.512Z",
"activatedAt": "2026-10-21T12:36:10.512Z",
"closedAt": null
}| Field | Type | Description |
|---|---|---|
id | string (UUID) | Binding ID. Pass it as recurring.bindingId to charge the binding |
status | string | Current status, see Binding statuses |
isTest | boolean | Test binding: created on a test terminal, no real payment source is saved |
merchantId | string (UUID) | Your merchant ID |
externalId | string / null | Binding ID in your system (the value you passed) |
externalUserId | string | Payer ID in your system: the binding belongs to this payer only |
purpose | string / null | Subscription description shown to the payer in the banking app |
initialOrderId | string / null | Order whose payment created the binding; null for a binding created without a payment |
confirmationUrl | string / null | Page where the payer confirms a binding created without a payment. Redirect the payer to it |
bankName | string / null | Payer's bank, if the provider reported it |
bankLogoUrl | string / null | Payer's bank logo, if the provider reported it |
callbackUrl | string / null | Webhook URL for this binding |
createdAt | string | Creation time (ISO 8601) |
updatedAt | string | Last update time (ISO 8601) |
activatedAt | string / null | When the binding first became ACTIVE |
closedAt | string / null | When the binding became DELETED or FAILED |
Binding statuses
| Status | Final | Description |
|---|---|---|
PENDING | No | Waiting for the payer: the first payment is not completed yet, or the payer has not confirmed the binding |
ACTIVE | No | The binding is confirmed: you can charge it |
SUSPENDED | No | Suspended by the provider or the payer's bank. Charges are rejected; the binding may become ACTIVE again |
DELETED | Yes | Deleted by you, by the payer in the banking app or by the provider. Charges are rejected |
FAILED | Yes | The binding was not created: the first payment failed or expired, or the payer did not confirm it in 24 hours |
A FAILED binding still becomes ACTIVE if the provider reports a late successful first payment. Every status change is reported with a RECURRING_BINDING_UPDATE webhook.
Manage bindings
| Request | Description |
|---|---|
GET /v1/recurring/bindings/{bindingId} | Binding by ID |
GET /v1/recurring/bindings/external/{externalId} | Binding by your ID |
POST /v1/recurring/bindings/find | Search with filters and pagination, e.g. all bindings of a payer |
POST /v1/recurring/bindings/{bindingId}/sync-status | Request the current state from the provider and apply it |
DELETE /v1/recurring/bindings/{bindingId} | Delete the binding at the provider; it becomes DELETED |
Find a payer's bindings
curl -X POST "https://api.1capital.capital/v1/recurring/bindings/find" \
-H "Content-Type: application/json" \
-H "X-Api-Token: YOUR_API_TOKEN" \
-d '{
"pagination": { "page": 1, "perPage": 20 },
"filter": { "externalUserId": "user_987", "status": ["ACTIVE"] }
}'Filters: externalUserId, externalId, status (any of), fromDate, toDate (creation time), q (binding ID, your ID, payer ID or first order ID). Sorting: sort: [{ "field": "createdAt", "order": "desc" }] by status, createdAt, updatedAt, activatedAt. The response is { "data": [binding objects], "total": number }.
Delete a binding
curl -X DELETE "https://api.1capital.capital/v1/recurring/bindings/0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b" \
-H "X-Api-Token: YOUR_API_TOKEN"Returns the binding with the DELETED status. A binding that is already DELETED or FAILED answers 409; if the provider does not answer — 503, the binding is unchanged.
Sync with the provider
The payer can suspend or delete the subscription in their banking app. PENDING bindings are synced in the background; for an ACTIVE one, call sync-status when a charge is refused to find out whether the binding is still valid. The response is { "providerState", "previousStatus", "binding" }.
Test mode
On a test payment method no real payment source is saved:
- a binding from the first payment becomes
ACTIVEwhen its test order isCOMPLETED(amount1000) andFAILEDwhen the order is not paid; - a binding without a payment becomes
ACTIVEwithin a minute,confirmationUrlisnull; - charges are test orders, their status follows the amount:
1000—COMPLETED,1001—CANCELED,1002—FAILED,1003—COMPLETED, thenREFUND, any other amount —EXPIREDin 5 minutes; sync-statusanswers400, deletion works without the provider.
Recommendations
- Pass
recurring.externalIdfor bindings andexternalIdfor charges: a repeated request with the same value is rejected with409, which protects you from duplicate bindings and double charges. - Charge only
ACTIVEbindings; wait for theRECURRING_BINDING_UPDATEwebhook after the first payment. - Treat a
PENDINGcharge as in progress: do not repeat it until it gets a final status. - Delete bindings of canceled subscriptions — the payer sees the subscription in the banking app until then.