Bulk payout to cards
This page describes two methods: creating a registry of card payouts and getting its result. A registry is processed asynchronously: creating it returns only a task identifier, and the result for each payout is requested separately.
Integration flow
- Prepare the list of payouts. Generate a separate unique
transactionfor every payout. - Sign the exact request body and send the registry.
- Save
idfrom the response — this is the task identifier (task_id) used to request the result. - Poll the bulk payout result until the task reaches the
SUCCESSorFAILUREstate. - Get the final status of each payout by its
transactionvia the transaction status method or a callback.
A 200 response doesn't confirm that payouts were created
A 200 response only means that the registry was saved and the task was queued. No payout and no transaction
is created at this step. Payouts are created in the background — one by one, sequentially, in the order
of the items elements.
Limits and errors of individual payouts
Shop limits are checked for every payout separately at the moment it is created. An error in one payout doesn't stop the registry: the remaining payouts are processed, and the failed element is returned in the task result with an error code and description.
OTP confirmation isn't applied
Payouts from a registry are created without one-time code confirmation, even if OTP is enabled for the shop. If payouts must be confirmed with a code, use a single card payout.
Creating a registry
URL
Headers
| Name | Type | Required | Description |
|---|---|---|---|
X-Api-Key |
string | Yes | Shop identifier. |
X-Signature |
string | Yes | Signature of the request body. |
Building the signature
The request must be signed with an RSA/SHA-256 electronic signature. Pass the result in the X-Signature header.
The step-by-step algorithm, key requirements, examples for Python, PHP and OpenSSL, and troubleshooting for error
20002 are given in the general section «Building a signature for payouts».
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Registry name in the merchant's system. Used to look the registry up. |
items |
array | Yes | List of payouts, at least one element. The element structure is below. |
items array element
| Parameter | Type | Required | Description |
|---|---|---|---|
recipient_pan |
string | Yes | Recipient's card number, without spaces or separators. |
customer_card |
object | No | Linked customer card: customer_key and customer_card_token. Passed instead of recipient_pan. |
amount |
integer | Yes | Amount in kopecks. For example, 15000 is 150 RUB. |
transaction |
string | Yes | Unique operation identifier in the merchant's system. |
description |
string | Yes | Purpose of the payout, no more than 210 characters. |
customer |
string | No | Customer identifier — email or phone number. |
extra_data |
object | No | Additional merchant data. |
fiscal_data |
object | No | Data for receipt fiscalization under 54-FZ. |
Handling the card number
recipient_pan contains payment data. Don't log the full card number or store it unless necessary.
Comply with applicable PCI DSS requirements.
Example
{
"name": "Card payouts 10.08.2026",
"items": [
{
"recipient_pan": "4111111111111111",
"amount": 15000,
"transaction": "payout-card-20260810-0001",
"description": "Payout under contract 42"
},
{
"recipient_pan": "5555555555554444",
"amount": 27000,
"transaction": "payout-card-20260810-0002",
"description": "Payout under contract 43"
}
]
}
curl --request POST \
--url 'https://api.pay.stage.kvell.group/v1/orders/account2card/bulk' \
--header 'X-Api-Key: <api-key>' \
--header 'X-Signature: <signature>' \
--header 'Content-Type: application/json' \
--data '{"name":"Card payouts 10.08.2026","items":[{"recipient_pan":"4111111111111111","amount":15000,"transaction":"payout-card-20260810-0001","description":"Payout under contract 42"}]}'
Response
Select an HTTP code to see the example, response parameters and recommended actions.
If no HTTP response is received
Handle a timeout or a dropped connection the same way as a 5XX response: it is unknown whether the task was
queued. Don't resend the registry until you make sure that no payouts were created for its transaction values.
Response parameters
| Parameter | Type | Description |
|---|---|---|
id |
string | Task identifier (task_id). Pass it to the result method. |
status |
string | Task state at the moment it was queued. Values are listed in «Task states». |
What the response means
The registry is saved and the task is queued. No payouts have been created yet and there are no transactions in KVELL.
What to do next
- Store
idon your side: without it the registry result can't be retrieved. - Poll the bulk payout result until the
SUCCESSorFAILUREstate.
{
"errors": [
{
"code": 20003,
"message": "Payout профиль не привязан к магазину"
}
]
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of errors. Codes and handling recommendations are given in «HTTP response errors». |
∟ code |
integer | Error code. |
∟ message |
string | Reason why the request was rejected. |
{
"errors": [
{
"code": 20006,
"message": "Магазин не найден"
}
]
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of errors. Codes and handling recommendations are given in «HTTP response errors». |
∟ code |
integer | Error code. |
∟ message |
string | Reason why the resource wasn't found. |
{
"errors": [
{
"code": 20020,
"message": "Получатель не задан"
}
]
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of validation errors. Codes and handling recommendations are given in «HTTP response errors». |
∟ code |
integer | Error code. |
∟ message |
string | Reason for the validation error. |
What the response means
The registry failed format validation: a single invalid items element is enough for the whole request
to be rejected. The registry wasn't saved and no payouts were created.
What to do next
- Fix the fields listed in
errors[].messageand make sureitemscontains at least one element. - Build a new signature for the corrected body and resend the registry with the same
transactionvalues.
{
"errors": [
{
"code": 20000,
"message": "Неизвестная ошибка"
}
]
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of errors. Codes and handling recommendations are given in «HTTP response errors». |
∟ code |
integer | Error code. |
∟ message |
string | Description of the technical error. |
5XX, timeout and dropped connection
The result of the request is considered undefined: the task could have been queued even if the client received no response. Resending the same registry without a check leads to duplicate payout attempts.
Unified handling algorithm
- Don't treat the registry as accepted or rejected based on a technical error alone.
- If you didn't receive the task
id, check transaction status for thetransactionvalues of the registry. - If no transaction is found, resend the registry with the same
transactionvalues. - If transactions are found, don't resend the registry: wait for their final statuses.
- If requests keep failing, contact support and provide the registry name and the request time.
Don't create duplicate payouts
Don't change transaction when resending a registry. A payout with an already used transaction is
rejected with code 20007, and this protects you from paying twice.
Getting the result
The method returns the state of the registry processing task and, once it is finished, the result for each payout. It doesn't change the state of the operations.
Polling interval
The recommended interval between requests is at least 1 minute. A registry is processed sequentially, so the processing time grows with the number of payouts.
The SUCCESS state refers to the task, not to the payouts
SUCCESS means that the registry was processed to the end. Individual payouts inside it may have failed or may
still be processed by the bank.
URL
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id |
string | Yes | Task identifier — the id field from the bulk payout response. |
Headers
| Name | Type | Required | Description |
|---|---|---|---|
X-Api-Key |
string | Yes | Shop identifier. |
X-Signature |
string | Yes | Signature of the request. |
Building the signature
Concatenate X-Api-Key, task_id and secret_key without separators, compute SHA-256 of the resulting UTF-8
string and pass the lowercase hex result in the X-Signature header.
secret_key is available in the shop settings. The order of the values can't be changed. Request the result with
the same X-Api-Key that was used to create the registry.
Example
curl --request GET \
--url 'https://api.pay.stage.kvell.group/v1/orders/account2card/bulk/8f0f5f4b-6f6d-4f4f-9a2a-0d1a1f6b0b21' \
--header 'X-Api-Key: <api-key>' \
--header 'X-Signature: <signature>'
Response
Select an HTTP code to see the example, response parameters and recommended actions.
If no HTTP response is received
The method only reads the task state and doesn't change the state of the payouts. On 5XX, a timeout or
a dropped connection it is safe to repeat the same request with the same task_id.
{
"state": "SUCCESS",
"response": [
{
"order": {
"id": "3b3c19f4-c680-4f5d-bf2e-c0835439cf12",
"status": "processing",
"transaction": "payout-card-20260810-0001",
"amount": 15000,
"commission": 150,
"description": "Payout under contract 42",
"additional_data": {
"auth_code": null,
"rrn": null
},
"error_code": null,
"error_message": null,
"created_at": "2026-08-10T09:15:27.231000+00:00"
},
"transaction": "payout-card-20260810-0001",
"status": "processing"
},
{
"transaction": "payout-card-20260810-0002",
"code": "20019",
"message": "Превышен лимит выплат по магазину"
}
]
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
state |
string | Task state. Possible values are listed in «Task states». |
response |
array | null | Payout results. Filled in only when state is SUCCESS, otherwise — null. |
∟ order |
object | null | The created payout. Absent in an element with an error. |
∟ id |
string | Payout identifier in KVELL. |
∟ status |
string | Current payout status. Possible values are listed in «Payout statuses». |
∟ transaction |
string | Operation identifier passed by the merchant. |
∟ amount |
integer | Payout amount in kopecks. |
∟ commission |
integer | Commission in kopecks. |
∟ description |
string | null | Purpose of the payout. May include the prefix configured for the shop. |
∟ additional_data |
object | null | Additional data received while processing the payout. |
∟ auth_code |
string | null | Authorization code, if the bank returned one. |
∟ rrn |
string | null | Bank transaction identifier generated by the acquiring bank. |
∟ error_code |
string | null | Rejection reason code. Possible values are listed in «Transaction error codes». |
∟ error_message |
string | null | Rejection reason description. For a payout that wasn't rejected — null. |
∟ created_at |
string | Payout creation date and time in ISO 8601 format. |
∟ transaction |
string | Operation identifier in the merchant's system. Returned both in a successful element and in an error element. |
∟ status |
string | Current payout status; matches order.status. |
∟ code |
string | Payout error code. Present only in an error element. The codes are listed in the «HTTP response errors» reference. |
∟ message |
string | Payout error description. Present only in an error element. |
What the response means
A 200 response is returned in any task state, including while the registry is still being processed.
The order of response elements matches the order of items in the registry request. Match elements by
transaction, not by index.
What to do next
- If
stateis neitherSUCCESSnorFAILURE, repeat the request later. - On
SUCCESSparseresponse: elements withorderare created payouts, elements withcodeandmessageare payouts that couldn't be created. - Payouts from error elements weren't created. Fix the cause and send them in a new registry with the same
transactionvalues. - For created payouts wait for the final status
completedorcanceled.
{
"errors": [
{
"code": 20002,
"message": "Неверная подпись"
}
]
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of errors. Codes and handling recommendations are given in «HTTP response errors». |
∟ code |
integer | Error code. |
∟ message |
string | Reason why the request was rejected. |
{
"errors": [
{
"code": 20006,
"message": "Магазин не найден"
}
]
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of errors. Codes and handling recommendations are given in «HTTP response errors». |
∟ code |
integer | Error code. |
∟ message |
string | Reason why the resource wasn't found. |
What the response means
No shop with the given X-Api-Key was found. An unknown or expired task_id is not returned as 404 —
see «Task states».
{
"errors": [
{
"code": 20000,
"message": "Неизвестная ошибка"
}
]
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of errors. Codes and handling recommendations are given in «HTTP response errors». |
∟ code |
integer | Error code. |
∟ message |
string | Description of the technical error. |
What the response means
A technical error of the result request itself. It doesn't change the task state and doesn't determine the result of the registry payouts.
What to do next
- Safely repeat the same request with the same
task_idafter the recommended interval. - If the error repeats, get the payout statuses by their
transactionvia transaction status. - If those requests fail too, contact support and provide the
task_id.
Task states
| State | Final | What to do |
|---|---|---|
PENDING |
No | The registry hasn't been processed yet. Keep polling. The same state is returned for an unknown task_id. |
SUCCESS |
Yes | The registry was processed. Parse response. |
FAILURE |
Yes | Registry processing was interrupted by an error. response is returned as null. |
PENDING for an unknown task_id
A task is stored for a limited time, and an unknown identifier is indistinguishable from a task that hasn't
started: both return PENDING. If the state doesn't change longer than the expected processing time of the
registry, check the task_id and get the payout statuses by their transaction.
Other states
Besides the listed ones, internal task queue states may be returned. Treat any state other than SUCCESS and
FAILURE as intermediate and keep polling.
What to do on FAILURE
Some payouts of the registry may have been created before processing was interrupted. Don't resend the registry
until you check every transaction of it via transaction status. Resend only the
payouts whose transactions weren't found, keeping their original transaction values.