Agent skill

Paxos Webhooks Skill

Receive and authenticate Paxos webhooks. Use when setting up a Paxos webhook consumer, configuring the API Key auth type, debugging 401s, or handling thin events like identity.approved, transfer.crypto_deposit.completed and orchestration.completed.

Install this skill

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


Paxos sends a JSON webhook to your HTTPS endpoint when something happens on the platform: an identity is approved, a stablecoin transfer completes, an orchestration (mint, convert, redeem) finishes. Webhooks are not signed (Paxos says signature verification is planned). Instead you set Auth Type: API Key on the webhook in Dashboard, choose a header name and value, and Paxos sends that header on every delivery. Your endpoint authenticates the request by comparing it with the value you stored.

Events are thin: {id, type, source, time, object, is_test}. Fetch the full object with the Get Event API using the event id.

When to Use This Skill

  • How do I receive Paxos webhooks?
  • Does Paxos sign webhook payloads? (No: use the API Key header you configure)
  • How do I authenticate requests from a Paxos webhook consumer?
  • How do I handle identity.approved, transfer.crypto_deposit.completed or orchestration.completed events?
  • Why does my Paxos webhook endpoint return 401?

Verification (core)

const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyPaxosRequest(headers, secret = process.env.PAXOS_WEBHOOK_SECRET,
                            headerName = process.env.PAXOS_WEBHOOK_HEADER || 'Authorization') {
  if (!secret) return false;                      // never accept when unconfigured
  const received = headers[headerName.toLowerCase()]; // Node lowercases header names
  return typeof received === 'string' && received !== '' && safeEqual(received, secret);
}
  • The header name is whatever you typed as API Key Header in Dashboard; the examples read it from PAXOS_WEBHOOK_HEADER (default Authorization) and look it up case-insensitively.
  • PAXOS_WEBHOOK_SECRET is the exact, whole header value, including any Bearer prefix you typed. Compare the whole value; don't strip or parse a prefix.
  • In Python use hmac.compare_digest(a.encode(), b.encode()) (never pass str, which raises on non-ASCII).
  • Return 401 for a missing or mismatched header, 400 for a body that isn't a JSON event, 200 once handled.
  • Read the body raw (express.raw({ type: '*/*' })) and authenticate before parsing it.
  • Security note: a static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS only and rotate the credential if it leaks. Dedupe on the event id.

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

Event Types

Webhook type values come from the Paxos Webhooks API Reference. Common ones:

EventSent When
identity.approvedAn identity verification is approved
identity.deniedAn identity verification is denied
identity.documents_requiredAdditional documents are required
identity.documents_accepted / identity.documents_deniedAn Enhanced Due Diligence review is accepted / denied
transfer.crypto_deposit.completedA crypto deposit completes
transfer.wire_withdrawal.failedA wire withdrawal fails
orchestration.completedA stablecoin orchestration completes

Transfers follow transfer.<method>_<deposit|withdrawal>.<pending|completed|failed> (methods include act, wire, rtp, crypto, cubix, bny_intrabank, scb_intrabank, banking_circle_intrabank). Orchestrations are orchestration.<processing|pending|rejected|completed|failed> plus orchestration.status_details.*. See references/overview.md.

Events sent from the Dashboard Test feature have is_test: true; ignore them.

Environment Variables

PAXOS_WEBHOOK_HEADER=X-Paxos-Webhook-Secret   # API Key Header name set in Dashboard (default: Authorization)
PAXOS_WEBHOOK_SECRET=your-api-key-value       # exact API Key value, including any "Bearer " prefix

Local Development

Paxos can't reach localhost (and requires an HTTPS URL). Tunnel with the Hookdeck CLI (no account required):

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

Use the printed URL as the webhook URL in Dashboard.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 4, 2026

View on GitHub →