FurlPay Docs
Open App
  • Services & Availability
  • Quickstart
  • For AI Agents
  • Monorepo
  • API Routes
  • Authenticationupdated
  • Webhook Eventsupdated
  • Error Codesupdated
  • Rate Limitsnew
  • Configuration & Setupnew
  • Merchant Paymentsnew
  • Payroll & Payoutsnew
  • Agentic Payments (x402)updated
  • Agent Trust & Mandates (TAP)updated
  • CCTP Cross-Chainupdated
  • LI.FI Swapsupdated
  • FurlPay Travels (Travel MCP)updated
  • Solana Actions & Blinks
  • Settlement Workspacenew
  • FURL Tokennew
  • Claude Connectorupdated
  • AI Assistants
  • Circlenew
  • Ramp Providersnew
  • Telegram Mini Appnew
  • Stripe Crypto
  • Persona KYC
  • Regulatory status
  • Screening & Casesnew
  • Security Postureupdated
  • Signing & WebAuthnupdated
  • Help Center
  • 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/Security & Crypto/Signing & WebAuthn

Security & Crypto

Signing & WebAuthn

The web application uses passkeys for authentication and a remote signing service for some wallet actions. Passkey authentication is not the same as user-held wallet keys or threshold signing. This page describes the current web path and its limits.

Correction: this page previously described 2-of-2 MPC

It said wallet keys were split between a device share and a server share, and that neither party could move funds alone. That was not what the code did. The web signing path returns a complete signature from a remote enclave; the device holds no key share. The description below is the accurate one, and docs/CUSTODY-DISCLOSURE.md in the repository records the correction in full.

Signing model in the web application

Mobile client source exists in the repository, but FurlPay does not currently distribute a production mobile app. Do not treat unreleased client code as an available self-custody product.

Web app — custodial. Signing runs through /api/mpc/sign to a secure enclave operated by Turnkey, which signs the digest and returns a complete signature. The client verifies that the signature recovers to the expected address, which is a correct check — but it is verification, not participation in a threshold scheme. Anything holding the Turnkey organisation credential can, subject to enclave policy, produce signatures for wallets in that organisation. That makes this path custodial.

Furlpay deliberately calls signRawPayload rather than letting Turnkey build and sign the whole transaction: the client serialises the transaction and computes the digest itself, so the address-recovery check is not Turnkey checking Turnkey.

Policy checks are not threshold signing

The property with genuine independence is authorisation, not key material. A signature request must survive three gates, each able to refuse without opening the others:

  • The client refuses to request an out-of-policy signature.
  • /api/mpc/sign refuses to forward one (lib/services/policyEngine.ts).
  • Turnkey's enclave policy refuses to produce one.

That is real defence in depth. It is just not key-share splitting, and the old copy conflated the two.

What would make true threshold signing real

A device-held share that is required to produce a signature — each side computing a partial signature, neither sufficient alone. That is on the roadmap and is not built. Until it ships and is verified end to end, this page describes the web path as custodial.

WebAuthn passkeys

Login uses WebAuthn / FIDO2 passkeys — fingerprint or Face ID, no passwords. The Relying Party ID is resolved from explicit server configuration (FURLPAY_RP_ID / Vercel deploy URL), never from the client-controlled Host header — the single most common WebAuthn deployment mistake, and the one that lets a reverse-proxy phishing origin bind credentials. Assertions verify against an allow-list of origins and RP IDs.

  • Registration and assertion run through @simplewebauthn/server (origin, challenge, and signature-counter checks).
  • Credentials are bound to the registrable eTLD+1, so they can never be replayed on another domain.
Rendering diagram…
Passkey authentication ceremony: server-issued challenge, authenticator assertion, server-side verification, signed session.

Signed sessions

A successful passkey ceremony mints a short-lived, HttpOnly session cookie — a signed JWT (jose, HS256) verified in edge middleware, so tampered or expired tokens are rejected before any handler runs. The signing secret is required in production; the app refuses to start with the weak development fallback.

Agentic payment security (x402)

Production x402 is currently unavailable: on 11 October 2026 the endpoint returned 503 controls_unavailable with reason plan_limit. Settlement is also in development, so an issued challenge is not proof that a payment can complete or a paid response will be delivered. Check the rail status and the x402 guide before integrating.

  • Validate the challenge fields and pay-to address before presenting a wallet signature request.
  • Release a paid resource only after the configured facilitator confirms settlement.
  • Do not treat a prepared authorization, challenge or pending status as a completed payment.

Production boundary

FurlPay-operated settlement is not currently enabled. External security audits for the settlement contracts are also pending; see the security posture page for the current audit statement.
Did this page help?
Edit this page on GitHub

← Previous

Security Posture

Next →

Help Center