Webhook events
How to verify and handle deliveries, with code: Webhooks.
Each event goes to your webhook URL as a POST with a JSON body. Events are recorded only while a webhook is
set.
- Delivery is at least once: handle each event
idonce. Order is not guaranteed: rely on the status indata, not on the order of events. - Verify every delivery:
v1inPortuna-Signatureis the HMAC-SHA256, in hex, of the string<t>.<body>with your webhook secret as the key, where<body>is the raw request body. Accepttonly within 5 minutes of your clock. Each attempt is signed anew. - Answer with any 2xx within 10 seconds. Any other answer, a redirect included, or no answer in time is a failed attempt: the event is tried again after 10 s, 20 s, 40 s and so on, up to an hour between attempts, 12 attempts in all.
- New event types may come: accept and ignore the ones you do not handle.
Headers
Section titled “Headers”| Name | Type | Description |
|---|---|---|
Portuna-Signature required | string | t=<unix seconds>,v1=<signature>: the time of this attempt and the HMAC-SHA256 of <t>.<body> with your webhook secret, in hex. Example: t=1791634982,v1=6e735f32a0… |
Portuna-Event required | string | The event type, as in type in the body. Example: sweep.swept |
Portuna-Delivery required | string (uuid) | The event’s id, as in the body. It is the same in every attempt. |
User-Agent | string | Names the sender. Verify deliveries by their signature, not by this header. Example: portuna-webhooks/1 |
The body is an Event object.
The body of a webhook delivery. type tells what happened and what data is: a sweep as in
GET /v1/sweeps/{id}, a revocation as in GET /v1/revocations/{id}, a hot wallet as in GET /v1/hot-wallets,
a low balance alert or a test event. Sweeps, revocations and hot wallets are shown as they are when the event
is delivered.
| Field | Type | Description |
|---|---|---|
id required | string (uuid) | The event’s id, the same in every attempt. |
type required | string | The event type. One of: sweep.swept, sweep.failed, sweep.rejected, revocation.revoked, revocation.failed, revocation.rejected, hot_wallet.active, hot_wallet.failed, balance.low, webhook.test. |
created_at required | string (date-time) | When the event happened. |
data required | Sweep | Revocation | HotWallet | BalanceLow | WebhookTest | Depends on type: see below. |
Event types
Section titled “Event types”| Type | data | Description |
|---|---|---|
sweep.sweptsweep.failedsweep.rejected | Sweep | A sweep ended. sweep.swept: it moved the funds; sweep.failed: it reverted on chain or ran out of attempts; sweep.rejected: it was never sent. |
revocation.revokedrevocation.failedrevocation.rejected | Revocation | A revocation ended. revocation.revoked: the delegation is removed; revocation.failed: the chain skipped the authorization, or the attempts ran out; revocation.rejected: it was never sent. |
hot_wallet.activehot_wallet.failed | HotWallet | A hot wallet’s delegate deployment ended. hot_wallet.active: the delegate is deployed; hot_wallet.failed: the deployment failed. |
balance.low | BalanceLow | Your gas wallet fell below a low balance threshold. One event each time it falls below; the alert re-arms once the balance recovers. |
webhook.test | WebhookTest | The test event you asked for with POST /v1/webhook/test. |
Example payload
Section titled “Example payload”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" }}