Приём оплаты на сайте — SCI
Покупатель переходит на страницу оплаты, платит с любого кошелька, а ваш сервер получает подписанное уведомление с итогом. Здесь — всё, что нужно для подключения.
Содержание
Как это работает
- Покупатель нажимает «Оплатить» на вашем сайте — браузер отправляет подписанную форму на страницу оплаты.
- Создаётся счёт: покупатель выбирает монету и видит сумму, адрес, QR-код и таймер.
- Покупатель переводит монеты. Мы ждём подтверждений сети и сверяем сумму, монету и время.
- Ваш сервер получает уведомление с итогом, покупатель возвращается на ваш сайт.
Подключение по шагам
- В кабинете: «Проекты» → «Добавить проект», название и адрес сайта. Вы получите ID магазина, ключ формы (sk_…) и секрет вебхуков (whsec_…). Ключи показываются один раз — сохраните их на сервере магазина.
- «Монеты» — включите монеты и сети, которые принимаете.
- «Страница оплаты» — срок счёта, допуск суммы, язык, адреса возврата и почта поддержки для покупателей.
- «Уведомления» — адрес вебхука и «Отправить тестовое».
- «Подключение» — подтвердите домен и переключите проект в режим «Тест».
- Проверьте свою сторону на тестовых счетах и переключите проект в режим «Работает».
Форма оплаты
Соберите форму на сервере магазина: браузер покупателя отправит поля методом POST на адрес страницы оплаты. Ключ формы в браузер не попадает — в форме только подпись.
<form method="post" action="https://sci.coinvertus.net">
<input type="hidden" name="shop" value="P4K7Q2A9">
<input type="hidden" name="order_id" value="10482">
<input type="hidden" name="amount" value="49.90">
<input type="hidden" name="currency" value="USD">
<input type="hidden" name="sign" value="<?= $sign ?>">
<input type="hidden" name="success_url" value="https://shop.example/orders/10482">
<button type="submit">Pay with crypto</button>
</form>Поля формы
| Поле | Описание |
|---|---|
shopОбязательное | ID магазина из кабинета, 8 символов. |
order_idОбязательное | Номер заказа в магазине: 1–64 символа без пробелов. Повтор с той же суммой и валютой откроет тот же счёт, с другой — ошибка S005. |
amountОбязательное | Сумма с точкой, до двух знаков после неё: 49.90, 49.9 или 50. Больше нуля. |
currencyОбязательное | Валюта суммы: USD, EUR, RUB, CNY или INR. Сумму в монете страница считает по курсу, когда покупатель выбирает монету. |
signОбязательное | Подпись формы — ниже. |
asset | Монета за покупателя в виде «символ:сеть» — как asset и network в уведомлении, например DOGE:dogecoin-mainnet или USDT:bsc. Если монета включена в проекте, покупатель сразу видит реквизиты. |
description | Описание заказа до 255 символов, покупатель видит его на странице оплаты. |
email | Почта покупателя, до 254 символов. |
lang | Язык страницы: ru, en или cn. Без него — язык из настроек проекта, иначе язык браузера. Покупатель может переключить язык сам. |
success_url | Куда вернуть покупателя после оплаты, http(s)-адрес. Без него — адрес из настроек проекта, иначе сайт магазина. |
fail_url | Куда вернуть покупателя, если оплаты не было или она не совпала. Без него — адрес из настроек проекта, иначе сайт магазина. |
metadata | JSON-объект или массив до 4 КБ, возвращается в уведомлении как есть. |
Подпись
sign — HMAC-SHA256 в шестнадцатеричном виде от строки shop:order_id:amount:currency, ключ — ключ формы проекта. Значения берутся ровно такими, какими уходят в форму: 49.90 и 49.9 дают разные подписи.
$shop = 'P4K7Q2A9';
$formKey = getenv('CVC_SCI_FORM_KEY');
$orderId = '10482';
$amount = '49.90';
$currency = 'USD';
$sign = hash_hmac('sha256', $shop . ':' . $orderId . ':' . $amount . ':' . $currency, $formKey);Ошибки формы
Если форма не прошла проверку, покупатель видит страницу с кодом ошибки и ссылкой на ваш сайт.
| Код ошибки | Что значит |
|---|---|
S002 | Поле не прошло проверку — его название есть в тексте ошибки. |
S003 | Подпись не совпала. |
S004 | Магазин не найден или не принимает оплату: проект в режиме «Черновик», в режиме «Работает» без подтверждённого домена или тариф без приёма оплаты. |
S005 | Заказ с этим order_id уже открыт с другой суммой или валютой. |
S006 | Внутренняя ошибка при создании счёта — повторите позже. |
S007 | Слишком много новых счетов: не больше 300 в час на проект и 30 в час с одного адреса покупателя. |
Счёт и статусы
Срок счёта — от 15 до 120 минут, он задаётся во вкладке «Страница оплаты». Сумма в монете фиксируется, когда покупатель выбирает монету; сменить монету можно до трёх раз. Сколько подтверждений ждать, решает сеть.
| Статус | Описание |
|---|---|
new | Счёт создан, монета не выбрана. |
waiting | Монета выбрана, ждём перевод. |
confirming | Перевод в сети, ждём подтверждений. |
partial | Пришла часть суммы, ждём доплату — не меньше 15 минут от первой части. |
paid | Оплачен: сумма в допуске, в срок, та монета. |
received | Деньги пришли, но что-то не совпало — причина в warnings. |
expired | Срок вышел, перевода не было. Перевод ровно на сумму, пришедший позже, всё равно будет учтён: счёт станет received с incorrect time. |
cancelled | Магазин отменил счёт через API до оплаты. |
Возврат в магазин
После оплаты кнопка «Продолжить» ведёт на success_url, в остальных случаях — на fail_url. Возврат ничего не доказывает: покупатель может открыть этот адрес сам. Меняйте статус заказа только по уведомлению.
API сервера магазина
Форма подходит, когда счёт открывает браузер покупателя. Если заказ оформляет ваш сервер, создайте счёт через API и отправьте покупателя по ссылке url из ответа. Через API же можно проверить счёт, если уведомление не дошло, отменить счёт без оплаты и получить список монет с суммами.
| Метод и адрес | Описание |
|---|---|
POST https://sci.coinvertus.net/api/v1/invoices | Создать счёт. |
GET https://sci.coinvertus.net/api/v1/invoices/{number} | Счёт по его номеру, например CVC-7F3K2A. |
GET https://sci.coinvertus.net/api/v1/invoices?order_id=… | Счёт по номеру заказа в магазине. |
POST https://sci.coinvertus.net/api/v1/invoices/{number}/cancel | Отменить счёт, пока по нему нет перевода. |
GET https://sci.coinvertus.net/api/v1/assets?currency=USD&amount=49.90 | Монеты, которые сейчас предложит страница оплаты, с курсом и суммой. |
Подпись запроса
Каждый запрос подписывается ключом формы проекта — тем же, что и форма; сам ключ не передаётся. v1 — HMAC-SHA256 в шестнадцатеричном виде от строки «t.МЕТОД.путь.тело»: путь — от /v1/ вместе со строкой запроса, ровно как вы его отправляете; тело — те же байты, что уходят в запросе, у GET — пустая строка. Запрос, у которого t отличается от наших часов больше чем на 5 минут, отклоняется.
| Заголовок | Описание |
|---|---|
X-CVC-Shop | ID магазина — то же значение, что в поле формы shop. |
X-CVC-Signature | Подпись: t=<время Unix>,v1=<подпись>. |
<?php
function cvcSignature(string $method, string $path, string $body): string
{
$t = time();
$v1 = hash_hmac('sha256', $t . '.' . $method . '.' . $path . '.' . $body, getenv('CVC_SCI_FORM_KEY'));
return 't=' . $t . ',v1=' . $v1;
}
$body = json_encode([
'order_id' => '10482',
'amount' => '49.90',
'currency' => 'USD',
'success_url' => 'https://shop.example/orders/10482',
]);
$response = $http->request('POST', 'https://sci.coinvertus.net/api/v1/invoices', $body, [
'Content-Type: application/json',
'X-CVC-Shop: P4K7Q2A9',
'X-CVC-Signature: ' . cvcSignature('POST', '/v1/invoices', $body),
]);
if ($response->status === 200 || $response->status === 201) {
header('Location: ' . json_decode($response->body, true)['url'], true, 303);
}
$path = '/v1/invoices?order_id=' . rawurlencode('10482');
$response = $http->request('GET', 'https://sci.coinvertus.net/api' . $path, '', [
'X-CVC-Shop: P4K7Q2A9',
'X-CVC-Signature: ' . cvcSignature('GET', $path, ''),
]);$http — ваш HTTP-клиент: он отправляет запрос с этими заголовками и возвращает код ответа и тело.
Создание счёта
Тело — JSON с теми же полями, что у формы, без shop и sign: order_id, amount и currency обязательны, остальные — по желанию. amount и order_id можно передать числом, metadata — объектом. Новый счёт возвращается с кодом 201, повтор того же order_id с той же суммой и валютой — тот же счёт с кодом 200. Лимит — 300 новых счетов в час на проект.
{
"test": false,
"validation": null,
"warnings": [],
"invoice": {
"number": "CVC-7F3K2A",
"order_id": "10482",
"status": "new",
"amount": "49.90",
"currency": "USD",
"asset": null,
"network": null,
"rate": null,
"amount_crypto": null,
"received_crypto": null,
"remaining_crypto": null,
"overpaid_crypto": null,
"expires_at": "2026-09-17T12:22:00Z",
"metadata": {
"cart": "a17"
}
},
"settlement": {
"mode": "direct",
"fee_percent": "0.00"
},
"url": "https://sci.coinvertus.net/pY3kQ9vT2mXc8LwR1nZb4A"
}Ответ — счёт в тех же полях, что в уведомлении: invoice, settlement и test. validation и warnings — текущий итог счёта: null и пустой список, пока денег нет. url — страница оплаты: на неё отправьте покупателя. Проверка счёта и отмена отвечают так же.
Отмена счёта
Отменить можно только счёт без денег: в статусе new или waiting и без перевода, даже неподтверждённого. Счёт получает статус cancelled, вашему серверу уходит уведомление invoice.cancelled, повторная отмена возвращает тот же счёт. Если покупатель всё же переведёт ровно сумму счёта, перевод будет учтён: придёт payment.received с validation false; другая сумма уведомления не даст.
Список монет
Монеты, которые страница оплаты предложит прямо сейчас: включённые в проекте и с актуальным курсом. currency — валюта курса, по умолчанию USD; с amount в ответе есть и сумма в монете, как её посчитает счёт. asset и network подходят для поля asset: DOGE:dogecoin-mainnet.
Ошибки API
Ошибка — код HTTP и тело {"error": {"code": "S002", "message": "…"}}; у S002 ещё есть fields — имена неверных полей. message — для людей, на языке из Accept-Language; в коде опирайтесь на code.
| Код HTTP | Код ошибки | Что значит |
|---|---|---|
400 | S002 | Неверное поле или тело запроса; имена полей — в error.fields. |
401 | S003 | Неизвестный X-CVC-Shop, неверная или устаревшая подпись. |
403 | S004 | Проект не принимает оплату: черновик, режим «Работает» без подтверждённого домена или тариф без приёма оплаты. Только при создании счёта. |
404 | S008 | Счёта с таким номером или order_id в проекте нет. |
409 | S005 | Заказ с этим order_id уже открыт с другой суммой или валютой. |
409 | S009 | Отмена невозможна: у счёта уже есть перевод или его статус не new и не waiting. |
429 | S007 | Больше 300 новых счетов за час в проекте. |
500 | S006 | Внутренняя ошибка при создании счёта — повторите позже. |
Уведомления (вебхуки)
Адрес задаётся во вкладке «Уведомления»: https-адрес и, если нужно, IP сервера и Host. Смена адреса — с кодом подтверждения из приложения-аутентификатора или из письма. «Отправить тестовое» шлёт ping; без доставленного ping проект нельзя перевести в «Работает».
Уведомление — POST с телом в JSON. Успех — любой ответ 2xx в течение 10 секунд. Иначе повторяем через 1 минуту, 5 минут, 30 минут, 2 часа, 6 часов и 24 часа. Редиректы не выполняются, ответ читается не больше 64 КБ.
| Заголовок | Описание |
|---|---|
X-CVC-Event | Событие, как event в теле. |
X-CVC-Delivery | event_id и номер попытки. |
X-CVC-Signature | Подпись: t=<время Unix>,v1=<подпись>. |
v1 — HMAC-SHA256 в шестнадцатеричном виде от строки «t.тело», ключ — секрет вебхуков проекта. Берите тело ровно в тех байтах, что пришли, до разбора JSON, и отклоняйте уведомление, если t отличается от ваших часов больше чем на 5 минут.
Одно и то же уведомление может прийти дважды — обрабатывайте каждый event_id один раз. Порядок доставки не гарантирован: не откатывайте оплаченный заказ из-за более раннего уведомления.
События уведомлений
| Событие | Когда приходит |
|---|---|
payment.received | Перевод на адрес счёта набрал подтверждения. Несёт validation и warnings. На каждый выход транзакции — своё уведомление. |
invoice.expired | Срок счёта вышел: перевода не было (status expired) или не доплатили (status received). |
invoice.cancelled | Счёт отменён через API. |
payment.reversed | Учтённый перевод пропал из цепочки после реорганизации блоков — разбираемся на нашей стороне. |
payment.restored | Блок с переводом вернулся в цепочку, второй раз платёж не учитывается. |
ping | Тестовое уведомление из кабинета. |
Все события, кроме ping, можно выключить галочками во вкладке «Уведомления».
Тело уведомления
Суммы — строкой с точкой, без лишних нулей. Пока монета не выбрана, поля монеты равны null. Время — в UTC.
{
"event": "payment.received",
"event_id": "evt_0123456789abcdef01234567",
"created_at": "2026-09-17T12:10:00Z",
"test": false,
"validation": true,
"warnings": [],
"invoice": {
"number": "CVC-7F3K2A",
"order_id": "10482",
"status": "paid",
"amount": "49.90",
"currency": "USD",
"asset": "DOGE",
"network": "dogecoin-mainnet",
"rate": "0.275",
"amount_crypto": "181.45",
"received_crypto": "181.45",
"remaining_crypto": "0",
"overpaid_crypto": "0",
"expires_at": "2026-09-17T12:22:00Z",
"metadata": {
"cart": "a17"
}
},
"payment": {
"txid": "c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0",
"output": 1,
"asset": "DOGE",
"network": "dogecoin-mainnet",
"amount_crypto": "181.45",
"confirmations": 6,
"seen_at": "2026-09-17T11:59:02Z"
},
"settlement": {
"mode": "direct",
"fee_percent": "0.00"
}
}| Поле | Описание |
|---|---|
test | true — счёт создан в режиме «Тест». |
validation, warnings | Итог проверки платежа, только в payment.received. |
invoice.number | Номер счёта на странице оплаты. |
invoice.status | Статус счёта после события. |
invoice.amount_crypto, received_crypto, remaining_crypto, overpaid_crypto | Сумма счёта в монете, получено, осталось доплатить, переплата. |
invoice.rate | Курс монеты к валюте счёта на момент выбора монеты. |
invoice.metadata | metadata из формы как есть. |
payment.output | Номер выхода: у UTXO — выход транзакции на адрес счёта (их в одной транзакции может быть несколько); у EVM — 0 для прямого перевода монеты, 1 и дальше — переводы монеты из контракта в той же транзакции, у токена — 1000000 плюс номер записи журнала в блоке (logIndex). У EVM txid — с 0x. У TRON — так же, но внутренние переводы монеты нумеруются с 1. У Solana — номер перевода в транзакции, у токена — 1000000 плюс он; txid — подпись транзакции. У XRP — 0, txid — хеш транзакции; счёт узнаётся по тегу назначения. У TON — 0, txid — хеш транзакции адреса проекта; счёт узнаётся по комментарию к переводу. У жетона TON — 1000000 плюс номер события в транзакции, txid — хеш транзакции кошелька жетона. У токена XRPL — 1000000, txid — хеш транзакции; счёт — тоже по тегу назначения. |
payment.seen_at | Когда перевод появился в сети — по этому времени проверяется срок. |
settlement | Куда идут деньги: direct — сразу на ваш кошелёк, pool — через котёл с комиссией fee_percent. |
validation и warnings
Отгружайте заказ только при validation: true. При validation: false деньги всё равно зачислены магазину — решите сами: отгрузить, вернуть разницу или связаться с покупателем.
| Предупреждение в warnings | Что значит |
|---|---|
incorrect amount | Сумма вне допуска: недоплата, переплата или доплата на уже закрытый счёт. |
incorrect network or token | Пришла не та монета или сеть. |
incorrect time | Перевод появился в сети после срока счёта. |
| Ситуация | Статус | validation | warnings |
|---|---|---|---|
| Сумма в допуске, в срок, та монета | paid | true | — |
| Недоплата, срок идёт | partial | false | incorrect amount |
| Доплатил в срок | paid | true | — |
| Недоплата, срок доплаты вышел | received | false | incorrect amount |
| Переплата сверх допуска | received | false | incorrect amount |
| Та же монета в другой сети, ровно на сумму | received | false | incorrect network or token |
| Перевод после срока, ровно на сумму | received | false | incorrect time |
| Другая монета, нулевой перевод, любой перевод на оплаченный счёт, после срока — не ровно на сумму | не меняется, уведомления нет | — | — |
Несколько переводов на один счёт суммируются; допуск суммы задаётся во вкладке «Страница оплаты».
Переводы, которых счёт не ждёт, платежами не считаются: уведомления нет и статус не меняется, при зачислении через котёл такие деньги магазину не выплачиваются. Так на адреса счетов приходит спам «отравления адреса». Если покупатель ошибся монетой или заплатил дважды, обратитесь в поддержку CVC с номером счёта и транзакцией.
Тестовый режим и имитация
В режиме «Тест» счета помечены test: true, а страница оплаты принимает те же монеты, что и в работе. Внизу страницы тестового счёта есть «Имитация оплаты»: оплатить точно, часть или с переплатой, истечь срок, откатить блок и вернуть его — без перевода. Счёт проходит тот же путь, что при настоящей оплате, и магазин получает те же уведомления с test: true.
Подтверждение домена
Для режима «Работает» подтвердите, что сайт ваш: положите в корень сайта файл cvc-sci.txt со строкой из вкладки «Подключение» и нажмите «Проверить». Если домен ещё не смотрит на ваш сервер, укажите IP сервера — проверка придёт на него.
Свой домен
На тарифах со своим доменом покупатель платит на вашем адресе, например pay.example.com, и адрес CoinVertus не видит. Перед страницей оплаты стоит ваш сервер (фасад): он держит ваш сертификат и проксирует страницу оплаты на sci.coinvertus.net. Сервер и сертификат — ваши.
- Направьте домен на свой сервер и выпустите для него сертификат.
- Настройте nginx по образцу ниже: файл /cvc-sci.txt, свои стили и иконку сервер отдаёт сам, пути страницы оплаты проксирует на sci.coinvertus.net, на остальное отвечает 404.
- Во вкладке «Подключение» проекта укажите домен и IP своих серверов, с которых они обращаются к sci.coinvertus.net. Смена — с кодом подтверждения.
- Отдавайте в /cvc-sci.txt ту же строку, что и для сайта проекта, и нажмите «Проверить».
- После подтверждения ссылки на новые счета — из формы и из API — ведут на ваш домен. Форму оплаты можно отправлять на корень своего домена. Если сайт магазина задаёт CSP form-action, добавьте в неё свой домен оплаты: после формы покупатель переходит на него.
Пример nginx для вашего сервера:
server {
listen 443 ssl;
server_name pay.example.com;
ssl_certificate /etc/ssl/pay.example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/pay.example.com/privkey.pem;
location = /cvc-sci.txt {
default_type text/plain;
return 200 "cvc-sci=P4K7Q2A9-0123456789abcdef0123456789abcdef\n";
}
location = /cvc-sci.css {
root /srv/pay;
}
location = /favicon.ico {
root /srv/pay;
}
location ~ "^/(?:$|[A-Za-z0-9_-]{22}$|sci/|api/v1/|language$|css/|js/|fonts/|webfonts/|images/|coin-icon/)" {
proxy_pass https://sci.coinvertus.net;
proxy_set_header Host sci.coinvertus.net;
proxy_ssl_server_name on;
proxy_ssl_name sci.coinvertus.net;
proxy_redirect https://sci.coinvertus.net/ /;
}
location / {
return 404;
}
}Оформление страницы
На вашем домене страница оплаты после наших стилей подключает /cvc-sci.css — этот файл отдаёт ваш сервер, пустой файл оставляет вид как есть. Логотип, иконка и название CoinVertus на вашем домене не показываются. Опирайтесь на классы из таблицы: остальная разметка может меняться.
| Селектор | Описание |
|---|---|
.cvc-sci-own | Вся страница на вашем домене: фон, шрифт, основной цвет — переменная --cvc-color-primary. |
.cvc-sci-bar | Верхняя полоса с выбором языка. |
.cvc-sci-brand | Место под ваш логотип слева в полосе: пустой блок, задайте ему размер и фон. |
.cvc-sci-column | Колонка по центру: ширина полосы и карточки. |
.cvc-sci-card | Карточка счёта. |
.cvc-sci-shop-avatar | Первая буква названия магазина в шапке счёта. |
.cvc-sci-coin-box .cvc-sci-asset-icon | Выбранная монета и иконки монет и сетей. |
.cvc-sci-copy-value .cvc-sci-address | Сумма, адрес и другие реквизиты, которые покупатель копирует. |
.cvc-sci-qr | QR-код для оплаты. |
.btn-login | Главные кнопки: «Оплатить из кошелька», «Продолжить». |
.cvc-sci-action | Второстепенные кнопки: чек, письмо магазину. |
.cvc-sci-waiting .cvc-sci-found .cvc-sci-outcome-icon | Ожидание оплаты, найденный платёж и значок исхода. |
.cvc-sci-progress | Полоса подтверждений и доплаты. |
.cvc-sci-rows | Строки сведений о платеже на экране исхода. |
Пример /cvc-sci.css:
.cvc-sci-own {
--cvc-color-primary: #1d4ed8;
background: #f4f1ea;
}
.cvc-sci-brand {
width: 140px;
height: 40px;
background: url(/logo.svg) no-repeat left center / contain;
}
.cvc-sci-card {
border-radius: 4px;
}Политика безопасности страницы пускает стили только из файлов вашего домена, картинки и шрифты — с вашего домена или data:. Внешние CDN не подключатся.
Пример на PHP
Подпись и форма:
<?php
$shop = 'P4K7Q2A9';
$formKey = getenv('CVC_SCI_FORM_KEY');
$orderId = '10482';
$amount = '49.90';
$currency = 'USD';
$sign = hash_hmac('sha256', $shop . ':' . $orderId . ':' . $amount . ':' . $currency, $formKey);
?>
<form method="post" action="https://sci.coinvertus.net">
<input type="hidden" name="shop" value="P4K7Q2A9">
<input type="hidden" name="order_id" value="10482">
<input type="hidden" name="amount" value="49.90">
<input type="hidden" name="currency" value="USD">
<input type="hidden" name="sign" value="<?= $sign ?>">
<input type="hidden" name="success_url" value="https://shop.example/orders/10482">
<button type="submit">Pay with crypto</button>
</form>Приём уведомления:
<?php
$secret = getenv('CVC_SCI_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
$t = null;
$signatures = [];
foreach (explode(',', $_SERVER['HTTP_X_CVC_SIGNATURE'] ?? '') as $pair) {
[$name, $value] = array_pad(explode('=', trim($pair), 2), 2, '');
if ($name === 't') {
$t = $value;
} elseif ($name === 'v1') {
$signatures[] = strtolower($value);
}
}
$expected = hash_hmac('sha256', $t . '.' . $body, $secret);
$valid = $t !== null && ctype_digit($t) && abs(time() - (int) $t) <= 300
&& array_filter($signatures, fn (string $v1): bool => hash_equals($expected, $v1)) !== [];
if (!$valid) {
http_response_code(400);
exit;
}
$hook = json_decode($body, true);
if (($hook['test'] && $liveMode) || !$events->remember($hook['event_id'])) {
http_response_code(200);
exit;
}
$orderId = $hook['invoice']['order_id'];
switch ($hook['event']) {
case 'payment.received':
$hook['validation']
? $orders->markPaid($orderId)
: $orders->markForReview($orderId, $hook['warnings']);
break;
case 'invoice.expired':
$hook['invoice']['status'] === 'expired'
? $orders->cancel($orderId)
: $orders->markForReview($orderId, ['incorrect amount']);
break;
case 'payment.reversed':
$orders->markForReview($orderId, ['reversed']);
break;
}
http_response_code(200);$orders, $events и $liveMode — ваши хранилище заказов, журнал обработанных event_id и признак режима «Работает». Отвечайте 200, только когда уведомление обработано.