Webhooks
Webhooks deliver near-real-time notifications for events on your profile. Your
server registers an HTTPS endpoint and a set of event types; The Vault delivers
signed JSON payloads to it as POST requests.
Setup
Register webhooks in the dashboard (Settings → Webhooks): the URL, the
event types to subscribe to, and an optional description. You can register
multiple webhooks per profile — for example to split high-volume events
(BALANCE_UPDATED, TRANSACTION_DETECTED) from low-volume administrative ones.
The API provides read access for monitoring:
GET /v1/profiles/{profile_id}/webhooks
GET /v1/profiles/{profile_id}/webhooks/{webhook_id}
GET /v1/profiles/{profile_id}/webhooks/{webhook_id}/notifications
GET /v1/profiles/{profile_id}/webhooks/{webhook_id}/notifications/{notification_id}The notifications endpoints list delivery attempts with their status
(PENDING, DELIVERED, FAILED) — use them to debug missed deliveries.
Receiving events
Each delivery is a POST with these headers:
| Header | Description |
|---|---|
Content-Type | Always application/json |
X-Webhook-Signature | Detached JWS (ES256) over timestamp + payload — see Verification |
X-Webhook-Timestamp | Unix timestamp (seconds) used during signing |
X-Webhook-Event-Type | Event type, e.g. TRANSFER_CREATED |
X-Webhook-Event-ID | Unique per-event UUID — use as your deduplication key |
Body structure:
{
"event_id": "3b80e869-f64b-404f-9759-3a3b2efc7f03",
"event_type": "TRANSFER_CREATED",
"profile_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-07-20T12:00:00Z",
"data": { }
}data is event-type-specific. Respond 2xx within 30 seconds; anything
else counts as a failed delivery. Acknowledge as soon as you’ve verified the
signature and persisted the event — process asynchronously.
Retries
Delivery is at-least-once — deduplicate on X-Webhook-Event-ID. Failed
deliveries are retried with backoff, up to 5 attempts total:
| Attempt | Delay after previous |
|---|---|
| 1 | immediate |
| 2 | 30 s |
| 3 | 2 min |
| 4 | 10 min |
| 5 | 30 min |
After the 5th failure the delivery is marked FAILED and not retried
automatically (it remains inspectable via the notifications endpoints).
Verifying signatures
Every delivery is signed with ECDSA P-256 / SHA-256 (ES256) as a
detached JWS (RFC 7515 §A.5). The signature covers
"<X-Webhook-Timestamp>.<raw body>", binding payload and timestamp together.
Public keys are published as a JWKS with automatic rotation:
GET https://<your-vault-domain>/.well-known/jwks.jsonVerification steps:
- Read the raw request body bytes — do not parse and re-serialize first.
- Reject if
|now − X-Webhook-Timestamp|exceeds your tolerance (≤ 5 minutes recommended). - Split
X-Webhook-Signatureon..into<headerB64>and<signatureB64>; the decoded header carriesalg(ES256) andkid. - Look up the key by
kidin your cached JWKS (respectCache-Control; on unknownkid, refresh the cache and retry — old and new keys overlap during rotation). - Rebuild the signing input
headerB64 + "." + base64url(timestamp + "." + rawBody)and verify with any JOSE library (josefor Node,jwcryptofor Python,lestrrat-go/jwxfor Go). Signature bytes are rawR‖S(64 bytes), unpadded base64url — standard JWS ES256.
Reject any request whose signature does not verify, and treat unknown event types gracefully (log and ignore) — new event types are added over time and must not break your integration.
Event types
| Category | Events |
|---|---|
| Transfer lifecycle | TRANSFER_CREATED, TRANSFER_UPDATED, TRANSFER_DELIVERED, TRANSFER_FAILED |
| Balances & transactions | BALANCE_UPDATED, TRANSACTION_DETECTED |
| Wallets | WALLET_CREATED, WALLET_UPDATED, WALLET_DELETED |
| Policies | POLICY_CREATED, POLICY_UPDATED, POLICY_DELETED |
| Addresses | ADDRESS_CREATED, ADDRESS_UPDATED, ADDRESS_DELETED |
| Wallet groups | WALLET_GROUP_CREATED, WALLET_GROUP_UPDATED, WALLET_GROUP_DELETED, WALLET_GROUP_USER_ASSIGNED |
| Address groups | ADDRESS_GROUP_CREATED, ADDRESS_GROUP_UPDATED, ADDRESS_GROUP_DELETED |
| Servers & keys | SERVER_CREATED, SERVER_DELETED, REFRESH_KEY_SHARES |
| Devices | DEVICE_CREATED, DEVICE_STATUS_CHANGED, DEVICE_REMOVED, CERTIFICATE_READY |
| Users | USER_CREATED, USER_ROLE_UPDATED, USER_BLOCKED, USER_UNBLOCKED, USER_PASSWORD_RESET_INITIATED |
| Security | POLICY_SIGNATURE_VERIFICATION_FAILED, ROLE_SCOPE_LOGIN, GEO_ANOMALY_DETECTED |
TRANSACTION_DETECTED fires when an on-chain transaction is first seen on one
of your addresses (possibly unconfirmed); BALANCE_UPDATED fires after
confirmation when a balance actually changes.
Integration checklist
- Expose a globally reachable HTTPS endpoint; read the request body raw.
- Verify
X-Webhook-Signatureon every request; enforce the timestamp window. - Deduplicate on
X-Webhook-Event-IDwith an appropriate TTL. - Respond
2xxfast; do business logic asynchronously. - Monitor your non-2xx rate — repeated failures exhaust the retry schedule.