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/Payments/Merchant Payments

Payments

Merchant payments: links, invoices and hosted checkout

A merchant creates a payment link, an invoice or a terminal request for an exact USDC amount. The payer opens a hosted checkout, chooses a network and sends native USDC from their own wallet to a destination reserved for that one checkout. FurlPay marks the payment paid only after it has read the transfer back from the chain.

Status: implemented, not production verified

The flow is implemented and covered by tests. It is not enabled by code alone: it needs the payment migrations applied, collection addresses registered for each merchant and network by a custody provider that controls them, and working node endpoints. No custody provider is integrated, so production has no collection addresses. Until one exists the reserve call answers 503 and no payment is requested from the payer.

What a merchant can create

ParameterTypeDescription
linkrequiredkindA payment link for a fixed amount. May be reusable (many payers) or single-use.
invoicerequiredkindSingle-use, with 1 to 50 line items. The total must equal the sum of quantity × unit price.
terminalrequiredkindSingle-use request shown as a QR code at a point of sale.

Amounts are decimal strings with up to six decimals, above zero and at most 1,000,000 USDC. They are stored as integer micro-units; no floating-point arithmetic touches a payment amount.

Networks and confirmation depth

Only Circle-issued native USDC is accepted. A network is collectable only when it has a reviewed deposit policy; the required depth is the stricter of that policy and the payment registry. The checkout offers the networks that currently have an unassigned destination for the merchant.

ParameterTypeDescription
Baserequired1 confirmationMainnet, chain ID 8453.
Arbitrumrequired1 confirmationMainnet, chain ID 42161.
Ethereumrequired12 confirmationsMainnet, chain ID 1.
Polygonrequired128 confirmationsMainnet, chain ID 137.
Solanarequired31 confirmationsMainnet. Matched by a one-time reference key included in the transfer.
Arcrequired1 confirmationMainnet, chain ID 5042. Receipts are keyed on the network's system-emitter log.
Suirequired1 checkpointMainnet. Read over GraphQL; a transaction in a certified checkpoint is final.
Arc Testnetoptionaltestnet onlyChain ID 5042002. Collectable only with FURLPAY_TESTNET_COLLECTION=1 and never in a production environment. Testnet payments book to separate ledger accounts.
OP Mainnet, Avalancheoptionalnot collectableListed in the registry, but they have no deposit policy, so a checkout cannot be reserved on them.

Fees

Each link records the merchant's processing rate when it is created. Rates are tiers assigned by operations: starter 95, growth 75, scale 50 and enterprise 35 basis points. A merchant with no assigned tier pays the growth rate. The fee is rounded down to the smallest USDC unit. The merchant chooses who pays it:

  • Merchant pays — the payer is charged the gross amount; the merchant nets gross minus fee.
  • Customer pays — the payer is charged gross plus fee; the merchant nets the gross amount.

Network gas is separate and is always paid by the payer's wallet.

Checkout flow

  • The merchant creates a link. The response contains a 24-character opaque slug; the hosted page is /p/<slug>. Payers are anonymous and need no account.
  • The payer picks a network. FurlPay reserves one never-reused destination address for this checkout and records the chain height at that moment.
  • The checkout shows a payment request and QR code for the payer's wallet.
  • While the page is open it polls the checkout endpoint, which runs the verifier. A scheduled job runs the same verifier every two minutes, so confirmation does not depend on the page staying open.
  • When the required depth is reached and screening passes, the receipt, ledger posting, status change and webhook event are committed together.

Payment statuses

ParameterTypeDescription
awaiting_paymentrequiredstatusDestination reserved; no qualifying transfer seen.
confirmingrequiredstatusA transfer was seen but has not reached the required depth.
underpaidrequiredstatusLess than the charge has been received; the checkout shows the remaining amount.
paidrequiredstatusThe exact charge was received and verified.
overpaidrequiredstatusMore than the charge was received. Treated as paid; the excess is recorded.
expiredrequiredstatusThe checkout window closed without full payment.
reviewrequiredstatusHeld for manual review, for example a screening result that is not an allow.

How a payment is verified

The client never reports a payment. The verifier reads the transfer to the reserved address from the network, checks it independently against the transaction receipt, confirms the network identity and that the node head is fresh, and re-checks that the block is still canonical. Before crediting, the payer and the destination are screened (see Screening and cases).

If a node is unreachable the checkout keeps its last proven state and reports that verification is delayed. An outage is never turned into a success or a failure. A transfer is recorded once per network, transaction and log position, so a duplicate scan cannot credit twice.

Receipts

A one-page PDF receipt is issued only for a payment the verifier credited (paid or overpaid). An underpaid, confirming or held payment has no receipt.

Refunds

On-chain payments cannot be reversed, so a refund is a new transfer back to the payer. Requesting one reserves the amount in the ledger immediately. Operations approve it, and the custody provider that controls the collection addresses sends it. The refund is marked complete only when that transfer is read on-chain.

ParameterTypeDescription
requestedrequiredstatusAmount reserved; awaiting operations.
pending_external_executionrequiredstatusApproved; waiting for the custody provider's transfer.
completedrequiredstatusThe refund transfer was verified on-chain.
cancelledrequiredstatusCancelled; the reservation is released.

Refund execution is blocked by an external dependency

Requesting, approving and verifying refunds is implemented. Sending one requires a custody provider, and none is integrated, so a refund cannot currently leave the pending state.

Reconciliation and alerts

Every ten minutes a scheduled job compares three records: the intent, the ledger and the chain. Recently recorded receipts are re-read from the network and must still be a successful, canonical USDC transfer of the recorded amount to the intent's address. It also raises liveness alerts for payments stuck confirming, a scanner that has stopped, held payments nobody has reviewed, and a network whose head is stale. Findings become alerts for operations. The job never edits an intent, reverses a journal or changes a merchant balance.

API

MethodPathDescription
GET/api/merchant/paymentsSigned-in merchant: links, payment intents and ready networks; ?id=<uuid> returns one payment with events and receipts
POST/api/merchant/paymentsCreate a link, invoice or terminal request. Idempotent on requestId
PATCH/api/merchant/paymentsActivate or deactivate a link
GET/api/merchant/payments/receiptPDF receipt for a credited payment
GET / POST / DELETE/api/merchant/refundsList, request or cancel refunds
GET / PUT / POST / DELETE/api/merchant/webhookWebhook endpoint; POST sends a test event or re-queues one failed event
GET/api/checkout/merchant/{slug}Public: link details; ?intent=<uuid> returns checkout status and runs verification
POST/api/checkout/merchant/{slug}Public: reserve a checkout on one network: { requestId, chain }
GET/api/checkout/merchant/{slug}/receiptPublic: receipt for a credited checkout
GET / POST/api/merchant/operationsOperators only: held payments, alerts, pricing tiers, address registration, refund approval
bashCreate a payment link
curl -X POST https://furlpay.com/api/merchant/payments \
  -H "content-type: application/json" \
  -b "session" \
  -d '{
    "requestId": "7d0a5c4e-2c1b-4b7e-9a51-3f2d6c8e1a90",
    "kind": "link",
    "title": "Annual plan",
    "description": "",
    "amount": "250.00",
    "chains": ["base", "arbitrum"],
    "reusable": false,
    "feePayer": "merchant",
    "expiresAt": null,
    "reference": "ORDER-1042",
    "lines": []
  }'

Merchant routes use the signed-in session; the merchant is always the authenticated account and no merchant ID is accepted from the client. The public checkout routes are rate limited per IP.

Responses to handle

  • 409 on reserve — the link is closed, or a single-use checkout is already in progress.
  • 503 on reserve — the network is not ready (no destination, or the node could not be verified). No payment was requested.
  • 503 on merchant routes — payment storage is not configured or a migration is not applied.

Webhook events

A verified payment writes payment.succeeded to the merchant's outbox, and invoice.paid when the link is an invoice. Delivery, signature format and retry schedule are described in Webhook Events. The webhook is a notification; the payment record returned by the API is the source of truth.

Where merchants manage this

  • /dashboard/payments, /dashboard/payment-links, /dashboard/invoices and /dashboard/qr — the merchant workspace (sign-in required).
  • /dashboard/operations — the operator console.
  • /p/<slug> — the public hosted checkout for one link.

Setup

  • Database — Supabase configured, with migrations 0040 (payments), 0041 (webhooks), 0042 (pricing tiers, reconciliation), 0043 (Sui), 0045 (refunds), 0046 (liveness alerts) and 0047 (Arc testnet) applied in order.
  • Collection addresses — registered through the operator console. Registration requires proof of control: the address itself signs a message naming the merchant, network, address and custody reference. FurlPay never generates, derives or stores keys for collection addresses. Each address is used for exactly one checkout, and there is no fallback to a shared address.
  • Node endpoints — BASE_RPC_URL, ARBITRUM_RPC_URL, ETHEREUM_RPC_URL, POLYGON_RPC_URL, SOLANA_RPC_URL, ARC_RPC_URL and SUI_GRAPHQL_URL, with optional _SECONDARY endpoints for failover. Unset values fall back to public endpoints, which are rate limited.
  • Scheduled jobs — /api/cron/merchant-payments every two minutes and /api/cron/merchant-reconcile every ten, both listed in vercel.json. Sub-daily schedules depend on the hosting plan.
  • Webhook secrets — FURLPAY_PARTNER_WEBHOOK_KEY seals merchant signing secrets at rest.
  • Check — npm run merchant:check-env reports what is missing. npm run merchant:seed-destinations registers pilot addresses on a local or staging database only; it refuses the production project.

What this is not

Collected funds arrive at addresses a custody provider controls and are recorded in a USDC subledger. Paying those balances out to a merchant's bank account is not offered: FurlPay has no contracted banking or off-ramp partner.
Did this page help?
Edit this page on GitHub

← Previous

Configuration & Setup

Next →

Payroll & Payouts