Payment Links
Create and manage payment collection links — shareable URLs that collect a fixed amount. The same link is payable two ways:
- An agent (or any backend/script) pays it programmatically over x402.
- A person opens it in a browser and pays on a hosted checkout page — with their FluxA wallet, or by connecting their own crypto wallet.
All management endpoints require Authorization: Bearer <agent-jwt>.
Create payment link
curl -X POST https://walletapi.fluxapay.xyz/api/payment-links \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $JWT_TOKEN" \
-d '{
"amount": "1000000",
"currency": "USDC",
"network": "base",
"description": "Premium API Access — 1 USDC",
"resourceContent": "{\"apiKey\": \"sk_live_xxx\", \"message\": \"Thank you!\"}",
"expiresAt": "2025-12-31T23:59:59Z",
"maxUses": 100
}'Key fields:
| Field | Type | Required | Description |
|---|---|---|---|
amount | string | Yes | Price in atomic units (1000000 = 1.0 USDC) |
currency | string | No | USDC only (the default). |
network | string | No | base (default) or base-sepolia. |
description | string | No | Human-readable label shown to the payer |
resourceContent | string | No | JSON or text delivered to the payer after payment |
expiresAt | string | No | ISO 8601 expiry timestamp |
maxUses | number | No | Max payments accepted; null for unlimited |
returnUrl | string | No | Where to send the payer after a browser payment. Must be on an allow-listed origin. |
Response (201):
{
"success": true,
"paymentLink": {
"id": 1,
"linkId": "pl_abc123xyz456",
"amount": "1000000",
"currency": "USDC",
"network": "base",
"payTo": "0xYourWalletAddress",
"assetAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"scheme": "exact",
"description": "Premium API Access — 1 USDC",
"resourceContent": "{\"apiKey\": \"sk_live_xxx\", \"message\": \"Thank you!\"}",
"status": "active",
"expiresAt": "2025-12-31T23:59:59.000Z",
"maxUses": 100,
"useCount": 0,
"url": "https://walletapi.fluxapay.xyz/paymentlink/pl_abc123xyz456",
"createdAt": "2025-01-25T12:00:00.000Z",
"updatedAt": "2025-01-25T12:00:00.000Z"
}
}Share the url with payers: a person opening it in a browser lands on the hosted checkout page; agents and scripts pay the same url over x402.
Payment links are USDC only. amount is in the token's smallest unit — 1000000 = 1 USDC (6 decimals).
List payment links
curl https://walletapi.fluxapay.xyz/api/payment-links?limit=50 \
-H "Authorization: Bearer $JWT_TOKEN"Query parameters:
| Param | Type | Default | Description |
|---|---|---|---|
limit | number | 100 | Max results (max 500) |
Response (200):
{
"paymentLinks": [ /* same shape as create response */ ]
}Get payment link
curl https://walletapi.fluxapay.xyz/api/payment-links/pl_abc123xyz456 \
-H "Authorization: Bearer $JWT_TOKEN"Response (200):
{
"paymentLink": { /* same shape as create response */ }
}Update payment link
curl -X PATCH https://walletapi.fluxapay.xyz/api/payment-links/pl_abc123xyz456 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $JWT_TOKEN" \
-d '{
"description": "Updated description",
"status": "disabled",
"maxUses": 200
}'Updatable fields:
| Field | Type | Description |
|---|---|---|
description | string | Update description |
resourceContent | string | Update delivered content |
status | string | active or disabled |
expiresAt | string | ISO 8601 timestamp, or null to remove expiry |
maxUses | number | Max uses, or null to remove limit |
Response (200):
{
"success": true,
"paymentLink": { /* updated link */ }
}Delete payment link
Soft-deletes a payment link.
curl -X DELETE https://walletapi.fluxapay.xyz/api/payment-links/pl_abc123xyz456 \
-H "Authorization: Bearer $JWT_TOKEN"Response (200):
{
"success": true,
"message": "Payment link deleted"
}Get payments for a link
List all payments received through a specific payment link.
curl https://walletapi.fluxapay.xyz/api/payment-links/pl_abc123xyz456/payments \
-H "Authorization: Bearer $JWT_TOKEN"Query parameters:
| Param | Type | Default | Description |
|---|---|---|---|
limit | number | 100 | Max results (max 500) |
Response (200):
{
"payments": [
{
"id": 1,
"payerAddress": "0xPayerWalletAddress",
"amount": "1000000",
"currency": "USDC",
"settlementStatus": "settled",
"settlementTxHash": "0xabc123...",
"createdAt": "2025-01-25T13:00:00.000Z"
}
]
}Hosted checkout (browser)
When a person opens a link in a browser, FluxA hosts the checkout — they pay on a FluxA page with their FluxA wallet, or by connecting their own crypto wallet (USDC only). No x402 client or payer-side integration is required.
Step 1 — Send the payer the link
Share the link's url — the same one agents use. When a person opens it in a browser (Accept: text/html, no X-Payment), it 302-redirects to the FluxA-hosted checkout page automatically.
To return the payer to your site after they pay, set returnUrl at creation:
curl -X POST https://walletapi.fluxapay.xyz/api/payment-links \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $JWT_TOKEN" \
-d '{
"amount": "1000000",
"currency": "USDC",
"returnUrl": "https://yourapp.com/orders/complete"
}'USDC payment links default to dual (browser checkout + x402), so hosted checkout is on without setting paymentMode.
returnUrl must be on an allow-listed origin; a disallowed origin is rejected at creation.
Step 2 — Payer pays
On the hosted page the payer reviews the amount and confirms. FluxA settles the payment, delivers resourceContent, and — if you set returnUrl — redirects back to your site.
Step 3 — Verify server-side
returnUrl is a UX convenience, not proof of payment. Confirm from your backend with Get payments for a link and require settlementStatus: "settled" for the expected amount before fulfilling:
curl https://walletapi.fluxapay.xyz/api/payment-links/pl_abc123xyz456/payments \
-H "Authorization: Bearer $JWT_TOKEN"A link never settles twice for one payment:
useCountincrements once per completed payment, whichever method was used.
Public payment endpoint (x402)
This is a public endpoint — no authentication required. Any external service (script, agent, or backend) pays the link by speaking the x402 protocol against this URL.
Prerequisites:
- Links accept x402 by default. (A browser-only link returns
409 hosted_checkout_requiredto anX-Paymentrequest.) - The payer needs an EVM key holding USDC on the link's network (
base/base-sepolia). - Send
Accept: application/json(or omitAccept). A request withAccept: text/htmland noX-Paymentis treated as a browser and302-redirected to the hosted checkout page — scripts should avoid that header.
Step 1 — Request payment requirements
curl -i https://walletapi.fluxapay.xyz/paymentlink/pl_abc123xyz456Response (402 Payment Required):
{
"error": "Payment Required",
"x402Version": 2,
"accepts": [
{
"x402Version": 2,
"scheme": "exact",
"network": "eip155:8453",
"maxAmountRequired": "1000000",
"resource": "/paymentlink/pl_abc123xyz456",
"description": "Premium API Access — 1 USDC",
"payTo": "0xRecipientAddress",
"maxTimeoutSeconds": 60,
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"extra": {
"name": "USD Coin",
"version": "2"
}
}
]
}Step 2 — Submit payment
Sign an EIP-3009 TransferWithAuthorization over the USDC EIP-712 domain (name: "USD Coin", version: "2", chainId of the network, verifyingContract = the asset address), then Base64-encode this payload and send it as the X-Payment header by retrying the same GET:
{
"x402Version": 1,
"scheme": "exact",
"payload": {
"authorization": {
"from": "0xPayerAddress",
"to": "0xRecipientAddress", // must equal payTo from Step 1
"value": "1000000", // must equal maxAmountRequired
"validAfter": "0",
"validBefore": "1735689599", // unix seconds, ~60s ahead
"nonce": "0x<random 32-byte hex>"
},
"signature": "0x<EIP-3009 signature>"
}
}Don't want to build the payload yourself? If you hold a Fluxa agent JWT, call
POST /api/payment/x402V1Paymentwith the Step-1 fields — it returns a readyxPaymentB64to drop into theX-Paymentheader. Otherwise any standard x402 client (e.g. Coinbase's x402 SDK) produces the same payload.
curl -i https://walletapi.fluxapay.xyz/paymentlink/pl_abc123xyz456 \
-H "Accept: application/json" \
-H "X-Payment: eyJ4NDAyVmVyc2lvbiI6MSwic2NoZW1lIjoiZXhhY3Qi..."Response (200 — success). The settled on-chain receipt is also returned in the X-Payment-Response header (Base64 of {"success":true,"transaction":"0x..."}, mirrored as Payment-Response):
{
"status": "success",
"resource": "{\"apiKey\": \"sk_live_xxx\", \"message\": \"Thank you!\"}",
"receipt": {
"txHash": "0xabc123def456...",
"payer": "0xPayerAddress",
"amount": "1000000",
"currency": "USDC"
}
}Step 3 — Handle the outcome
| Status | Meaning | What to do |
|---|---|---|
200 | Settled. resource + receipt returned. | Done. |
402 | Verification or settlement failed (reason explains). | Re-sign with a new nonce and retry. |
202 | settle_result_unknown — funds may have moved but the result wasn't confirmed. | Don't re-pay. Poll Get payments for a link or let the merchant retry settlement; the same nonce resolves to the same attempt. |
409 | payment_attempt_nonce_conflict (nonce already used by another attempt) or hosted_checkout_required (link is hosted-only). | New nonce, or use the hosted checkoutUrl. |
Idempotency. Replaying the GET with the same (linkId, nonce) returns the same payment attempt — a settled one replays 200 with the original receipt, with no double-charge and no extra useCount. So retrying a request whose response you lost is safe as long as you reuse the same X-Payment payload.
