Agent skill
Bridge (bridge.xyz) Webhooks Skill
Receive and verify Bridge (bridge.xyz) webhooks. Use when setting up Bridge webhook handlers, debugging RSA signature verification of the X-Webhook-Signature header, or handling stablecoin/fiat events like customer.updated, kyc_link.updated, transfer.updated, and virtual_account.activity.
Install this skill
npx skills add hookdeck/webhook-skills --skill bridge-xyz-webhooks
Bridge is a stablecoin orchestration platform (customers, KYC links, transfers, virtual accounts, cards). It delivers webhooks signed with an RSA-SHA256 signature and verified against a per-endpoint PEM public key returned when you create/update the webhook — there is no HMAC shared secret and no official SDK.
When to Use This Skill
- How do I receive Bridge webhooks?
- How do I verify the Bridge
X-Webhook-Signatureheader? - Why is my Bridge webhook signature verification failing?
- How do I handle
customer.updated,kyc_link.updated,transfer.updated, orvirtual_account.activityevents? - How do I create and enable a Bridge webhook endpoint via the API?
Verification (core)
Bridge sends X-Webhook-Signature: t=<timestamp_ms>,v0=<base64_signature>. Verify with the endpoint's RSA public key (the public_key PEM from the webhook API response). Use the raw request body — don't JSON.parse first.
Quirk: Bridge SHA256-hashes
<timestamp>.<rawBody>to a digest, then RSA-SHA256 verifies that digest — so the digest is hashed again insideverify. Feed the digest (not the raw string) into an RSA-SHA256 verifier, exactly as below.
const crypto = require('crypto');
function verifyBridgeSignature(rawBody, header, publicKeyPem, toleranceMs = 10 * 60 * 1000) {
const parts = {}; // split on FIRST '=' — base64 '=' padding is safe
for (const p of header.split(',')) {
const i = p.indexOf('=');
parts[p.slice(0, i)] = p.slice(i + 1);
}
const { t: timestamp, v0: signature } = parts;
if (!timestamp || !signature) return false;
if (Date.now() - Number(timestamp) > toleranceMs) return false; // reject stale events (replay guard)
const digest = crypto.createHash('sha256').update(`${timestamp}.${rawBody}`).digest();
const verifier = crypto.createVerify('sha256'); // RSA-SHA256 hashes `digest` a second time
verifier.update(digest);
verifier.end();
try {
return verifier.verify(publicKeyPem, signature, 'base64');
} catch {
return false;
}
}
Return a non-2xx (Bridge's docs use 400) on failure so Bridge retries.
For complete handlers with route wiring, event dispatch, and tests, see:
Common Event Types
Event names are <category>.<action>. You subscribe by category (not by individual event) via the event_categories array when creating the webhook.
| Event | Category | Triggered When |
|---|---|---|
customer.created | customer | A customer is created |
customer.updated | customer | Customer details or KYC status change |
kyc_link.updated | kyc_link | A KYC / ToS link status changes |
transfer.created | transfer | A transfer is created |
transfer.updated | transfer | A transfer changes status (e.g. payment processed) |
virtual_account.activity | virtual_account | Funds are received/processed on a virtual account |
For the full list of categories and events, see references/overview.md and Bridge's webhook docs.
Environment Variables
# Per-endpoint RSA public key (PEM) from the webhook create/update API response.
# Store the single-line form with literal \n; the examples convert \n back to newlines.
BRIDGE_WEBHOOK_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIIB...\n-----END PUBLIC KEY-----"
There is no webhook signing secret — verification uses the public key only. Your Bridge Api-Key is used to create/enable webhooks, not to verify them.
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 bridge-xyz --path /webhooks/bridge-xyz
Reference Materials
- references/overview.md - What Bridge webhooks are, event categories, payload shape
- references/setup.md - Create, enable, and test a webhook via the Bridge API
- references/verification.md - RSA-SHA256 signature verification details and gotchas