FurlPay Docs
Open App
  • Introduction
  • Quickstart
  • For AI Agents
  • Monorepo
  • API Routes
  • Authenticationnew
  • Webhook Eventsnew
  • Error Codesnew
  • Rate Limitsnew
  • Agentic Payments (x402)
  • Agent Trust & Mandates (TAP)
  • CCTP Cross-Chainnew
  • LI.FI Swapsnew
  • FurlPay Travels (Travel MCP)
  • Solana Actions & Blinks
  • Claude Connectornew
  • AI Assistants
  • Stripe Crypto
  • Persona KYC
  • MiCA Roadmap
  • Security Posturenew
  • MPC & WebAuthn
  • Help Centernew
  • Getting Started
  • KYC Verification
  • Passkeys & Biometrics
  • Privacy & Data Protection
  • Transaction Statuses
  • Gasless Transfers
  • Deposits & Withdrawals
  • Managing Virtual Cards
  • Freezing & Unfreezing Cards
  • Declined Transactions
  • SDK & API Support
  • x402 Monetization Basics
  • Booking Travel
  • Travel Refunds & Cancellations

Resources

  • Changelog
  • System Status
  • OpenAPI Spec
  • Community
  • GitHub
Docs/Architecture/Webhook Events

Architecture

Webhook Events

Every webhook handler verifies a signature over the raw body before trusting a byte, rejects stale timestamps, and fails closed when its secret is unset. This page lists the inbound handlers and shows how to verify events Furlpay sends you.

Inbound handlers

MethodPathDescription
POST/api/webhooks/stripeStripe events — stripe-signature verified via constructEvent
POST/api/webhooks/sumsubSumsub KYC outcomes — HMAC digest over the raw body
POST/api/webhooks/personaPersona inquiry outcomes — persona-signature HMAC
POST/api/webhooks/wiseWise transfer state changes — signature verified
POST/api/webhooks/bridgeBridge fiat rail events — signature verified
POST/api/webhooks/marqetaCard processor callbacks (JIT funding, auth events)
POST/api/webhooks/card-auth3DS2 card authentication events — endpoint secret
  • Signatures are computed over the raw request body — parse only after verification.
  • Comparisons are constant-time; timestamped schemes reject events older than the tolerance window.
  • A handler whose secret is not configured refuses the event (fail closed) rather than accepting unsigned input.
  • Webhooks are exempt from the mutating-request IP rate-limit baseline (providers batch deliveries from few IPs) — the signature is the gate.

Verifying events from Furlpay

Events delivered to your endpoint carry a Furlpay-Signature header: an HMAC-SHA256 over timestamp + "." + rawBody with your endpoint secret. Verify before trusting:

typescriptapp/api/webhooks/furlpay/route.ts
import crypto from "crypto";

export async function POST(req: Request) {
  const raw = await req.text();                       // RAW body — do not parse first
  const sig = req.headers.get("furlpay-signature") ?? "";
  const ts = req.headers.get("furlpay-timestamp") ?? "";

  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) {
    return new Response("stale", { status: 400 });    // replay window: 5 minutes
  }
  const expected = crypto
    .createHmac("sha256", process.env.FURLPAY_WEBHOOK_SECRET!)
    .update(`${ts}.${raw}`)
    .digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(sig);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return new Response("bad signature", { status: 401 });
  }

  const event = JSON.parse(raw);                      // safe to parse now
  // handle event.type ...
  return new Response("ok");
}

Local development

Forward live events to localhost with the CLI — it prints the signing secret to verify against:

bash
furlpay listen --forward-to localhost:3000/api/webhooks/furlpay

Never credit balances from an unverified event

A webhook body is attacker-controllable input until the signature passes. Verify first, act second, and make handlers idempotent — providers redeliver.
Did this page help?
Edit this page on GitHub

← Previous

Authentication

Next →

Error Codes