Agent skill

Utila Webhooks Skill

Receive and verify Utila webhooks. Use when setting up Utila webhook handlers, debugging x-utila-signature RSA/PSS verification, or handling Utila digital-asset events like TRANSACTION_CREATED, TRANSACTION_STATE_UPDATED, WALLET_CREATED, WALLET_ADDRESS_CREATED, and TRANSACTION_AML_SCREENING_RESULT_READY.

Install this skill

npx skills add hookdeck/webhook-skills --skill utila-webhooks


Utila is a digital-asset operations platform. Webhooks notify your endpoint when transactions and wallets change state so you can reconcile them against the Utila API. Verification is asymmetric RSA, not HMAC — there is no shared secret.

When to Use This Skill

  • How do I receive Utila webhooks?
  • How do I verify the x-utila-signature header?
  • Why is my Utila signature verification failing?
  • How do I handle TRANSACTION_CREATED or TRANSACTION_STATE_UPDATED events?
  • How do I configure a webhook in the Utila Console?

How Verification Works

Utila signs each delivery with an RSA-4096 private key using SHA-512 and PSS padding, base64-encodes the result, and sends it in the x-utila-signature header. You verify with Utila's PEM-encoded RSA-4096 public key, copied from the Console (Vault Settings → Webhooks). Key facts:

  • No shared secret / no HMAC. You hold only the public key.
  • Not Standard Webhooks. There is no webhook-id / webhook-timestamp header.
  • No timestamp header, so Utila provides no built-in replay protection — dedupe on the event id and treat deliveries as idempotent.
  • Always verify over the raw request body (exact bytes), before JSON parsing.

Verification (core)

const crypto = require('crypto');

// signatureB64: the raw x-utila-signature header value.
// rawBody:      the exact request bytes (Buffer), NOT parsed JSON.
// publicKeyPem: Utila's PEM RSA-4096 PUBLIC key from the Console.
function verifyUtilaSignature(rawBody, signatureB64, publicKeyPem) {
  if (!signatureB64) return false;
  try {
    return crypto.verify(
      'sha512',
      rawBody,
      {
        key: publicKeyPem,
        padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
        saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO, // auto-detect PSS salt
      },
      Buffer.from(signatureB64, 'base64')
    );
  } catch {
    return false; // malformed key/signature = not authentic
  }
}

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Common Event Types

Utila emits exactly five event types (SCREAMING_SNAKE_CASE), delivered in the payload's type field:

EventTriggered When
TRANSACTION_CREATEDA new transaction is created
TRANSACTION_STATE_UPDATEDA transaction changes state (e.g. signing, completed, failed)
WALLET_CREATEDA new wallet is created
WALLET_ADDRESS_CREATEDA new address is generated for a wallet
TRANSACTION_AML_SCREENING_RESULT_READYAn AML screening result becomes available

Payloads are thin — they carry identifiers (id, vault, type, resourceType, resource, optional details), not the full resource. Fetch the complete object from the Utila API / Stream using the resource path.

See references/overview.md for the full payload shape.

Environment Variables

VariableDescription
UTILA_WEBHOOK_PUBLIC_KEYUtila's PEM-encoded RSA-4096 public key (from the Console).

The PEM is multi-line. Store it either as a real multi-line value or with escaped \n newlines — the examples normalize \n back to real newlines.

SDK note: Utila documents an npm package @utila/api, but it does not currently resolve on the public registry, and there is no Python SDK. The examples therefore verify manually with the platform crypto libraries (Node crypto, Python cryptography).

Local Development

For local webhook testing, run the Hookdeck CLI via npx — no install required:

npx hookdeck-cli listen 3000 utila --path /webhooks/utila

No account required — the CLI creates a guest account on first run and provides a local tunnel + web UI for inspecting requests.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Aug 6, 2026

View on GitHub →