Приём платежей через Slipstream
Accept payments with Slipstream
REST API для мерчантов: вы создаёте заявку, получаете реквизиты или ссылку на оплату и показываете их покупателю. Когда платёж подтверждён — Slipstream присылает подписанный вебхук. Поддерживаются приём (payin) и выплаты (payout) в рублях.
A REST API for merchants: you create a request, receive payment requisites or a payment link and show them to your customer. Once the payment is confirmed, Slipstream sends you a signed webhook. Both pay-ins and payouts in RUB are supported.
https://api.slipstream.cash/api/v1/merchantУказанный адрес — тестовый контур. Адрес боевого API, API-ключ и HMAC-секрет выдаются менеджером при подключении. Во всех примерах ключи — заглушки (YOUR_MERCHANT_KEY, YOUR_HMAC_SECRET).
The address above is the test environment. The production URL, API key and HMAC secret are issued by your manager during onboarding. All keys in the examples are placeholders (YOUR_MERCHANT_KEY, YOUR_HMAC_SECRET).
Как это работает
How it works
POST /requestsPOST /requestsConfirmed или Canceled на ваш callback_urlConfirmed or Canceled to your callback_urlЭндпоинты
Endpoints
| Метод | Method | Путь | Path | Назначение | Purpose |
|---|---|---|---|---|---|
| POST | /requests | Создать заявку payin или payoutCreate a payin or payout request | |||
| POST | /requests/simple | Алиас /requests (тот же обработчик и тело)Alias of /requests (same handler and body) | |||
| POST | /payin/proof | Приложить чек оплатыAttach a payment receipt | |||
| POST | /payin/cancel | Отменить payinCancel a payin | |||
| GET | /payin/{public_id} | Статус payinPayin status | |||
| GET | /payout/{public_id} | Статус payoutPayout status | |||
| GET | /limits | Актуальные лимиты сумм по методамCurrent amount limits per method | |||
| POST | /disputes | Открыть диспут по заявкеOpen a dispute on a request | |||
| GET | /disputes/{dispute_id} | Статус диспутаDispute status |
Запросы и ответы — JSON в UTF-8 (кроме multipart-загрузки чека). Суммы передаются строкой в рублях с точкой: "5000.00". Успешный ответ — {"success": true, "data": {…}}, ошибка — {"success": false, "error": "…"}.
Requests and responses are UTF-8 JSON (except multipart receipt upload). Amounts are passed as a string in RUB with a dot: "5000.00". Success — {"success": true, "data": {…}}, error — {"success": false, "error": "…"}.
01Авторизация и подписьAuthentication & signing
Каждый запрос идентифицируется API-ключом и подписывается HMAC-SHA256 вашим секретом.
Every request is identified by your API key and signed with HMAC-SHA256 using your secret.
При подключении вы получаете две строки — они показываются один раз, храните их только на сервере:
During onboarding you receive two strings — they are shown once, keep them on your server only:
- API-ключ (
YOUR_MERCHANT_KEY) — передаётся в заголовкеAuthorization; - API key (
YOUR_MERCHANT_KEY) — sent in theAuthorizationheader; - HMAC-секрет (
YOUR_HMAC_SECRET) — ключ подписи ваших запросов и наших вебхуков. Сам секрет в запросах не передаётся. - HMAC secret (
YOUR_HMAC_SECRET) — the signing key for your requests and our webhooks. The secret itself is never sent.
Заголовки
Headers
| Заголовок | Header | Описание | Description | |
|---|---|---|---|---|
Authorization | обязательноrequired | Bearer YOUR_MERCHANT_KEY | ||
X-Timestamp | обязательноrequired | Unix-время в секундах. Допустимое расхождение с часами сервера — ±300 с (держите часы сервера синхронизированными по NTP).Unix time in seconds. Allowed clock skew — ±300 s (keep your server clock NTP-synced). | ||
X-Signature-Version | рекомендуетсяrecommended | 2 — текущая версия подписи (см. ниже). Без заголовка сервер проверяет устаревшую подпись v1.2 — the current signature version (see below). Without the header the server checks the deprecated v1 signature. | ||
X-Signature | обязательноrequired | hex HMAC-SHA256 от строки подписи (см. ниже)hex HMAC-SHA256 of the signing string (see below) | ||
X-Idempotency-Key | для POSTfor POST | Обязателен для всех POST. До 128 символов, уникален на одну бизнес-операцию. См. Идемпотентность.Required for every POST. Up to 128 chars, unique per business operation. See Idempotency. | ||
Content-Type | для POSTfor POST | application/json (или multipart/form-data для файлов чеков)(or multipart/form-data for receipt files) |
Строка подписи (v2)
Signing string (v2)
Поля склеиваются через перевод строки \n (без завершающего перевода строки). Отправляйте заголовок X-Signature-Version: 2.
Fields are joined with a newline \n (no trailing newline). Send the header X-Signature-Version: 2.
X-Signature = hex( HMAC_SHA256( YOUR_HMAC_SECRET,
"v2" + "\n" +
timestamp + "\n" +
METHOD + "\n" +
path_with_query + "\n" +
idempotency_key + "\n" +
canonical_json ) )v2— литерал, версия формата;v2— a literal, the format version;timestamp— то же значение, что вX-Timestamp;timestamp— the same value as inX-Timestamp;METHOD—GET/POSTв верхнем регистре;METHOD—GET/POSTin upper case;path_with_query— путь и query-строка ровно как в URL запроса, без домена:/api/v1/merchant/requests,/api/v1/merchant/payin/proof?public_id=PI-…. Отправляете с завершающим/— подписывайте тоже с ним;path_with_query— the path and query string exactly as in the request URL, without host:/api/v1/merchant/requests,/api/v1/merchant/payin/proof?public_id=PI-…. If you send a trailing/, sign it too;idempotency_key— значениеX-Idempotency-Key; если заголовка нет (GET) — пустая строка;idempotency_key— theX-Idempotency-Keyvalue; empty string if there is no such header (GET);canonical_json— тело JSON с рекурсивно отсортированными ключами, без пробелов. Для GET и для multipart-запросов — строка{}.canonical_json— the JSON body with recursively sorted keys, no whitespace. For GET and multipart requests — the literal{}.
Сервер проверяет подпись по разобранному телу, сериализованному канонически, поэтому порядок ключей в отправленном теле не важен — важно отсортировать их при подписи. Проще всего отправлять ту же каноническую строку, что подписали. В Python используйте ensure_ascii=False, иначе кириллица превратится в \uXXXX и подпись не совпадёт.
The server verifies the signature against the parsed body serialized canonically, so key order in the sent body does not matter — what matters is sorting keys when signing. Easiest: send the exact canonical string you signed. In Python use ensure_ascii=False, otherwise Cyrillic becomes \uXXXX and the signature won't match.
Защита от повтора. Каждая подпись мутирующего запроса (POST) принимается один раз: повтор той же подписи в пределах окна → 401 Signature already used (replay). Сетевой ретрай безопасен: повторите запрос с тем же X-Idempotency-Key (он входит в подпись v2) — сервер вернёт сохранённый ответ первой попытки, операция второй раз не выполнится. Для новой операции — новый ключ и свежий X-Timestamp.
Replay protection. Each signature of a mutating (POST) request is accepted once: reusing the same signature within the window → 401 Signature already used (replay). Network retries are safe: resend the request with the same X-Idempotency-Key (it is part of the v2 signature) — the server returns the stored response of the first attempt and does not execute the operation twice. For a new operation use a new key and a fresh X-Timestamp.
const crypto = require('crypto');
const API_KEY = 'YOUR_MERCHANT_KEY';
const HMAC_SECRET = 'YOUR_HMAC_SECRET';
// JSON with recursively sorted keys, no whitespace
function canonical(v) {
const sort = (x) => Array.isArray(x) ? x.map(sort)
: x && typeof x === 'object'
? Object.keys(x).sort().reduce((o, k) => (o[k] = sort(x[k]), o), {})
: x;
return JSON.stringify(sort(v ?? {}));
}
// path — path + query exactly as in the URL; idemKey — X-Idempotency-Key (POST) or ''
function signedHeaders(method, path, body, idemKey = '') {
const ts = String(Math.floor(Date.now() / 1000));
const payload = method === 'GET' ? '{}' : canonical(body);
const msg = ['v2', ts, method, path, idemKey, payload].join('\n');
const sig = crypto.createHmac('sha256', HMAC_SECRET).update(msg, 'utf8').digest('hex');
const headers = {
'Authorization': `Bearer ${API_KEY}`,
'X-Timestamp': ts,
'X-Signature-Version': '2',
'X-Signature': sig,
'Content-Type': 'application/json',
};
if (idemKey) headers['X-Idempotency-Key'] = idemKey;
return headers;
}import hashlib, hmac, json, time
API_KEY = "YOUR_MERCHANT_KEY"
HMAC_SECRET = "YOUR_HMAC_SECRET"
def canonical(body) -> str:
# sorted keys, no whitespace, keep UTF-8 as is
return json.dumps(body or {}, sort_keys=True,
separators=(",", ":"), ensure_ascii=False)
def signed_headers(method: str, path: str, body=None, idem_key: str = "") -> dict:
ts = str(int(time.time()))
payload = "{}" if method == "GET" else canonical(body)
msg = "\n".join(["v2", ts, method, path, idem_key, payload]).encode("utf-8")
sig = hmac.new(HMAC_SECRET.encode(), msg, hashlib.sha256).hexdigest()
headers = {
"Authorization": f"Bearer {API_KEY}",
"X-Timestamp": ts,
"X-Signature-Version": "2",
"X-Signature": sig,
"Content-Type": "application/json",
}
if idem_key:
headers["X-Idempotency-Key"] = idem_key
return headersTS=$(date +%s)
P="/api/v1/merchant/limits"
# v2\n{ts}\nGET\n{path}\n{idempotency_key — empty for GET}\n{}
SIG=$(printf 'v2\n%s\nGET\n%s\n\n{}' "$TS" "$P" \
| openssl dgst -sha256 -hmac "YOUR_HMAC_SECRET" | awk '{print $NF}')
curl -s "https://api.slipstream.cash$P" \
-H "Authorization: Bearer YOUR_MERCHANT_KEY" \
-H "X-Timestamp: $TS" \
-H "X-Signature-Version: 2" \
-H "X-Signature: $SIG"Подпись v1 (устаревшая)
Signature v1 (deprecated)
Устарела. Запросы без X-Signature-Version (или с X-Signature-Version: 1) проверяются по старой формуле. Она пока принимается, но не защищает query-строку и X-Idempotency-Key и будет отключена — переходите на v2. Повтор той же v1-подписи тоже отклоняется (401), кроме ретрая с уже использованным X-Idempotency-Key.
Deprecated. Requests without X-Signature-Version (or with X-Signature-Version: 1) are verified with the old formula. It is still accepted but does not protect the query string and X-Idempotency-Key, and it will be switched off — move to v2. Reusing the same v1 signature is rejected too (401), except for a retry with an already used X-Idempotency-Key.
X-Signature = hex( HMAC_SHA256( YOUR_HMAC_SECRET,
"{timestamp}.{METHOD}.{path}{canonical_json}" ) ) // path — without query stringДополнительная защита
Additional protection
- IP-allowlist. Если для вашего аккаунта заданы разрешённые IP, корректно подписанные запросы с других адресов получают
403(адрес в ответе не раскрывается). Пустой список — доступ с любого IP. - IP allowlist. If allowed IPs are configured for your account, correctly signed requests from other addresses get
403(the address is not echoed back). Empty list — any IP is allowed. - Перевыпуск. Ключ и секрет можно перевыпустить в кабинете мерчанта (раздел «Интеграция») или через менеджера. Старый ключ перестаёт работать сразу; новый секрет сразу используется и для подписи новых вебхуков.
- Rotation. Key and secret can be rotated in the merchant cabinet (“Integration”) or via your manager. The old key stops working immediately; the new secret is used for new webhook signatures right away.
02Быстрый стартQuick start
Три шага от ключей до первого подтверждённого платежа.
Three steps from keys to the first confirmed payment.
- Проверьте доступ и методыCheck access and methods
Вызовите
GET /limits— это проверит подпись и покажет, какие методы вам подключены и доступны сейчас (available: true), и в каких пределах сумм. CallGET /limits— it verifies your signature and shows which methods are enabled and currently available for you (available: true) and their amount range. - Создайте заявкуCreate a request
POST /requestsс методом, суммой иcallback_url. В ответе —public_idиrequisites(карта / телефон / ссылка). Покажите их покупателю.POST /requestswith a method, amount andcallback_url. The response containspublic_idandrequisites(card / phone / link). Show them to the customer. - Примите вебхукReceive the webhook
Проверьте
X-Signature-V2(см. Вебхуки), найдите заказ поmerchant_request_id/public_idи проведите его приConfirmed. Для методов*_checkпосле оплаты отправьте чек черезPOST /payin/proof. VerifyX-Signature-V2(see Webhooks), find the order bymerchant_request_id/public_idand fulfil it onConfirmed. For*_checkmethods, upload the receipt viaPOST /payin/proofafter payment.
// signedHeaders(), canonical() — from the “Authentication” section
const HOST = 'https://api.slipstream.cash';
async function call(method, path, body, idemKey = '') {
// v2: X-Idempotency-Key входит в подпись / is part of the signature
const headers = signedHeaders(method, path, body, idemKey);
const res = await fetch(HOST + path, {
method, headers,
body: method === 'POST' ? canonical(body) : undefined,
});
return res.json();
}
async function main() {
const limits = await call('GET', '/api/v1/merchant/limits');
console.log(limits.data.methods.filter((m) => m.available));
const order = {
merchant_request_id: 'order-10001',
sub_direction: 'payin_simple',
preferred_method: 'c2c',
client_amount: '5000.00',
client_bank: 'any',
client_requisites: 'customer-42',
callback_url: 'https://merchant.example.com/webhook',
};
const r = await call('POST', '/api/v1/merchant/requests', order, order.merchant_request_id);
console.log(r.data.public_id, r.data.status, r.data.requisites);
}
main();03Создание заявкиCreate a request
Один эндпоинт для приёма (payin) и выплат (payout) — направление задаётся sub_direction. POST /requests/simple принимает то же тело и работает идентично.
One endpoint for pay-ins and payouts — direction is set by sub_direction. POST /requests/simple accepts the same body and behaves identically.
Параметры тела
Body parameters
| Поле | Field | Тип | Type | Описание | Description | |
|---|---|---|---|---|---|---|
sub_direction | enum | обязательноrequired | Приём: payin_simple, payin_check, payin_split, payin_split_check. Выплата: payout_check, payout_split, payout_split_check. На обработку влияет только префикс payin/payout; суффиксы принимаются для совместимости. Нужен ли чек — определяет метод, а не суффикс.Pay-in: payin_simple, payin_check, payin_split, payin_split_check. Payout: payout_check, payout_split, payout_split_check. Only the payin/payout prefix affects processing; suffixes are accepted for compatibility. Whether a receipt is required is defined by the method, not the suffix. | |||
preferred_method | string | обязательноrequired | Код метода: c2c, sbp, sbp_check, card_check, deeplink_sbp, deeplink_card, sim, nspk, account, viet_qr, cross_border. Регистр не важен, есть алиасы — см. Методы оплаты. Метод должен быть подключён вашему аккаунту.Method code: c2c, sbp, sbp_check, card_check, deeplink_sbp, deeplink_card, sim, nspk, account, viet_qr, cross_border. Case-insensitive, aliases available — see Payment methods. The method must be enabled for your account. | |||
client_amount | string | обязательноrequired | Сумма в рублях: десятичная строка > 0, не более 2 знаков после точки — "5000", "5000.00". Запятая, экспонента и число вместо строки не принимаются. Максимум по умолчанию — 500 000.00.Amount in RUB: decimal string > 0, up to 2 fractional digits — "5000", "5000.00". Commas, exponent notation and numbers instead of strings are rejected. Default maximum — 500,000.00. | |||
callback_url | url | обязательноrequired | Куда слать вебхуки по этой заявке. Только https:// на публичное доменное имя: IP-адреса, внутренние/локальные адреса и имена без точки отклоняются с 400. Редиректы (3xx) при доставке не выполняются — это ошибка доставки.Where to send webhooks for this request. https:// to a public domain name only: IP addresses, internal/local addresses and dotless names are rejected with 400. Redirects (3xx) are not followed on delivery — they count as a delivery failure. | |||
client_requisites | string | обязательноrequired | Payout: реквизит получателя (номер карты, телефон, счёт) — на него будет сделан перевод. Payin: обязателен по схеме, но в обработке не используется — передайте реквизит или идентификатор плательщика.Payout: recipient requisite (card number, phone, account) — the transfer goes there. Payin: required by schema but not used in processing — pass the payer's requisite or identifier. | |||
client_bank | string | обязательно*required* | Банк покупателя — код из справочника (sber, tinkoff…). Используется при подборе реквизита. any, пустая строка, none, null, -, — = любой банк.Customer's bank — a code from the reference (sber, tinkoff…). Used for requisite selection. any, empty string, none, null, -, — = any bank. | |||
client_bank_code | string | обязательно*required* | Альтернатива client_bank для совместимости. * — нужно хотя бы одно из двух полей. На подбор влияет только client_bank.Alternative to client_bank for compatibility. * — at least one of the two is required. Only client_bank affects selection. | |||
merchant_request_id | string ≤128 | необязательноoptional | Ваш ID заказа. Возвращается в ответе и во всех вебхуках. Должен быть уникален среди ваших открытых и оплаченных заявок того же направления: повтор → 409 merchant_request_id already used by request PI-… (статус). ID отменённой заявки можно использовать снова. Сетевой ретрай делайте с тем же X-Idempotency-Key.Your order ID. Returned in the response and every webhook. Must be unique among your open and paid requests of the same direction: a repeat → 409 merchant_request_id already used by request PI-… (status). The ID of a canceled request can be reused. Retry network failures with the same X-Idempotency-Key. | |||
client_full_name | string | необязательноoptional | ФИО плательщика. С ним сверяют поступление; для некоторых методов он нужен для выдачи реквизита — рекомендуем передавать.Payer's full name. The incoming transfer is matched against it; for some methods it is needed to issue a requisite — recommended. | |||
client_additional_requisites | string | необязательноoptional | Payout: дополнительные данные получателя (например, банк / ФИО) — используются вместе с client_requisites.Payout: extra recipient details (e.g. bank / name) — used together with client_requisites. | |||
info | string | необязательноoptional | Произвольный комментарий; возвращается в ответе на создание.Free-text note; echoed in the create response. | |||
merchant_client_id | string ≤128 | необязательноoptional | ID вашего клиента. Обязателен только при customer_mode: "assigned".Your customer's ID. Required only with customer_mode: "assigned". | |||
customer_mode | "assigned" | необязательноoptional | Режим «закреплённый клиент» (только payin и конкретный метод). Включается по согласованию с менеджером; без поля — обычный поток.“Assigned customer” mode (payin and an explicit method only). Enabled by agreement with your manager; omit for the regular flow. | |||
customer_access | boolean | необязательноoptional | Для assigned: явное подтверждение, что клиент допущен к оплате. В этом режиме обязателен.For assigned: explicit assertion that the customer is allowed to pay. Required in this mode. | |||
payments_count_total | int ≥1 | необязательноoptional | Принимается для совместимости, на обработку не влияет.Accepted for compatibility, does not affect processing. |
Запрос
Request
# body is already canonical: keys sorted, no whitespace
BODY='{"callback_url":"https://merchant.example.com/webhook","client_amount":"5000.00","client_bank":"tinkoff","client_full_name":"Иван Иванов","client_requisites":"customer-42","merchant_request_id":"order-10001","preferred_method":"c2c","sub_direction":"payin_simple"}'
TS=$(date +%s)
P="/api/v1/merchant/requests"
IDEM="order-10001"
SIG=$(printf 'v2\n%s\nPOST\n%s\n%s\n%s' "$TS" "$P" "$IDEM" "$BODY" | openssl dgst -sha256 -hmac "YOUR_HMAC_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api.slipstream.cash$P" \
-H "Authorization: Bearer YOUR_MERCHANT_KEY" \
-H "X-Timestamp: $TS" \
-H "X-Signature-Version: 2" \
-H "X-Signature: $SIG" \
-H "X-Idempotency-Key: $IDEM" \
-H "Content-Type: application/json" \
--data "$BODY"// signedHeaders(), canonical() — see “Authentication”
const path = '/api/v1/merchant/requests';
const body = {
merchant_request_id: 'order-10001',
sub_direction: 'payin_simple',
preferred_method: 'c2c',
client_amount: '5000.00',
client_bank: 'tinkoff',
client_full_name: 'Иван Иванов',
client_requisites: 'customer-42',
callback_url: 'https://merchant.example.com/webhook',
};
const res = await fetch('https://api.slipstream.cash' + path, {
method: 'POST',
headers: signedHeaders('POST', path, body, body.merchant_request_id), // v2: key is signed
body: canonical(body),
});
const { success, data, error } = await res.json();
if (!success) throw new Error(error);
if (!data.requisites) { /* no requisite found — see “Requisites & links” */ }import requests # signed_headers(), canonical() — see “Authentication”
path = "/api/v1/merchant/requests"
body = {
"merchant_request_id": "order-10001",
"sub_direction": "payin_simple",
"preferred_method": "c2c",
"client_amount": "5000.00",
"client_bank": "tinkoff",
"client_full_name": "Иван Иванов",
"client_requisites": "customer-42",
"callback_url": "https://merchant.example.com/webhook",
}
headers = signed_headers("POST", path, body, body["merchant_request_id"]) # v2: key is signed
r = requests.post("https://api.slipstream.cash" + path,
data=canonical(body).encode("utf-8"), headers=headers, timeout=30)
resp = r.json()
if not resp["success"]:
raise RuntimeError(resp["error"])
print(resp["data"]["public_id"], resp["data"].get("requisites"))Ответ 200
Response 200
{
"success": true,
"data": {
"kind": "payin",
"public_id": "PI-3f9a1c7e5b2d4a60",
"merchant_request_id": "order-10001",
"status": "Working",
"created_at": "2026-09-23T08:15:02.114Z",
"rub_amount": "5000.00",
"usdt_amount": "51.35135135",
"rate": "92.5000",
"rate_with_commission": "87.8750",
"requisites": {
"method": "c2c",
"type": "card",
"card_number": "2200123412341234",
"phone": null,
"account": null,
"bik": null,
"bank": "tinkoff",
"bank_name": "Т-Банк",
"holder": "Иван П.",
"link": null,
"payment_url": null,
"qr_payload": null,
"expires_at": "2026-09-23T08:35:02.114Z"
},
"instant_match": { "attempted": true, "matched": false }
}
}| Поле | Field | Описание | Description |
|---|---|---|---|
kind | payin | payout | ||
public_id | ID заявки в Slipstream: PI-… для payin, PO-… для payout. Используйте во всех следующих вызовах.Slipstream request ID: PI-… for payin, PO-… for payout. Use it in all subsequent calls. | ||
merchant_request_id | Ваш ID (если передан).Your ID (if provided). | ||
status | Статус на момент ответа — см. Статусы.Status at response time — see Statuses. | ||
info | Эхо вашего info (нет, если не передан).Echo of your info (absent if not sent). | ||
created_at | Время создания, ISO 8601 UTC.Creation time, ISO 8601 UTC. | ||
rub_amount | Сумма заявки в рублях.Request amount in RUB. | ||
usdt_amount | Сумма к зачислению в USDT по курсу, зафиксированному при создании, за вычетом вашей комиссии. Пустая строка, если курс недоступен.Amount to be credited in USDT at the rate fixed at creation, net of your commission. Empty string if the rate is unavailable. | ||
rate | Курс RUB/USDT без комиссии (справочно).RUB/USDT rate without commission (for reference). | ||
rate_with_commission | Эффективный курс: rate × (1 − комиссия).Effective rate: rate × (1 − commission). | ||
requisites | Реквизиты для покупателя (только payin). Отсутствует, если реквизит не найден. Пока заявка открыта, те же реквизиты отдаёт GET /payin/{public_id}. См. Реквизиты и ссылки.Requisites for the customer (payin only). Absent if no requisite was found. While the request is open, the same requisites are returned by GET /payin/{public_id}. See Requisites & links. | ||
instant_match | Служебное поле (attempted, matched), на интеграцию не влияет — игнорируйте.Service field (attempted, matched), not needed for integration — ignore it. |
04Примеры по методамExamples by method
Тело запроса и типичный ответ для каждого метода. Заголовки и подпись — как в разделе Авторизация.
Request body and a typical response for each method. Headers and signing — as in Authentication.
{
"merchant_request_id": "order-c2c-1",
"sub_direction": "payin_simple",
"preferred_method": "c2c",
"client_amount": "5000.00",
"client_bank": "sber",
"client_full_name": "Иван Иванов",
"client_requisites": "customer-42",
"callback_url": "https://merchant.example.com/webhook"
}{
"success": true,
"data": {
"kind": "payin",
"public_id": "PI-3f9a1c7e5b2d4a60",
"merchant_request_id": "order-c2c-1",
"status": "Working",
"created_at": "2026-09-23T08:15:02.114Z",
"rub_amount": "5000.00",
"usdt_amount": "51.35135135",
"rate": "92.5000",
"rate_with_commission": "87.8750",
"requisites": {
"method": "c2c",
"type": "card",
"card_number": "2200123412341234",
"phone": null,
"account": null,
"bik": null,
"bank": "sber",
"bank_name": "Сбер",
"holder": "Иван П.",
"link": null,
"payment_url": null,
"qr_payload": null,
"expires_at": "2026-09-23T08:35:02.114Z"
},
"instant_match": { "attempted": true, "matched": false }
}
}phone (type: "sbp") в формате +7XXXXXXXXXX.phone (type: "sbp"), formatted as +7XXXXXXXXXX.{
"merchant_request_id": "order-sbp-1",
"sub_direction": "payin_simple",
"preferred_method": "sbp",
"client_amount": "3200.00",
"client_bank": "any",
"client_full_name": "Иван Иванов",
"client_requisites": "customer-42",
"callback_url": "https://merchant.example.com/webhook"
}{
"success": true,
"data": {
"kind": "payin",
"public_id": "PI-8c21d0e94ab37f15",
"merchant_request_id": "order-sbp-1",
"status": "Working",
"created_at": "2026-09-23T08:20:44.901Z",
"rub_amount": "3200.00",
"usdt_amount": "32.86486486",
"rate": "92.5000",
"rate_with_commission": "87.8750",
"requisites": {
"method": "sbp",
"type": "sbp",
"card_number": null,
"phone": "+79001234567",
"account": null,
"bik": null,
"bank": "tinkoff",
"bank_name": "Т-Банк",
"holder": "Мария С.",
"link": null,
"payment_url": null,
"qr_payload": null,
"expires_at": "2026-09-23T08:40:44.901Z"
},
"instant_match": { "attempted": true, "matched": false }
}
}phone. После оплаты обязательно пришлите PDF-чек через POST /payin/proof — без него платёж не будет засчитан. Так же работает card_check (карта + PDF).phone. After payment you must upload the PDF receipt via POST /payin/proof — without it the payment will not be credited. card_check (card + PDF) works the same way.{
"merchant_request_id": "order-sbpcheck-1",
"sub_direction": "payin_check",
"preferred_method": "sbp_check",
"client_amount": "7500.00",
"client_bank": "sber",
"client_full_name": "Иван Иванов",
"client_requisites": "customer-42",
"callback_url": "https://merchant.example.com/webhook"
}{
"success": true,
"data": {
"kind": "payin",
"public_id": "PI-5e0b7a3c91f24d88",
"merchant_request_id": "order-sbpcheck-1",
"status": "Working",
"created_at": "2026-09-23T08:31:10.377Z",
"rub_amount": "7500.00",
"usdt_amount": "77.02702702",
"rate": "92.5000",
"rate_with_commission": "87.8750",
"requisites": {
"method": "sbp_check",
"type": "sbp",
"card_number": null,
"phone": "+79007654321",
"account": null,
"bik": null,
"bank": "sber",
"bank_name": "Сбербанк",
"holder": "Алексей К.",
"link": null,
"payment_url": null,
"qr_payload": null,
"expires_at": "2026-09-23T08:51:10.377Z"
},
"instant_match": { "attempted": true, "matched": false }
}
}link (дублируется в payment_url), type: "link": отправьте покупателя по ссылке (редирект или QR). Алиасы метода: Deeplink, Link.link (duplicated in payment_url), type: "link": send the customer to it (redirect or QR). Method aliases: Deeplink, Link.{
"merchant_request_id": "order-dl-sbp-1",
"sub_direction": "payin_simple",
"preferred_method": "deeplink_sbp",
"client_amount": "2500.00",
"client_bank": "any",
"client_requisites": "customer-42",
"callback_url": "https://merchant.example.com/webhook"
}{
"success": true,
"data": {
"kind": "payin",
"public_id": "PI-a7d44e1f0c9b2356",
"merchant_request_id": "order-dl-sbp-1",
"status": "Working",
"created_at": "2026-09-23T08:40:05.012Z",
"rub_amount": "2500.00",
"usdt_amount": "25.67567567",
"rate": "92.5000",
"rate_with_commission": "87.8750",
"requisites": {
"method": "deeplink_sbp",
"type": "link",
"card_number": null,
"phone": null,
"account": null,
"bik": null,
"bank": null,
"bank_name": null,
"holder": null,
"link": "https://pay.example.com/f/9Qx2LmA7",
"payment_url": "https://pay.example.com/f/9Qx2LmA7",
"qr_payload": null,
"expires_at": "2026-09-23T09:00:05.012Z"
},
"instant_match": { "attempted": true, "matched": false }
}
}link.link.{
"merchant_request_id": "order-dl-card-1",
"sub_direction": "payin_simple",
"preferred_method": "deeplink_card",
"client_amount": "4100.00",
"client_bank": "any",
"client_requisites": "customer-42",
"callback_url": "https://merchant.example.com/webhook"
}{
"success": true,
"data": {
"kind": "payin",
"public_id": "PI-2b9f60c3d815ea47",
"merchant_request_id": "order-dl-card-1",
"status": "Working",
"created_at": "2026-09-23T08:44:51.640Z",
"rub_amount": "4100.00",
"usdt_amount": "42.10810810",
"rate": "92.5000",
"rate_with_commission": "87.8750",
"requisites": {
"method": "deeplink_card",
"type": "link",
"card_number": null,
"phone": null,
"account": null,
"bik": null,
"bank": null,
"bank_name": null,
"holder": null,
"link": "https://pay.example.com/f/Hk3vT0pZ",
"payment_url": "https://pay.example.com/f/Hk3vT0pZ",
"qr_payload": null,
"expires_at": "2026-09-23T09:04:51.640Z"
},
"instant_match": { "attempted": true, "matched": false }
}
}Значения usdt_amount / rate в примерах посчитаны для курса 92.50 и комиссии 5% — у вас будут свои. Реквизиты и ссылки в примерах вымышлены.
usdt_amount / rate in the examples assume a 92.50 rate and 5% commission — yours will differ. Requisites and links in the examples are fictional.
05Методы оплатыPayment methods
Метод передаётся в preferred_method. Использовать можно только методы, подключённые вашему аккаунту — список и текущие суммы смотрите в GET /limits.
The method is passed in preferred_method. Only methods enabled for your account can be used — see the list and current amounts via GET /limits.
payment_url.payment_url.payment_url.payment_url.| Код | Code | Название | Name | Что получает покупатель | What the customer gets | Чек | Receipt |
|---|---|---|---|---|---|---|---|
c2c | К2КCard-to-card | card_number, bank, holder | не нуженno | ||||
sbp | СБПSBP | phone*, bank, holder | не нуженno | ||||
sbp_check | СБП (PDF)SBP (PDF) | phone*, bank, holder | PDF обязателенPDF required | ||||
card_check | Карта (PDF)Card (PDF) | card_number, bank, holder | PDF обязателенPDF required | ||||
deeplink_sbp | Deeplink СБПSBP | payment_url | не нуженno | ||||
deeplink_card | Deeplink КартаCard | payment_url | не нуженno | ||||
sim | SIM | телефон* + оператор (банки sim_*)phone* + carrier (sim_* banks) | не нуженno | ||||
account | СчётBank account | account, bik, bank | не нуженno | ||||
nspk | NSPK (QR) | ссылка / QR в payment_urllink / QR in payment_url | не нуженno | ||||
viet_qr | Вьетнамский QRVietnam QR | строка для QR в qr_payload (type: "qr", EMVCo 000201…)QR string in qr_payload (type: "qr", EMVCo 000201…) | не нуженno | ||||
cross_border | Трансграничный переводCross-border transfer | card_number или phone (международный формат, напр. +992…)card_number or phone (international format, e.g. +992…) | не нуженno |
* Телефон всегда приходит в phone в формате +7XXXXXXXXXX. sim, account, nspk, viet_qr, cross_border подключаются по запросу.
* The phone always arrives in phone, formatted +7XXXXXXXXXX. sim, account, nspk, viet_qr, cross_border are enabled on request.
Алиасы
Aliases
Для совместимости с интеграциями предыдущей версии Slipstream принимаются имена (регистр не важен):
For compatibility with previous Slipstream integrations the following names are accepted (case-insensitive):
| Передано | Sent | Метод | Method |
|---|---|---|---|
Card | c2c | ||
SBP | sbp | ||
Deeplink, Link | deeplink_sbp | ||
SBP_QR | nspk | ||
All | all — служебное значение, не рекомендуется: передавайте конкретный метод.all — service value, not recommended: pass a concrete method. |
06Реквизиты и ссылкиRequisites & links
Реквизиты для оплаты приходят в объекте requisites — в ответе на создание заявки и в GET /payin/{public_id}, пока заявка открыта. Формат один и тот же для всех методов.
Payment requisites come in the requisites object — in the create response and in GET /payin/{public_id} while the request is open. The format is the same for all methods.
| Поле | Field | Тип | Type | Описание | Description |
|---|---|---|---|---|---|
method | string | Код метода заявки (c2c, sbp, sbp_check, deeplink_sbp, …).Request method code (c2c, sbp, sbp_check, deeplink_sbp, …). | |||
type | string | Как платить: card — по номеру карты, sbp — по телефону, account — по счёту, link — по ссылке, qr — по QR-строке из qr_payload.How to pay: card — by card number, sbp — by phone, account — by account, link — via link, qr — by the QR string in qr_payload. | |||
card_number | string | null | Номер карты. Гарантированно заполнен при type: "card"; телефон сюда не попадает.Card number. Guaranteed when type: "card"; a phone never goes here. | |||
phone | string | null | Телефон для СБП / SIM в формате +7XXXXXXXXXX. Гарантированно заполнен при type: "sbp".Phone for SBP / SIM, formatted +7XXXXXXXXXX. Guaranteed when type: "sbp". | |||
account | string | null | Номер счёта (type: "account").Account number (type: "account"). | |||
bik | string | null | БИК банка для перевода по счёту.Bank BIC for account transfers. | |||
bank | string | null | Банк получателя: код из справочника, если сопоставился; иначе — исходное название банка.Recipient bank: a reference code when matched; otherwise the original bank name. | |||
bank_name | string | null | Название банка для показа покупателю (как пришло).Bank name to show the customer (as received). | |||
holder | string | null | Имя получателя.Recipient name. | |||
link | string | null | Ссылка на оплату (deeplink / платёжная форма / QR). Гарантированно заполнена при type: "link".Payment link (deeplink / payment form / QR). Guaranteed when type: "link". | |||
payment_url | string | null | То же, что link (оставлено для совместимости).Same as link (kept for compatibility). | |||
qr_payload | string | null | Строка для QR-кода, когда это не ссылка (EMVCo 000201… у viet_qr; ASCII, до 512 символов). Гарантированно заполнена при type: "qr" — нарисуйте из неё QR для покупателя.QR-code string when it is not a link (EMVCo 000201… for viet_qr; ASCII, up to 512 chars). Guaranteed when type: "qr" — render a QR from it for the customer. | |||
expires_at | string | null | До какого момента реквизит действителен, ISO 8601 UTC.Requisite valid until, ISO 8601 UTC. |
Что показывать покупателю — по type: link → отправьте по ссылке link; sbp → phone; card → card_number; account → account/bik; qr → QR из qr_payload. Вместе с bank_name, holder и точной суммой rub_amount.
What to show the customer — by type: link → send them to link; sbp → phone; card → card_number; account → account/bik; qr → a QR from qr_payload. Together with bank_name, holder and the exact rub_amount.
Нет requisites в ответе = под этот метод/банк/сумму сейчас нет свободного реквизита. Заявка остаётся Waiting (если результат подбора ещё уточняется — Processing) и будет автоматически отменена по истечении срока жизни; для части аккаунтов по настройке она сразу возвращается в статусе Canceled. Рекомендуем сразу отменить её через POST /payin/cancel и предложить покупателю другой метод или сумму.
No requisites in the response = no free requisite for this method/bank/amount right now. The request stays Waiting (or Processing while the issuance result is being confirmed) and is auto-canceled when its lifetime ends; for some accounts, by configuration, it is returned as Canceled right away. We recommend canceling it right away via POST /payin/cancel and offering another method or amount.
Повторно получить реквизиты можно через GET /payin/{public_id} — пока заявка открыта (Waiting/Working/Processing) и реквизит выдан. После Confirmed/Canceled там requisites: null. Если потерялся ответ на создание, можно также повторить тот же POST /requests с тем же X-Idempotency-Key и телом (см. Идемпотентность).
Requisites can be re-fetched via GET /payin/{public_id} while the request is open (Waiting/Working/Processing) and a requisite has been issued. After Confirmed/Canceled it returns requisites: null. If the create response was lost you can also repeat the same POST /requests with the same X-Idempotency-Key and body (see Idempotency).
07Чеки (proof)Receipts (proof)
Приложить чек об оплате к заявке. Для sbp_check и card_check — обязательно (только PDF); для остальных методов — по желанию, помогает при спорных платежах.
Attach a payment receipt to a request. Mandatory for sbp_check and card_check (PDF only); optional for other methods — helps with disputed payments.
Вариант 1 — JSON (base64)
Option 1 — JSON (base64)
| Поле | Field | Описание | Description | |
|---|---|---|---|---|
public_id | обязательноrequired | ID заявкиRequest ID | ||
file_name | обязательноrequired | Имя файла, до 200 символовFile name, up to 200 chars | ||
mime_type | обязательноrequired | application/pdf, image/png, image/jpeg, image/webp, image/heic | ||
file_base64 | обязательноrequired | Содержимое файла в base64 (префикс data:…;base64, допустим)File content in base64 (a data:…;base64, prefix is allowed) | ||
note | необязательноoptional | Комментарий, до 1000 символовComment, up to 1000 chars |
Для этого эндпоинта лимит JSON-тела увеличен до 15 МБ: через base64 проходят файлы до 10 МБ (base64 раздувает размер примерно на треть). Несколько файлов одним запросом — через multipart.
For this endpoint the JSON body limit is raised to 15 MB: base64 fits files up to 10 MB (base64 adds about a third to the size). Use multipart to send several files in one request.
{
"public_id": "PI-5e0b7a3c91f24d88",
"file_name": "receipt.pdf",
"mime_type": "application/pdf",
"file_base64": "JVBERi0xLjQKJcfsj6IK...",
"note": "order-sbpcheck-1"
}{
"success": true,
"data": {
"proof_id": "1842",
"public_id": "PI-5e0b7a3c91f24d88",
"status": "pending",
"file_name": "receipt.pdf",
"file_size": 48213,
"created_at": "2026-09-23T08:36:12.508Z",
"forwarded": true
}
}Вариант 2 — multipart/form-data
Option 2 — multipart/form-data
Файлы — в поле receipts (до 5 файлов по 10 МБ), public_id — полем формы или в query ?public_id=, опционально note. Подпись для multipart считается от тела {}.
Files go into the receipts field (up to 5 files × 10 MB), public_id — as a form field or ?public_id= query, optional note. The signature for multipart is computed over the body {}.
TS=$(date +%s)
P="/api/v1/merchant/payin/proof"
KEY="proof-PI-5e0b7a3c91f24d88-1"
# v2: "v2\n{ts}\nPOST\n{path}\n{idempotency_key}\n{}" (multipart body is always {})
SIG=$(printf 'v2\n%s\nPOST\n%s\n%s\n{}' "$TS" "$P" "$KEY" | openssl dgst -sha256 -hmac "YOUR_HMAC_SECRET" | awk '{print $NF}')
curl -s -X POST "https://api.slipstream.cash$P" \
-H "Authorization: Bearer YOUR_MERCHANT_KEY" \
-H "X-Timestamp: $TS" -H "X-Signature-Version: 2" -H "X-Signature: $SIG" \
-H "X-Idempotency-Key: $KEY" \
-F "public_id=PI-5e0b7a3c91f24d88" \
-F "receipts=@receipt.pdf;type=application/pdf"{
"success": true,
"data": {
"attached": 1,
"receipts": [ { "id": "1843", "filename": "receipt.pdf" } ],
"public_id": "PI-5e0b7a3c91f24d88",
"forwarded": true
}
}Правила и ответы
Rules and responses
- Для
sbp_check/card_checkпринимается только PDF, файл проверяется по сигнатуре%PDF— переименованная картинка вернёт400. - For
sbp_check/card_checkPDF only is accepted; the file is checked for the%PDFsignature — a renamed image returns400. - Чек можно приложить в любом статусе, кроме
Canceled(→409, в т.ч. для истёкшей заявки). Можно отправить несколько чеков — до 10 на заявку (больше →409 Too many receipts for this request); повторная отправка того же файла лимит не расходует. - A receipt can be attached in any status except
Canceled(→409, including expired requests). Multiple receipts are allowed — up to 10 per request (more →409 Too many receipts for this request); re-sending the same file does not count against the limit. forwarded:true— чек передан на проверку платежа и принят;null— дополнительная передача не требуется, чек сохранён для проверки.forwarded:true— the receipt was passed on for payment verification and accepted;null— no further forwarding needed, the receipt is stored for review.424— чек сохранён, но не принят на проверку (причина — вerror). Исправьте файл или повторите отправку того же файла с новымX-Idempotency-Key— повторно он не сохраняется.424— the receipt is saved but was not accepted for verification (reason inerror). Fix the file, or resend the same file with a newX-Idempotency-Key— it is not stored twice.- Сам по себе чек не подтверждает платёж: решение принимается после проверки поступления, результат придёт вебхуком.
- A receipt alone does not confirm the payment: the decision is made after the incoming payment is verified, the result arrives by webhook.
{
"success": false,
"error": "Receipts were saved on our side, but were not accepted on verification. Check the file and retry with a new idempotency key.",
"attached": 1,
"receipts": [ { "id": "1844", "filename": "receipt.pdf" } ]
}08Статус заявкиRequest status
GET подписывается от тела {}; путь для подписи — с public_id, например /api/v1/merchant/payin/PI-3f9a1c7e5b2d4a60. Чужая заявка или не то направление → 404.
GET is signed over the body {}; the signed path includes public_id, e.g. /api/v1/merchant/payin/PI-3f9a1c7e5b2d4a60. Someone else's request or the wrong direction → 404.
{
"success": true,
"data": {
"public_id": "PI-3f9a1c7e5b2d4a60",
"status": "Working",
"rub_amount": "5000.00",
"actual_amount": null,
"amount_mismatch": false,
"usdt_amount": "51.35135135",
"rate": "92.5000",
"rate_with_commission": "87.8750",
"requisites": {
"method": "c2c",
"type": "card",
"card_number": "2200123412341234",
"phone": null,
"account": null,
"bik": null,
"bank": "tinkoff",
"bank_name": "Т-Банк",
"holder": "Иван П.",
"link": null,
"payment_url": null,
"expires_at": "2026-09-23T08:35:02.114Z"
},
"expires_at": "2026-09-23T08:35:02.114Z",
"linked_requests": []
}
}{
"success": true,
"data": {
"public_id": "PI-3f9a1c7e5b2d4a60",
"status": "Confirmed",
"rub_amount": "5000.00",
"actual_amount": "4700.00",
"amount_mismatch": true,
"usdt_amount": "51.35135135",
"rate": "92.5000",
"rate_with_commission": "87.8750",
"requisites": null,
"expires_at": null,
"linked_requests": []
}
}| Поле | Field | Описание | Description |
|---|---|---|---|
rub_amount | Сумма заявки (номинал) в рублях.Request (nominal) amount in RUB. | ||
actual_amount | Фактически оплаченная сумма, если при подтверждении она отличалась от rub_amount; иначе null.Actually paid amount if it differed from rub_amount at confirmation; otherwise null. | ||
amount_mismatch | true, если actual_amount отличается от rub_amount.true if actual_amount differs from rub_amount. | ||
requisites | Только payin. Реквизиты в едином формате, пока заявка открыта и реквизит выдан; null — реквизит ещё не выдан или заявка закрыта (Confirmed/Canceled).Payin only. Requisites in the unified format while the request is open and a requisite has been issued; null — not issued yet or the request is closed (Confirmed/Canceled). | ||
expires_at | Только payin. Срок жизни открытой заявки, ISO 8601 UTC; для закрытой — null.Payin only. Lifetime of an open request, ISO 8601 UTC; null once closed. |
Ответ для payout — public_id, status, rub_amount, actual_amount, amount_mismatch, usdt_amount, rate, rate_with_commission (без requisites, expires_at и linked_requests).
The payout response has public_id, status, rub_amount, actual_amount, amount_mismatch, usdt_amount, rate, rate_with_commission (no requisites, expires_at or linked_requests).
Статусы
Statuses
| Статус | Status | Значение | Meaning | Финальный | Final |
|---|---|---|---|---|---|
| Waiting | Заявка создана, реквизит ещё не назначен или под заявку не нашлось свободного реквизита. Если реквизит уже выдан — он есть в requisites ответа на создание и в GET.Request created; no requisite assigned yet, or no free requisite was found. If a requisite has been issued, it is in requisites of the create response and in GET. | нетno | |||
| Working | Реквизит назначен, ожидается оплата и её подтверждение.Requisite assigned, awaiting payment and its confirmation. | нетno | |||
| Confirmed | Платёж подтверждён, средства зачислены. Можно выдавать товар.Payment confirmed, funds credited. Safe to fulfil the order. | да*yes* | |||
| Canceled | Отменена вами, оператором, истёк срок жизни, либо платёж отменён после зачисления (сторно).Canceled by you or an operator, the lifetime expired, or the payment was reversed after crediting. | да*yes* | |||
| Processing | Реквизит ещё выдаётся, результат пока неизвестен (в т.ч. в режиме customer_mode: "assigned"). Отменить заявку в этом статусе нельзя; дождитесь вебхука или запросите GET позже.The requisite is still being issued, outcome not yet known (including customer_mode: "assigned"). Cannot be canceled in this status; wait for the webhook or poll GET later. | нетno |
Финальные статусы могут смениться ещё один раз: (1) Canceled → Confirmed — если оплата всё-таки поступила: для заявки, отменённой по истечении срока, — при подтверждении оплаты (обычно в течение 72 ч), для любой отменённой — по итогам диспута; (2) Confirmed → Canceled — отмена после зачисления (сторно), в вебхуке будет "reversed": true. Вы получите второй вебхук — обрабатывайте такие переходы корректно.
A final status may change once more: (1) Canceled → Confirmed — if the payment actually arrived: for a request canceled due to expiry, once the payment is confirmed (usually within 72 h); for any canceled request, as the outcome of a dispute; (2) Confirmed → Canceled — reversal after crediting, the webhook carries "reversed": true. You will receive a second webhook — handle these transitions correctly.
(Processing) ──► Waiting ──► Working ──► Confirmed ──(reversal)──► Canceled
│ │
└───────────┴──► Canceled ──(late confirm / dispute)──► Confirmed09ОтменаCancel
Отменить payin, пока он в статусе Waiting или Working. Отмена payout через API не поддерживается.
Cancel a payin while it is Waiting or Working. Payout cancellation via API is not supported.
{ "public_id": "PI-3f9a1c7e5b2d4a60" }{ "success": true, "data": { "public_id": "PI-3f9a1c7e5b2d4a60", "status": "Canceled" } }- Заявка в финальном статусе →
409 Cannot cancel request in terminal status Confirmed(илиCanceled). - Request in a final status →
409 Cannot cancel request in terminal status Confirmed(orCanceled). - Статус изменился в момент отмены или заявка в
Processing→409 Request state changed, cannot cancel— запросите статус. - Status changed during cancellation, or the request is
Processing→409 Request state changed, cannot cancel— query the status. - После успешной отмены на
callback_urlприходит вебхукCanceled, реквизит освобождается. - After a successful cancel a
Canceledwebhook is sent tocallback_url, the requisite is released.
Не отменяйте заявку, если покупатель уже мог перевести деньги: отменённую вами заявку можно подтвердить только через диспут. В спорном случае дождитесь вебхука.
Do not cancel a request if the customer may have already paid: a request you canceled can only be confirmed through a dispute. In doubtful cases wait for the webhook.
10Выплаты (payout)Payouts
Выплата создаётся тем же POST /requests с sub_direction вида payout_*. Реквизит получателя — в client_requisites (+ client_additional_requisites).
A payout is created with the same POST /requests using a payout_* sub_direction. The recipient requisite goes into client_requisites (+ client_additional_requisites).
{
"merchant_request_id": "payout-777",
"sub_direction": "payout_check",
"preferred_method": "c2c",
"client_amount": "12000.00",
"client_bank": "sber",
"client_requisites": "2200123412341234",
"client_additional_requisites": "Иван Иванов",
"client_full_name": "Иван Иванов",
"callback_url": "https://merchant.example.com/webhook"
}{
"success": true,
"data": {
"kind": "payout",
"public_id": "PO-1d7c9e20ab45f318",
"merchant_request_id": "payout-777",
"status": "Working",
"created_at": "2026-09-23T09:02:17.221Z",
"rub_amount": "12000.00",
"usdt_amount": "127.13513514",
"rate": "92.5000",
"rate_with_commission": "90.6500",
"instant_match": { "attempted": true, "matched": false }
}
}requisites для payout не возвращаются. Working — выплата принята в работу, Waiting — ещё ожидает назначения. Итог приходит вебхуком payout.status.changed; статус — GET /payout/{public_id}. В примере комиссия на выплату — 2%. Выплата создаётся только в пределах доступного баланса аккаунта (баланс минус открытые выплаты), иначе — 409 Insufficient merchant balance for payout: ….
requisites are not returned for payouts. Working — the payout is in progress, Waiting — awaiting assignment. The outcome arrives via the payout.status.changed webhook; status — GET /payout/{public_id}. The example assumes a 2% payout commission. A payout is accepted only within your available balance (balance minus open payouts), otherwise — 409 Insufficient merchant balance for payout: ….
11ВебхукиWebhooks
Slipstream отправляет POST на callback_url заявки, когда payin получил реквизит и когда заявка переходит в финальный статус.
Slipstream sends a POST to the request's callback_url when a payin gets its requisite and when the request reaches a final status.
Событие (event / X-Event) | Event (event / X-Event) | Когда | When |
|---|---|---|---|
payin.requisites.issued | payin получил реквизит для оплаты. Отправляется один раз на заявку. Реквизит может быть уже в ответе на создание — вебхук тогда дублирует его (удобно, если реквизит выдан позже, например в режиме Processing).A payin received its payment requisite. Sent once per request. The requisite may already be in the create response — then the webhook duplicates it (useful when the requisite is issued later, e.g. in Processing mode). | ||
payin.status.changed | payin перешёл в Confirmed или Canceledpayin became Confirmed or Canceled | ||
payout.status.changed | payout перешёл в Confirmed или Canceledpayout became Confirmed or Canceled | ||
test | Тестовый вебхук из кабинета мерчанта (кнопка проверки интеграции). Подписывается так же, как боевые.Test webhook from the merchant cabinet (integration check button). Signed exactly like live webhooks. |
Прочие промежуточные переходы (Waiting → Working) отдельным вебхуком не отправляются. Порядок доставки разных событий не гарантирован: ориентируйтесь на поле event и не откатывайте финальный статус назад по более позднему payin.requisites.issued.
Other intermediate transitions (Waiting → Working) are not sent as separate webhooks. Delivery order across events is not guaranteed: rely on the event field and never roll a final status back because of a later payin.requisites.issued.
Заголовки
Headers
POST /webhook HTTP/1.1
Content-Type: application/json
X-Event: payin.status.changed
X-Delivery-Id: 9b2f6c1e-4d7a-4f0e-9a51-2c8e7d3b1f60
X-Timestamp: 1790154137
X-Signature-V2: 5e0c…a41f
X-Signature: 3b6f…e91a| Заголовок | Header | Описание | Description |
|---|---|---|---|
X-Event | Имя события (совпадает с полем event в теле).Event name (same as the event body field). | ||
X-Delivery-Id | UUID доставки. Одинаков для всех повторов одной доставки (и для ручной повторной отправки из кабинета) — используйте для дедупликации.Delivery UUID. Identical across all retries of one delivery (and for a manual resend from the cabinet) — use it for deduplication. | ||
X-Timestamp | Unix-время отправки в секундах. Своё у каждой попытки.Send time, Unix seconds. Fresh on every attempt. | ||
X-Signature-V2 | Подпись с timestamp (защищает от повтора) — см. ниже. Проверяйте именно её.Timestamped signature (replay-protected) — see below. Verify this one. | ||
X-Signature | Переходный период: прежняя подпись без timestamp hex(HMAC_SHA256(secret, canonical_json(body))). После миграции мерчантов в этом заголовке будет та же подпись, что в X-Signature-V2.Transition period: the previous signature without timestamp hex(HMAC_SHA256(secret, canonical_json(body))). After merchants migrate, this header will carry the same value as X-Signature-V2. |
Тело: payin.requisites.issued
Body: payin.requisites.issued
{
"event": "payin.requisites.issued",
"public_id": "PI-3f9a1c7e5b2d4a60",
"merchant_request_id": "order-10001",
"status": "Working",
"kind": "payin",
"method": "c2c",
"rub_amount": "5000.00",
"requisites": {
"method": "c2c",
"type": "card",
"card_number": "2200123412341234",
"phone": null,
"account": null,
"bik": null,
"bank": "sber",
"bank_name": "Сбербанк",
"holder": "Иван И.",
"link": null,
"payment_url": null,
"expires_at": "2026-09-23T09:17:17.221Z"
},
"expires_at": "2026-09-23T09:17:17.221Z",
"sent_at": "2026-09-23T09:02:18.004Z"
}| Поле | Field | Описание | Description |
|---|---|---|---|
status | Текущий статус заявки на момент выдачи: обычно Working, иногда Waiting.Request status at issue time: usually Working, sometimes Waiting. | ||
method | Код метода оплаты.Payment method code. | ||
rub_amount | Сумма заявки (номинал), RUB.Request (nominal) amount, RUB. | ||
requisites | Реквизиты в едином формате — как в ответе на создание и в GET.Requisites in the unified format — same as in the create response and GET. | ||
expires_at | Срок жизни заявки, ISO 8601 UTC, или null.Request lifetime, ISO 8601 UTC, or null. | ||
sent_at | Время отправки этой попытки, ISO 8601 UTC.Time this attempt was sent, ISO 8601 UTC. |
Тело: payin.status.changed / payout.status.changed
Body: payin.status.changed / payout.status.changed
{
"event": "payin.status.changed",
"public_id": "PI-3f9a1c7e5b2d4a60",
"status": "Confirmed",
"merchant_request_id": "order-10001",
"kind": "payin",
"method": "c2c",
"rub_amount": "5000.00",
"actual_amount": "4900.00",
"amount_mismatch": true,
"completed_at": "2026-09-23T09:10:41.512Z",
"canceled_at": null,
"sent_at": "2026-09-23T09:10:41.873Z"
}{
"event": "payout.status.changed",
"public_id": "PO-1d7c9e20ab45f318",
"status": "Canceled",
"merchant_request_id": "payout-777",
"kind": "payout",
"method": "c2c",
"rub_amount": "12000.00",
"actual_amount": null,
"amount_mismatch": false,
"completed_at": null,
"canceled_at": "2026-09-23T09:20:05.030Z",
"sent_at": "2026-09-23T09:20:05.311Z"
}| Поле | Field | Описание | Description |
|---|---|---|---|
event | payin.status.changed | payout.status.changed | ||
public_id | ID заявки в SlipstreamSlipstream request ID | ||
status | Confirmed | Canceled | ||
merchant_request_id | Ваш ID или null, если не передавалсяYour ID, or null if not provided | ||
kind | payin | payout | ||
method | Код метода оплаты.Payment method code. | ||
rub_amount | Сумма заявки (номинал), RUB.Request (nominal) amount, RUB. | ||
actual_amount | Фактически оплаченная сумма, если при подтверждении она отличалась от rub_amount; иначе null. Зачисление идёт по фактической сумме.Actually paid amount if it differed from rub_amount at confirmation; otherwise null. Settlement uses the actual amount. | ||
amount_mismatch | true, если actual_amount отличается от rub_amount.true if actual_amount differs from rub_amount. | ||
completed_at | Время подтверждения (для Confirmed), иначе null.Confirmation time (for Confirmed), otherwise null. | ||
canceled_at | Время отмены (для Canceled), иначе null.Cancellation time (for Canceled), otherwise null. | ||
sent_at | Время отправки этой попытки, ISO 8601 UTC.Time this attempt was sent, ISO 8601 UTC. | ||
reversed, reversed_at | Только при отмене уже подтверждённой заявки (сторно): status: "Canceled", reversed: true и время сторно. Средства по заявке списаны обратно.Only when an already confirmed request is reversed: status: "Canceled", reversed: true and the reversal time. The request funds are debited back. |
Тело: test
Body: test
{
"event": "test",
"merchant_id": "42",
"ts": "2026-09-23T09:00:00.000Z",
"sent_at": "2026-09-23T09:00:00.000Z"
}Проверка подписи
Signature verification
X-Signature-V2 = hex( HMAC_SHA256( YOUR_HMAC_SECRET, X-Timestamp + "." + raw_body ) )
X-Signature = hex( HMAC_SHA256( YOUR_HMAC_SECRET, raw_body ) ) // переходный период / transition periodТело отправляется уже в каноническом виде (ключи отсортированы, без пробелов), поэтому проверяйте подпись по сырому телу запроса как есть — до парсинга JSON. Алгоритм проверки: (1) |now − X-Timestamp| ≤ 300 секунд, иначе отклонить — это защита от повторного воспроизведения; (2) посчитать HMAC от X-Timestamp + "." + raw_body и сравнить с X-Signature-V2 в константное время; (3) дедуплицировать по X-Delivery-Id. Часы сервера синхронизируйте по NTP.
The body is already sent in canonical form (sorted keys, no whitespace), so verify the signature over the raw request body as-is — before parsing JSON. Verification: (1) |now − X-Timestamp| ≤ 300 seconds, otherwise reject — this is replay protection; (2) compute HMAC over X-Timestamp + "." + raw_body and compare with X-Signature-V2 in constant time; (3) deduplicate by X-Delivery-Id. Keep your server clock NTP-synced.
const crypto = require('crypto');
const express = require('express');
const app = express();
const SECRET = 'YOUR_HMAC_SECRET';
const WINDOW_SEC = 300;
// raw body: the signature is computed over the exact bytes we sent
app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
const raw = req.body.toString('utf8');
const ts = Number(req.get('X-Timestamp'));
if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > WINDOW_SEC) {
return res.status(401).end(); // stale or missing timestamp — possible replay
}
const expected = Buffer.from(crypto.createHmac('sha256', SECRET)
.update(`${ts}.${raw}`, 'utf8').digest('hex'));
const got = Buffer.from(String(req.get('X-Signature-V2') || ''));
if (got.length !== expected.length || !crypto.timingSafeEqual(got, expected)) {
return res.status(401).end();
}
const deliveryId = req.get('X-Delivery-Id');
if (await alreadyProcessed(deliveryId)) return res.status(200).json({ ok: true });
const body = JSON.parse(raw);
switch (body.event) {
case 'payin.requisites.issued': await showRequisites(body.merchant_request_id, body.requisites); break;
case 'payin.status.changed':
case 'payout.status.changed': await applyStatus(body.merchant_request_id, body.public_id, body.status, body.actual_amount); break;
}
await markProcessed(deliveryId);
res.status(200).json({ ok: true });
});import hashlib, hmac, json, time
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = b"YOUR_HMAC_SECRET"
WINDOW_SEC = 300
@app.post("/webhook")
def webhook():
raw = request.get_data() # exact bytes as sent
try:
ts = int(request.headers.get("X-Timestamp", ""))
except ValueError:
abort(401)
if abs(time.time() - ts) > WINDOW_SEC:
abort(401) # stale timestamp — possible replay
expected = hmac.new(SECRET, f"{ts}.".encode() + raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("X-Signature-V2", "")):
abort(401)
delivery_id = request.headers.get("X-Delivery-Id")
if already_processed(delivery_id):
return {"ok": True}, 200
body = json.loads(raw)
if body["event"] == "payin.requisites.issued":
show_requisites(body.get("merchant_request_id"), body["requisites"])
elif body["event"] in ("payin.status.changed", "payout.status.changed"):
apply_status(body.get("merchant_request_id"), body["public_id"], body["status"], body.get("actual_amount"))
mark_processed(delivery_id)
return {"ok": True}, 200Переход со старой подписи. Сейчас X-Signature — прежняя формула без timestamp (для совместимости), новая с timestamp приходит в X-Signature-V2. Переходите на проверку X-Signature-V2 по сырому телу и X-Timestamp: после миграции мерчантов X-Signature будет совпадать с ней.
Migrating from the old signature. For now X-Signature is the previous formula without timestamp (compatibility); the new timestamped one arrives in X-Signature-V2. Switch to verifying X-Signature-V2 over the raw body and X-Timestamp: once merchants migrate, X-Signature will equal it.
Доставка и повторы
Delivery and retries
- Успех — любой HTTP-ответ
2xxв течение 10 секунд. Всё остальное (другой код, таймаут, ошибка соединения) — повтор. - Success — any
2xxHTTP response within 10 seconds. Anything else (other code, timeout, connection error) triggers a retry. - Всего до 5 попыток: сразу, затем примерно через 5 с, 30 с, 5 мин и 1 ч. После пятой неудачи доставка прекращается — сверяйте статус через
GET /payin/{public_id}. - Up to 5 attempts in total: immediately, then after roughly 5 s, 30 s, 5 min and 1 h. After the fifth failure delivery stops — reconcile via
GET /payin/{public_id}. - У всех повторов одной доставки одинаковые тело (кроме
sent_at) иX-Delivery-Id;X-Timestampи подпись — свежие на каждую попытку. - All retries of one delivery share the body (except
sent_at) andX-Delivery-Id;X-Timestampand the signature are fresh on every attempt. - Финальный вебхук записывается атомарно со сменой статуса и не теряется при сбоях на нашей стороне. Повторную отправку можно запустить вручную из кабинета мерчанта (с тем же
X-Delivery-Id). - The final webhook is recorded atomically with the status change and is not lost on failures on our side. A resend can be triggered manually from the merchant cabinet (with the same
X-Delivery-Id).
Рекомендации. Отвечайте 200 быстро и обрабатывайте асинхронно. Обработчик должен быть идемпотентным: храните обработанные X-Delivery-Id, ищите заказ по своему ID (merchant_request_id) и сверяйте public_id. При сомнении — запросите актуальный статус через API.
Recommendations. Reply 200 quickly and process asynchronously. The handler must be idempotent: store processed X-Delivery-Id values, look up the order by your ID (merchant_request_id) and verify public_id. When in doubt, query the current status via API.
12ИдемпотентностьIdempotency
Все POST требуют X-Idempotency-Key — защита от двойного создания заявки при повторах и сетевых сбоях.
Every POST requires X-Idempotency-Key — protection against duplicate requests on retries and network failures.
| Ситуация | Situation | Результат | Result |
|---|---|---|---|
| Новый ключNew key | Запрос выполняется, ответ сохраняется за ключом.The request is executed, the response is stored under the key. | ||
| Тот же ключ + то же телоSame key + same body | Возвращается сохранённый ответ (тот же HTTP-код и тело), повторного выполнения нет.The stored response is returned (same HTTP code and body), nothing is executed again. | ||
| Тот же ключ + другое телоSame key + different body | 409 Idempotency key reuse with different payload is not allowed | ||
| Первый запрос с этим ключом ещё выполняетсяFirst request with this key still in progress | 409 — подождите и повторитеwait and retry | ||
| Нет заголовка или длиннее 128Missing header or longer than 128 | 400 X-Idempotency-Key is required |
- Ключ уникален в рамках вашего аккаунта. Тело сравнивается канонически — порядок ключей не важен.
- The key is scoped to your account. Bodies are compared canonically — key order does not matter.
- Ответы
4xxтоже сохраняются: исправив тело, используйте новый ключ. 4xxresponses are stored too: after fixing the body, use a new key.- Если получили
5xxили таймаут, повтор с тем же ключом может вернуть409: первый запрос мог быть выполнен. Если в теле естьmerchant_request_id, через ~10 минут повтор с тем же ключом и телом выполнится заново — безопасно, т.к. дубль поmerchant_request_idвернёт409сpublic_idуже созданной заявки. Безmerchant_request_idне создавайте новую заявку вслепую — сверьтесь с кабинетом или поддержкой. - If you got a
5xxor a timeout, retrying with the same key may return409: the first request may have been executed. If the body has amerchant_request_id, after ~10 minutes a retry with the same key and body is executed again — safe, because a duplicatemerchant_request_idreturns409with the existingpublic_id. Withoutmerchant_request_id, do not blindly create a new request — check the cabinet or contact support.
merchant_request_id уникален среди ваших открытых и оплаченных заявок одного направления: повтор → 409 merchant_request_id already used by request <public_id> (<status>); после Canceled его можно использовать снова. Удобная практика — использовать его же как X-Idempotency-Key при создании заявки, а для чеков и отмены — производные ключи (cancel-<public_id>, proof-<public_id>-<n>).
merchant_request_id is unique across your open and paid requests of the same direction: a repeat → 409 merchant_request_id already used by request <public_id> (<status>); it can be reused after Canceled. A handy practice is to use it as X-Idempotency-Key when creating a request, and derived keys for receipts and cancel (cancel-<public_id>, proof-<public_id>-<n>).
13ЛимитыLimits
Актуальные min/max суммы по каждому методу для вашего аккаунта — считаются по реквизитам, которые прямо сейчас могут принять заявку. Значения меняются в течение дня: опрашивайте периодически (например, раз в минуту) и не показывайте покупателю методы с available: false.
Current min/max amounts per method for your account, computed from requisites that can accept a request right now. Values change during the day: poll periodically (e.g. once a minute) and hide methods with available: false from customers.
{
"success": true,
"data": {
"currency": "RUB",
"updatedAt": "2026-09-23T09:10:00.000Z",
"methods": [
{ "code": "c2c", "label": "К2К", "available": true, "sources": 4,
"min": 100000, "max": 15000000, "minRub": 1000, "maxRub": 150000 },
{ "code": "sbp", "label": "СБП", "available": true, "sources": 2,
"min": 50000, "max": 50000000, "minRub": 500, "maxRub": 500000 },
{ "code": "deeplink_card", "label": "Deeplink Карта", "available": false, "sources": 0,
"min": null, "max": null, "minRub": null, "maxRub": null }
]
}
}| Поле | Field | Описание | Description |
|---|---|---|---|
available | Метод подключён вам и сейчас есть хотя бы один доступный источник реквизитов.The method is enabled for you and at least one requisite source is available now. | ||
min, max | В копейках. max — реальный потолок суммы прямо сейчас (не выше максимальной суммы заявки, см. ниже). У недоступного метода (available: false) min и max — null.In kopecks. max — the actual amount ceiling right now (never above the max request amount, see below). For an unavailable method (available: false) min and max are null. | ||
minRub, maxRub | То же в рублях.Same in RUB. | ||
sources | Количество доступных источников реквизитов.Number of available requisite sources. |
В список входят все включённые в системе методы; неподключённые вам отдаются с available: false.
The list contains all methods enabled in the system; those not enabled for you come with available: false.
Технические лимиты API
Technical API limits
| Максимальная сумма заявкиMax request amount | 500 000.00 RUB (по умолчанию)(default) |
| Частота запросовRequest rate | 600 запросов / 60 с на мерчанта и 1200 запросов / 60 с с одного IP, иначе 429 с заголовком Retry-After600 requests / 60 s per merchant and 1200 requests / 60 s per IP, otherwise 429 with a Retry-After header |
Окно X-TimestampX-Timestamp window | ±300 s |
| Размер JSON-телаJSON body size | 256 KB → 413 |
| Чеки (multipart)Receipts (multipart) | до 5 файлов × 10 МБ за запрос, не более 10 чеков на заявку (иначе 409)up to 5 files × 10 MB per call, at most 10 receipts per request (otherwise 409) |
| Срок жизни заявкиRequest lifetime | 10 минут (по умолчанию), затем автоотмена → Canceled10 minutes (default), then auto-cancel → Canceled |
14ДиспутыDisputes
Покупатель утверждает, что оплатил, а заявка не подтверждена? Откройте диспут; пока заявка не в Canceled, приложите чек через POST /payin/proof к той же заявке. К заявке в статусе Canceled чек через API не прикрепить (409) — укажите в reason сумму, время и банк платежа и передайте чек менеджеру.
The customer says they paid but the request isn't confirmed? Open a dispute; while the request is not Canceled, attach the receipt via POST /payin/proof to the same request. A receipt cannot be attached via API to a Canceled request (409) — put the payment amount, time and bank in reason and send the receipt to your manager.
{ "public_id": "PI-3f9a1c7e5b2d4a60", "reason": "Клиент оплатил 5000 ₽ в 11:42, чек приложен" }{
"success": true,
"data": {
"dispute_id": "57",
"public_id": "PI-3f9a1c7e5b2d4a60",
"status": "open",
"kind": "payment",
"reason": "Клиент оплатил 5000 ₽ в 11:42, чек приложен",
"note": null,
"resolved_at": null,
"deadline_at": "2026-10-07T09:20:00.000Z",
"created_at": "2026-09-23T09:20:00.000Z"
}
}reason— от 3 до 2000 символов. Срок рассмотрения — 14 дней (deadline_at).reason— 3 to 2000 chars. Review deadline — 14 days (deadline_at).- Статусы:
open→ (in_progress) →wonилиrejected.kind: "payment"— спор по неподтверждённой заявке:won= платёж признан, придёт вебхукConfirmed.kind: "chargeback"— спор по уже подтверждённой заявке:won= оплата отменена, придёт вебхукCanceledсreversed: true. Об изменении статуса спора вебхук не приходит — опрашивайтеGET /disputes/{dispute_id}. - Statuses:
open→ (in_progress) →wonorrejected.kind: "payment"— dispute on an unconfirmed request:won= payment accepted, aConfirmedwebhook follows.kind: "chargeback"— dispute on an already confirmed request:won= payment reversed, aCanceledwebhook withreversed: truefollows. No webhook is sent on dispute status changes — pollGET /disputes/{dispute_id}. - По заявке, оплата которой уже отменена (сторно), диспут не открыть (
409); на заявку может быть только один активный диспут (409). - No dispute on a request whose payment has already been reversed (
409); only one active dispute per request (409).
15Коды ошибокError codes
Формат ошибки единый. Для ошибок валидации дополнительно приходит массив errors вида "поле: сообщение".
The error format is uniform. Validation errors additionally include an errors array of "field: message".
{
"success": false,
"error": "client_amount: client_amount must be a positive decimal string",
"errors": [
"client_amount: client_amount must be a positive decimal string"
]
}| HTTP | Примеры error | error examples | Что делать | What to do |
|---|---|---|---|---|
400 | <field>: …(root): Either client_bank or client_bank_code is requiredМетод «sbp» не подключён для этого мерчантаclient_amount exceeds max (500000.00 RUB)X-Idempotency-Key is requiredЭтот метод требует чек в формате PDF | Исправьте запрос; повторяйте с новым ключом идемпотентности.Fix the request; retry with a new idempotency key. | ||
401 | Authorization header is requiredInvalid tokenX-Timestamp is requiredTimestamp is outside the allowed windowX-Signature is requiredInvalid signatureSignature already used (replay)Unsupported X-Signature-Version (allowed: 1, 2) | Проверьте ключ, часы сервера (NTP), строку подписи.Check the key, server clock (NTP), signing string. | ||
403 | Merchant is disabledДоступ с этого адреса запрещён настройками мерчантаCUSTOMER_ACCESS_DENIED, CUSTOMER_PAUSED | Свяжитесь с менеджером / добавьте IP.Contact your manager / add the IP. | ||
404 | Request not found, Dispute not found | Проверьте public_id и направление (payin/payout).Check public_id and direction (payin/payout). | ||
409 | Cannot cancel request in terminal status …Request state changed, cannot cancelCannot attach proof to canceled requestToo many receipts for this request (max 10)merchant_request_id already used by request …Active dispute #… already exists for this requestRequest reversed — nothing to disputeInsufficient merchant balance for payout: …Idempotency key reuse with different payload is not allowed | Запросите статус заявки; про ключ — см. Идемпотентность.Query the request status; for keys see Idempotency. | ||
413 | Request body too largefile too large (…) | Уменьшите тело; для файлов используйте multipart.Reduce the body; use multipart for files. | ||
424 | Receipts were saved on our side, but were not accepted on verification. … | Чек сохранён, но не принят при проверке — проверьте файл и повторите с новым ключом.Receipt saved but not accepted on verification — check the file and retry with a new key. | ||
429 | Too many requests | Снизьте частоту, повторите позже.Reduce the rate, retry later. | ||
5xx | Internal server error | Временная ошибка. Для создания заявки — не создавайте новую вслепую (см. Идемпотентность).Temporary error. For request creation — do not blindly create a new one (see Idempotency). |
16Справочник банковBank reference
Коды для client_bank. Регистр не важен. any — любой банк. Список может расширяться; неизвестный код не вызывает ошибку, но может сузить подбор реквизита.
Codes for client_bank. Case-insensitive. any — any bank. The list may grow; an unknown code does not raise an error but may narrow requisite selection.
sbertinkoffalfavtbpsbsovcombankozonyandexwbpskbrenesans_bankrocket_bankbankspbdomrfakbarssevergazbankgazenergobankbankdolinskbcssolidarnostrsbhiseraiffeisenuralsibbbrrosselkhozmtsingostrahotpzenitetranslockoyoomoneynskblgazprombanksim_beelinesim_mtssim_tele2Коды sim_* — только для метода sim.
sim_* codes are for the sim method only.
17FAQ
Сколько живёт заявка?How long does a request live?
По умолчанию 10 минут с момента создания. Если за это время платёж не подтверждён, заявка автоматически переходит в Canceled и приходит вебхук. Если оплата всё же поступила позже, заявку могут подтвердить — придёт вебхук Confirmed.
By default 10 minutes from creation. If the payment is not confirmed in that time, the request is automatically set to Canceled and a webhook is sent. If the payment arrives later, the request can still be confirmed — a Confirmed webhook follows.
Покупатель перевёл не ту сумму — что будет?The customer paid a different amount — what happens?
При подтверждении фиксируется фактически полученная сумма. Если расхождение меньше 200 ₽ (по умолчанию) — заявка закрывается на исходную сумму; если больше — расчёт идёт по фактической сумме. Фактическая сумма возвращается в GET /payin/{public_id} и GET /payout/{public_id} в поле actual_amount с флагом amount_mismatch: true; rub_amount остаётся суммой заявки.
On confirmation the actually received amount is recorded. If the difference is under 200 RUB (default), the request closes at the original amount; otherwise settlement uses the actual amount. The actual amount is returned by GET /payin/{public_id} and GET /payout/{public_id} in actual_amount with amount_mismatch: true; rub_amount stays the request amount.
Можно ли повторно получить реквизиты?Can I fetch the requisites again?
Да: GET /payin/{public_id} возвращает requisites (в том же формате, что и при создании) и expires_at, пока заявка открыта. После Confirmed/Canceled — requisites: null. Ещё вариант — повторить тот же POST /requests с тем же X-Idempotency-Key и телом.
Yes: GET /payin/{public_id} returns requisites (same format as on create) and expires_at while the request is open. After Confirmed/Canceled — requisites: null. Alternatively, repeat the same POST /requests with the same X-Idempotency-Key and body.
В ответе нет requisites. Что делать?There's no requisites in the response. What now?
Если status: "Processing" — реквизит ещё выдаётся: не отменяйте, опрашивайте GET /payin/{public_id} до появления requisites. Иначе сейчас нет свободного реквизита под этот метод/банк/сумму. Отмените заявку (POST /payin/cancel) и предложите покупателю другой метод или сумму; сверьтесь с GET /limits.
If status: "Processing", the requisite is still being issued: do not cancel, poll GET /payin/{public_id} until requisites appear. Otherwise there is no free requisite for this method/bank/amount right now. Cancel the request (POST /payin/cancel) and offer another method or amount; check GET /limits.
Нужно ли присылать чек?Do I need to upload a receipt?
Для sbp_check и card_check — да, в PDF, иначе платёж не будет засчитан. Для остальных методов — нет, но чек ускоряет разбор спорных платежей.
For sbp_check and card_check — yes, as PDF, otherwise the payment is not credited. For other methods — no, but a receipt speeds up disputed cases.
Пришёл Canceled, а потом Confirmed. Это ошибка?I got Canceled and then Confirmed. Is that a bug?
Нет. Так выглядит позднее подтверждение: заявка истекла по времени, но оплата поступила и была подтверждена. Confirmed — окончательный результат; исключение — отмена уже зачисленной оплаты (сторно, например по признанному спору-чарджбеку): тогда придёт Canceled с reversed: true.
No. That's a late confirmation: the request expired by time, but the payment arrived and was confirmed. Confirmed is the final outcome, except when an already credited payment is reversed (e.g. an upheld chargeback dispute): then a Canceled webhook with reversed: true follows.
Вебхук не дошёл — как узнать статус?The webhook didn't arrive — how do I get the status?
Мы делаем до 5 попыток в течение примерно часа. Если ваш сервер был недоступен дольше — запросите GET /payin/{public_id} (или /payout/…) либо перешлите вебхук из кабинета. Рекомендуем периодически сверять «зависшие» заказы через API.
We make up to 5 attempts over roughly one hour. If your server was down longer, call GET /payin/{public_id} (or /payout/…) or resend the webhook from the cabinet. We recommend periodically reconciling “stuck” orders via API.
Чем отличаются /requests и /requests/simple?What's the difference between /requests and /requests/simple?
Сейчас ничем: это один обработчик с одинаковой схемой тела. /requests/simple оставлен для совместимости.
Nothing at the moment: it's the same handler with the same body schema. /requests/simple is kept for compatibility.