Agent skill

Bridge API Webhooks Skill

Receive and verify Bridge API webhooks (bridgeapi.io — the open-banking aggregator by Bridge/Bankin', NOT bridge.xyz). Use when setting up Bridge API webhook handlers, debugging BridgeApi-Signature HMAC-SHA256 verification, or handling events like item.created, item.refreshed, item.account.updated, payment.transaction.created, or user.deleted.

Install this skill

npx skills add hookdeck/webhook-skills --skill bridge-api-webhooks


Which Bridge? This skill is for Bridge API (bridgeapi.io), the open-banking / account-aggregation platform by Bridge (formerly Bankin'). It is not bridge.xyz (the stablecoin/crypto payments company). See bridge-xyz-webhooks for that one.

When to Use This Skill

  • How do I receive Bridge API webhooks?
  • How do I verify the BridgeApi-Signature header?
  • Why is my Bridge API webhook signature verification failing?
  • How do I handle item.refreshed, item.account.updated, or payment.transaction.created events?
  • How do I support Bridge's signing-secret rotation (two active secrets)?

Verification (core)

Bridge signs the raw request body with HMAC-SHA256, keyed on the webhook's signing secret, and sends the digest in the BridgeApi-Signature header as one or more scheme-prefixed, comma-separated values — hex, uppercase:

BridgeApi-Signature: v1=E5637CDB...,v1=A1B2C3D4...

Only the v1 scheme is valid — ignore any other scheme to avoid downgrade attacks. Multiple v1 values can appear during a secret rotation (the old secret stays valid for 24h, up to 2 active signatures), so accept the webhook if any v1 value matches. Compare timing-safe over the raw body.

Node:

const crypto = require('crypto');

function verifyBridgeWebhook(rawBody, signatureHeader, secret) {
  if (!signatureHeader) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  // Keep only v1= signatures (ignore other schemes → no downgrade), strip the prefix
  const signatures = signatureHeader.split(',')
    .map((s) => s.trim())
    .filter((s) => s.startsWith('v1='))
    .map((s) => s.slice(3));
  if (signatures.length === 0) return false;
  // hex decode is case-insensitive, so Bridge's UPPERCASE hex compares cleanly
  return signatures.some((sig) => {
    try {
      return crypto.timingSafeEqual(Buffer.from(sig, 'hex'), Buffer.from(expected, 'hex'));
    } catch {
      return false; // malformed / length mismatch
    }
  });
}

Python:

import hmac, hashlib

def verify_bridge_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
    if not signature_header:
        return False
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    signatures = [s.strip()[3:] for s in signature_header.split(",") if s.strip().startswith("v1=")]
    # hex compare is case-insensitive → lowercase both sides before compare_digest
    return any(hmac.compare_digest(sig.lower(), expected.lower()) for sig in signatures)

For complete handlers with route wiring, event dispatch, and tests, see:

Common Event Types

The event name is the type field in the JSON body (there is no event header). Names are lowercase resource.action (except the dashboard test event).

EventTriggered When
item.createdA bank connection (item) is created
item.refreshedAn item's data finished refreshing
item.account.createdA new account is discovered under an item
item.account.updatedAn account's balance/details changed
item.account.deletedAn account is removed from an item
payment.transaction.createdA payment transaction is created
payment.transaction.updatedA payment transaction's status changed
payment.link.updatedA payment link's status changed
user.deletedA user is deleted
TEST_EVENTSent by the dashboard's "Send a test" button

For the full event reference, see Bridge API Webhooks docs.

Payload Structure

{
  "type": "item.refreshed",
  "timestamp": 1699999999,
  "content": {
    "item_id": 12345,
    "user_uuid": "a1b2c3d4-...",
    "status": 0
  }
}

content fields vary by event. Expect webhooks for already-deleted users or items — handle them defensively (a lookup miss is normal, not an error).

Important Headers

HeaderDescription
BridgeApi-SignatureHMAC-SHA256 signatures, v1=<UPPERCASE_HEX> (comma-separated for rotation)

Environment Variables

BRIDGE_WEBHOOK_SECRET=your_webhook_signing_secret   # Shown once when the webhook is created/rotated

Source IPs

Bridge delivers from fixed IPs — optionally allowlist them (read the client IP from X-Forwarded-For if you sit behind a proxy/load balancer):

63.32.31.5
52.215.247.62
34.249.92.209

Keep your response body under 10 KB and reply with 200 as quickly as possible. Non-200 or slow responses are retried with exponential backoff for 1–2 days.

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 bridge-api --path /webhooks/bridge-api

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Aug 5, 2026

View on GitHub →