Вебхуки
Вебхук — HTTP-запрос от Invoise на ваш сервер при событии входящего перевода, инвойса или пейаута. Используйте GET состояния инвойса или депозита для проверки его текущего состояния.
1. Зарегистрируйте адрес
Заголовок раздела «1. Зарегистрируйте адрес»curl -X POST 'https://platform.invoise.me/api/v1/shops/{shop_id}/webhooks' \ -H 'Authorization: Bearer ivk_...' \ -H 'Idempotency-Key: <saved-unique-key>' \ -H 'Content-Type: application/json' \ -d '{"url":"https://example.com/invoise","filters":["transfer","invoice","payout"]}'Укажите свой публичный HTTPS-адрес. Локальные и приватные адреса не принимаются. Сохраните secret из ответа на создание — им проверяется подпись запросов.
У магазина может быть до 10 адресов. URL существующего адреса можно изменить, поэтому используйте его вместо нового. См. Лимиты.
2. Выберите события
Заголовок раздела «2. Выберите события»| Событие | Что означает |
|---|---|
transfer.observed |
Перевод замечен, но ещё не подтверждён. |
transfer.confirmed |
Входящий перевод получил достаточно подтверждений. Это может быть лишь частичная оплата инвойса. |
transfer.reverted |
Предыдущее событие перевода отменено. |
invoice.closed |
Инвойс закрыт в блокчейне. |
invoice.cancelled |
Checkout отменён или срок инвойса истёк; это не возврат денег. |
payout.sent |
Пейаут отправлен в сеть или замечен исходящий перевод. |
payout.confirmed |
Полный учёт пейаута подтверждён. |
payout.reverted |
Предыдущее подтверждение пейаута отменено. |
payout.failed |
Окончательная ошибка пейаута требует вмешательства. |
У invoice.cancelled поле data.reason объясняет причину: expired — истёк срок действия инвойса, merchant_blocked — сотрудники Invoise заблокировали мерчанта. При отмене самим мерчантом reason нет.
Подписывайтесь по группам: transfer, invoice, payout. Входящая оплата и исходящий пейаут — разные события. Временная ошибка RPC или ожидание газа не означают payout.failed.
gas.wait и sweep.failed принимаются как фильтры, но Invoise не отправляет их на ваш адрес. Это внутренние сигналы: gas.wait — пейаут ждёт средств на комиссию сети и продолжится сам; sweep.failed — попытка перевести средства получателю не удалась. Ни то, ни другое не требует от вас действий. Пейаут, который не может завершиться, придёт как payout.failed.
В sandbox-магазине оба сигнала можно симулировать через POST /shops/{shop_id}/sandbox/simulate, чтобы проверить, что задержка пейаута не ломает ваш сценарий. Они меняют только состояние пейаута и не отправляют вебхук.
3. Проверьте подпись
Заголовок раздела «3. Проверьте подпись»Заголовок Invoise-Signature имеет вид t=<timestamp>,v1=<hex>. Подпись — HMAC-SHA256 строки timestamp + "." + raw_body с секретом вебхука.
Пример для Node.js также отклоняет время с расхождением более пяти минут. Часы сервера должны быть синхронизированы; допустимое расхождение выбирайте осознанно.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(rawBody, header, secret) { const match = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(header ?? ''); if (!match) return false; const [, timestamp, signature] = match; const age = Math.abs(Date.now() / 1000 - Number(timestamp)); if (!Number.isFinite(age) || age > 300) return false; const expected = createHmac('sha256', secret) .update(timestamp + '.') .update(rawBody) .digest(); return timingSafeEqual(Buffer.from(signature, 'hex'), expected);}В rawBody передавайте исходные байты запроса. Нельзя сначала разобрать JSON, собрать его заново и проверять подпись уже над ним. Неверную подпись отклоняйте до сохранения и обработки содержимого.
4. Обработайте событие один раз
Заголовок раздела «4. Обработайте событие один раз»Сокращённый пример:
{ "id": "<event-id>", "type": "transfer.confirmed", "created_at": "2026-09-19T10:00:00Z", "data": { "issuance_id": "<issuance-id>", "shop_id": "<shop-id>", "external_id": "order-123", "type": "invoice", "invoice_cancelled": false }}| Ситуация | Обработка |
|---|---|
| Дубли | Дубли определяйте по Invoise-Event-ID, совпадающему с id конверта. Сохраняйте ID и изменение бизнес-данных в одной транзакции. |
| Приём события | Надёжно сохраните принятое событие, быстро ответьте 2xx и выполняйте остальную работу из очереди. |
| Повторная доставка | Повторы и ручная доставка сохраняют ID события. Invoise-Attempt-ID меняется и не подходит для защиты от дублей. |
| Порядок событий | Порядок прихода не гарантируется. Смотрите на содержимое и при необходимости сверяйтесь с текущим статусом. |
При отмене chain_event.reference_event_id ссылается на предыдущее событие сети. Пейауты содержат стабильный payout_id, хэш транзакции, суммы и комиссию; неизвестные суммы равны null. Список ID входящих переводов ограничен 100 записями и помечает усечение.
В старых сохранённых событиях может быть project_id: сначала читайте shop_id, а при его отсутствии — project_id. Повторная доставка сохраняет исходные байты.
Проверить доставку
Заголовок раздела «Проверить доставку»История доступна через GET /api/v1/shops/{shop_id}/deliveries. Повторить доставку можно через POST /api/v1/shops/{shop_id}/deliveries/{id}/replay с сохранённым ключом идемпотентности. Нового бизнес-события от этого не появляется.