Guide to Utila Webhooks: Features and Best Practices
Utila webhooks notify your systems about digital-asset activity: a transaction is created or changes state, a wallet or wallet address is created, an AML screening result is ready. If you're building on Utila's custody/wallet platform, webhooks are how you react to on-chain and account events without polling.
This guide covers how Utila webhooks work, the events you'll handle, how to verify the RSA signature (asymmetric crypto, not HMAC), the thin payloads, and the best practices for production.
What are Utila webhooks?
Utila webhooks are HTTP POSTs delivered to endpoints you configure in the Console. Each is signed with an asymmetric RSA signature, not an HMAC. The x-utila-signature header carries an RSA-4096 signature computed with SHA-512 and PSS padding, base64-encoded, which you verify with Utila's PEM-encoded RSA public key. There's no shared secret and no timestamp header (so no built-in replay protection).
Utila webhook features
| Feature | Details |
|---|---|
| Configuration | Console > Vault Settings > Webhooks |
| Signature header | x-utila-signature (base64) |
| Signature scheme | RSA-4096, SHA-512, PSS padding, over the raw body |
| Verification key | Utila's PEM RSA-4096 public key (from the Console); no shared secret |
| Replay protection | None (no timestamp header) |
| Payload | Thin, fetch full detail via the Stream/API |
| Retries | Exponential backoff up to 24h, then discarded; return HTTP 200 |
| SDK | @utila/api is documented but does not currently resolve on the public registry |
Common events
Utila has exactly five events, in SCREAMING_SNAKE_CASE:
| Event | Fires when |
|---|---|
TRANSACTION_CREATED | A transaction is created |
TRANSACTION_STATE_UPDATED | A transaction changes state |
WALLET_CREATED | A wallet is created |
WALLET_ADDRESS_CREATED | A wallet address is created |
TRANSACTION_AML_SCREENING_RESULT_READY | An AML screening result is ready |
Payloads are thin, use the identifiers to fetch full detail via the Utila Stream/API.
Setting up Utila webhooks
Create webhooks in the Console under Vault Settings > Webhooks, and copy Utila's PEM-encoded RSA-4096 public key from the webhook settings, that's what you verify with. There's no shared secret to store.
Securing Utila webhooks
The x-utila-signature header is a base64 RSA signature over the raw body, computed with RSA-4096, SHA-512, and PSS padding. Verify it with Utila's public key.
const crypto = require("crypto");
const PUBLIC_KEY = process.env.UTILA_PUBLIC_KEY; // PEM RSA-4096 from the Console
function verify(rawBody, signatureB64) {
return crypto.verify(
"sha512",
rawBody, // raw body bytes
{
key: PUBLIC_KEY,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST,
},
Buffer.from(signatureB64 || "", "base64")
);
}
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
if (!verify(req.body, req.headers["x-utila-signature"])) {
return res.sendStatus(401);
}
res.sendStatus(200); // acknowledge fast
processQueue.add(JSON.parse(req.body)); // fetch full detail via API, async
});
The same check in Python (using pycryptodome):
import base64
import os
from Crypto.PublicKey import RSA
from Crypto.Signature import pss
from Crypto.Hash import SHA512
PUBLIC_KEY = RSA.import_key(os.environ["UTILA_PUBLIC_KEY"])
def verify(raw_body: bytes, signature_b64: str) -> bool:
signature = base64.b64decode(signature_b64)
h = SHA512.new(raw_body)
try:
pss.new(PUBLIC_KEY).verify(h, signature)
return True
except (ValueError, TypeError):
return False
Utila webhook limitations and pain points
RSA-PSS with SHA-512, not HMAC
The Problem: Verification is asymmetric (RSA-4096, SHA-512, PSS padding) against Utila's public key. There's no shared secret, so HMAC logic doesn't apply, and PSS padding must be set explicitly (the default RSA padding won't verify).
Why It Happens: Utila signs with an RSA private key and PSS padding.
Workarounds:
- Verify with the public key, SHA-512, and PSS padding (salt length = digest length), over the raw body.
How Hookdeck Can Help: Hookdeck verifies the RSA-PSS signature at the edge, so your application receives pre-verified events without embedding asymmetric crypto and padding config in your handler.
No timestamp, so no replay window
The Problem: There's no timestamp header, so a captured, validly signed payload can be replayed and it will still verify.
Why It Happens: Utila's scheme signs the body without a timestamp.
Workarounds:
- Make processing idempotent and dedupe on a transaction/event identifier.
How Hookdeck Can Help: Hookdeck deduplicates deliveries at the edge, filtering replays. See our guide to webhook idempotency.
Thin payloads
The Problem: Payloads are minimal, so you fetch full detail from the Stream/API for anything useful.
Why It Happens: Utila keeps payloads small.
Workarounds:
- Acknowledge fast, then fetch detail via the API off a queue.
How Hookdeck Can Help: Hookdeck durably queues each event so your worker fetches detail at a controlled rate.
Failure means discard
The Problem: Failed deliveries retry with exponential backoff up to 24 hours, then are discarded. A longer outage loses events.
Why It Happens: Utila bounds retries at 24 hours.
Workarounds:
- Keep the endpoint healthy and reconcile via the API for anything critical.
How Hookdeck Can Help: Hookdeck accepts and durably stores deliveries, retrying to your downstream on a schedule you control, so a 24-hour ceiling isn't your only safety net.
Best practices
Verify RSA-PSS/SHA-512 with the public key
Verify the base64 x-utila-signature with Utila's public key, SHA-512, and PSS padding over the raw body.
Dedupe (there's no replay window)
Since there's no timestamp, dedupe on a transaction/event id so replays are no-ops.
Acknowledge fast, fetch via the API
Return 200 immediately and fetch full detail off a queue. See why to process webhooks asynchronously.
Reconcile for critical events
Reconcile via the API since deliveries are discarded after 24 hours of failure.
Make Utila webhooks production-ready
Hookdeck verifies the RSA-PSS signature, deduplicates, and durably queues every transaction and wallet event
Conclusion
Utila webhooks are signed with RSA-4096, SHA-512, and PSS padding, verified against Utila's public key over the raw body, with no shared secret and no timestamp (so no replay window). Set PSS padding explicitly, dedupe to guard against replays, acknowledge fast, and reconcile via the API for critical events.
Hookdeck verifies the RSA-PSS signature, deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique events.
Get started with Hookdeck for free and handle Utila webhooks reliably in minutes.