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_. 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_ordeposit.completed orchestration.completedevents? - 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(defaultAuthorization) and look it up case-insensitively. PAXOS_WEBHOOK_SECRETis the exact, whole header value, including anyBearerprefix you typed. Compare the whole value; don't strip or parse a prefix.- In Python use
hmac.compare_(never passdigest(a.encode(), b.encode()) str, which raises on non-ASCII). - Return
401for a missing or mismatched header,400for a body that isn't a JSON event,200once 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:
| Event | Sent When |
|---|---|
identity.approved | An identity verification is approved |
identity.denied | An identity verification is denied |
identity.documents_ | Additional documents are required |
identity.documents_ / identity.documents_ | An Enhanced Due Diligence review is accepted / denied |
transfer.crypto_ | A crypto deposit completes |
transfer.wire_ | A wire withdrawal fails |
orchestration.completed | A stablecoin orchestration completes |
Transfers follow transfer.<method>_ (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_. 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
- references/overview.md - What Paxos webhooks are, events and payload shape
- references/setup.md - Create the webhook in Dashboard with API Key auth
- references/verification.md - Header check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests