Architecture
API Routes
The API surface lives under apps/web/src/app/api as Next.js route handlers. Every request body is validated with zod; every mutation is written through the double-entry ledger.
Authentication
| Method | Path | Description |
|---|---|---|
| POST | /api/auth/webauthn | Register / authenticate a passkey (FIDO2) |
| POST | /api/auth/otp/start | Send a phone / email OTP (Twilio Verify) |
| POST | /api/auth/google | Sign in with Google Identity Services |
Cards
| Method | Path | Description |
|---|---|---|
| POST | /api/cards | Issue a 2-of-2 MPC virtual card |
| POST | /api/cards/settings | Freeze, set limits, update controls |
curl -X POST https://furlpay.app/api/cards/settings \
-H "Authorization: Bearer sk_live_…" \
-d '{ "cardId": "card_1a2b", "frozen": true, "spendLimit": 25000 }'Investing & market data
| Method | Path | Description |
|---|---|---|
| POST | /api/investing/order | Fractional stock / ETF order (Alpaca) |
| GET | /api/investing/portfolio | Holdings, cost basis and P/L |
Quotes and fundamentals are sourced from the Twelve Data API; real-time execution and price streams run over Alpaca WebSockets.
Agentic payments (x402)
Furlpay speaks x402 — the HTTP 402 "Payment Required" protocol — so AI agents and API clients pay per-request in USDC with no account or key. An unpaid request returns a signed quote; the client pays and retries with an X-PAYMENT header.
| Method | Path | Description |
|---|---|---|
| GET | /api/x402/fx | Pay-per-call mid-market FX quote ($0.01 USDC) |
# 1. unpaid request → 402 with a payment quote
curl -i https://furlpay.com/api/x402/fx?from=USD&to=EUR&amount=100
# 2. sign the EIP-3009 authorization, then retry with the payment attached
curl https://furlpay.com/api/x402/fx?from=USD&to=EUR&amount=100 \
-H "X-PAYMENT: $(base64_payload)" # → 200 + X-PAYMENT-RESPONSE receiptVerification is hardened against the authorization, binding, replay, and web-layer attack classes documented in 2026 x402 research. See /docs/payments/agentic-x402 for the full flow.
Solana Actions & Blinks
Shareable checkout links unfurl into signable payment widgets on X, Discord, and any Blink client.
| Method | Path | Description |
|---|---|---|
| GET | /actions.json | Blink client discovery rules |
| GET | /api/actions/pay/[orderId] | Blink metadata (icon, label, buttons) |
| POST | /api/actions/pay/[orderId] | Return the base64 SOL/USDC transaction to sign |
Webhooks & signature verification
| Method | Path | Description |
|---|---|---|
| POST | /api/webhooks/marqeta | Card processor events |
| POST | /api/webhooks/bridge | On/off-ramp settlement events |
| POST | /api/webhooks/wise | Local bank account events |
Every webhook carries a Furlpay-Signature header. Verify it against your endpoint secret before acting on the payload.
import { Furlpay } from '@furlpay/furlpay-node';
export async function POST(req: Request) {
const raw = await req.text();
const event = Furlpay.webhooks.constructEvent(
raw,
req.headers.get('furlpay-signature'),
process.env.FURLPAY_ENDPOINT_SECRET!,
);
// event is now trusted — handle event.type
}Idempotency
Idempotency-Key header on POSTs so retries never double-issue a card or double-place an order.