События вебхука
Как проверять и обрабатывать доставки, с примерами кода, — в гайде Вебхуки.
Каждое событие приходит на адрес вебхука запросом POST с телом в JSON. События записываются, только пока
вебхук задан.
- Доставка «хотя бы один раз»: обрабатывайте каждый
idсобытия один раз. Порядок не гарантирован: опирайтесь на статус вdata, а не на порядок событий. - Проверяйте каждую доставку:
v1вPortuna-Signature— HMAC-SHA256 в hex строки<t>.<body>с секретом вебхука в качестве ключа, где<body>— тело запроса как есть. Принимайтеt, только если он расходится с вашими часами не больше чем на 5 минут. Каждая попытка подписывается заново. - Отвечайте любым 2xx в пределах 10 секунд. Любой другой ответ, в том числе редирект, или отсутствие ответа — неудачная попытка: событие отправляется снова через 10 с, 20 с, 40 с и так далее, с паузой до часа, всего 12 попыток.
- Могут появиться новые типы событий: принимайте и пропускайте те, которые не обрабатываете.
Заголовки
Заголовок раздела «Заголовки»| Имя | Тип | Описание |
|---|---|---|
Portuna-Signature обязательно | string | t=<unix seconds>,v1=<signature> — время этой попытки и HMAC-SHA256 строки <t>.<body> с секретом вебхука, в hex. Пример: t=1791634982,v1=6e735f32a0… |
Portuna-Event обязательно | string | Тип события, как type в теле. Пример: sweep.swept |
Portuna-Delivery обязательно | string (uuid) | id события, как в теле. Во всех попытках один и тот же. |
User-Agent | string | Называет отправителя. Проверяйте доставки по подписи, а не по этому заголовку. Пример: portuna-webhooks/1 |
Тело — объект Event.
Тело доставки вебхука. type говорит, что произошло и что лежит в data: задание на sweep, как в
GET /v1/sweeps/{id}; снятие делегации, как в GET /v1/revocations/{id}; горячий кошелёк, как в
GET /v1/hot-wallets; алерт о низком балансе или тестовое событие. Задания, снятия и кошельки показываются
такими, какие они в момент доставки.
| Поле | Тип | Описание |
|---|---|---|
id обязательно | string (uuid) | id события, во всех попытках один и тот же. |
type обязательно | string | Тип события. Одно из: sweep.swept, sweep.failed, sweep.rejected, revocation.revoked, revocation.failed, revocation.rejected, hot_wallet.active, hot_wallet.failed, balance.low, webhook.test. |
created_at обязательно | string (date-time) | Когда произошло событие. |
data обязательно | Sweep | Revocation | HotWallet | BalanceLow | WebhookTest | Зависит от type, см. ниже. |
Типы событий
Заголовок раздела «Типы событий»| Тип | data | Описание |
|---|---|---|
sweep.sweptsweep.failedsweep.rejected | Sweep | Задание на sweep закончилось. sweep.swept — средства переведены; sweep.failed — откатилось в сети или кончились попытки; sweep.rejected — так и не отправлено. |
revocation.revokedrevocation.failedrevocation.rejected | Revocation | Снятие делегации закончилось. revocation.revoked — делегация снята; revocation.failed — сеть пропустила авторизацию или кончились попытки; revocation.rejected — так и не отправлено. |
hot_wallet.activehot_wallet.failed | HotWallet | Развёртывание делегата горячего кошелька закончилось. hot_wallet.active — делегат развёрнут; hot_wallet.failed — развернуть не удалось. |
balance.low | BalanceLow | Баланс газ-кошелька опустился ниже порога. По событию на каждое падение ниже порога; алерт снова взводится, когда баланс восстановится. |
webhook.test | WebhookTest | Тестовое событие, запрошенное через POST /v1/webhook/test. |
Пример события
Заголовок раздела «Пример события»Content-Type: application/jsonUser-Agent: portuna-webhooks/1Portuna-Event: sweep.sweptPortuna-Delivery: 9c41e7d2-58a3-4b0f-a6d1-2e7f3c9b8a05Portuna-Signature: t=1791634982,v1=<hex HMAC-SHA256>{ "id": "9c41e7d2-58a3-4b0f-a6d1-2e7f3c9b8a05", "type": "sweep.swept", "created_at": "2026-10-11T07:03:02Z", "data": { "id": "8a6e2f31-0c4b-4d7a-b1e5-92f3c6d8a047", "external_id": "dep-80c2d4", "chain_id": 11155111, "hot_wallet": "0x2Cd50979a8A33e8CA37DA85F8517fb148a67b769", "account": "0x2c7536E3605D9C16a7a3D7b1898e529396a65c23", "token": "USDT", "token_address": "0x5B74f75040584b133Ff1EB67eEe646B0da26e39C", "status": "swept", "attempts": 1, "swept_amount": "250000000", "tx_hash": "0xe2a3d8ffdcd7c4ed8a2389a59ce91622863c495d052a7571206bb494919dc35b", "kind": "repeat", "gas": 18078, "gas_cost": "21693600000000", "fee": "23358000000000", "classic_gas": 53633, "rate_bps": 7000, "created_at": "2026-10-11T07:02:15Z", "updated_at": "2026-10-11T07:03:02Z" }}