Skip to content

Refunds

Issue and manage Payment Link refunds. Refunds use the x402 reverse payment flow — the system generates a refund link, the agent/user pays the refund link using a mandate, and the funds are settled back to the original payer via the Facilitator.

All management endpoints require Authorization: Bearer <agent-jwt>.

Refund flow overview

Agent/User              FluxA API                   Facilitator          Onchain
   |                        |                           |                   |
   |-- POST initiate ------>|                           |                   |
   |<-- refundUrl + plr_* --|                           |                   |
   |                        |                           |                   |
   |-- GET refundUrl ------>|                           |                   |
   |<-- 402 (payTo=payer) --|                           |                   |
   |                        |                           |                   |
   |-- sign via mandate --->|                           |                   |
   |-- GET + X-Payment ---->|-- verify + settle ------->|-- transfer ------>|
   |<-- 200 {txHash} -------|                           |                   |
  1. Initiate — call POST /api/payment-links/refunds/initiate with the original paymentId. The system creates a pending refund record and returns a refundUrl.
  2. Get 402GET the refundUrl. The server returns HTTP 402 with payTo set to the original payer's address (the refund recipient).
  3. Sign and submit — sign the payment using a mandate and submit with X-Payment header. The Facilitator verifies and settles onchain.
  4. Done — the refund record updates to settled and appears in the payment feed.

TIP

The payTo in a refund link points to the original payer — this is the key difference from a normal payment link where payTo is the link creator.


Initiate refund

Create a refund record and get a refund link URL.

bash
curl -X POST https://walletapi.fluxapay.xyz/api/payment-links/refunds/initiate \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $JWT_TOKEN" \
  -d '{
    "paymentId": 43,
    "amount": "1000000",
    "reason": "Customer requested refund"
  }'

Request fields

FieldTypeRequiredDescription
paymentIdnumberYesOriginal payment record ID (payment_link_payments.id)
amountstringNoRefund amount in atomic units. Omit for full refund
reasonstringNoRefund reason

Response (201)

json
{
  "refundId": "plr_abc123xyz456",
  "refundUrl": "https://walletapi.fluxapay.xyz/refundlink/plr_abc123xyz456",
  "paymentId": 43,
  "amount": "1000000",
  "currency": "USDC",
  "network": "base",
  "refundFrom": "0xLinkOwnerAddress",
  "refundTo": "0xOriginalPayerAddress",
  "refundType": "full",
  "reason": "Customer requested refund",
  "status": "pending",
  "expiresAt": "2026-04-14T12:00:00.000Z",
  "createdAt": "2026-04-13T12:00:00.000Z"
}

Error responses

StatusDescription
400Invalid parameters / refund amount exceeds limit / original payment not settled / payer address unknown
404Original payment not found or not authorized

Over-refund protection — if the requested amount exceeds the remaining refundable amount:

json
{
  "error": "Refund amount exceeds remaining refundable amount",
  "originalAmount": "5000000",
  "totalRefunded": "3000000",
  "remainingRefundable": "2000000",
  "requestedAmount": "3000000"
}

Public endpoint — no authentication required. The agent uses a mandate to pay this URL, which transfers funds back to the original payer.

Step 1 — Request refund payment requirements

bash
curl -i https://walletapi.fluxapay.xyz/refundlink/plr_abc123xyz456

Response (402 Payment Required):

json
{
  "error": "Payment Required",
  "x402Version": 1,
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:8453",
      "maxAmountRequired": "1000000",
      "resource": "/refundlink/plr_abc123xyz456",
      "description": "Refund: Customer requested refund",
      "payTo": "0xOriginalPayerAddress",
      "maxTimeoutSeconds": 60,
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "extra": {
        "name": "USD Coin",
        "version": "2"
      }
    }
  ]
}

WARNING

Note that payTo is the original payer's address — the refund recipient. This is the key difference from a normal payment link.

Step 2 — Submit refund payment

Sign an EIP-3009 TransferWithAuthorization using a mandate, encode as Base64, and attach as the X-Payment header.

bash
curl -i https://walletapi.fluxapay.xyz/refundlink/plr_abc123xyz456 \
  -H "X-Payment: eyJ4NDAyVmVyc2lvbiI6MSwicGF5bG9hZCI6ey..."

Response (200 — success):

json
{
  "status": "success",
  "refund": {
    "refundId": "plr_abc123xyz456",
    "txHash": "0xRefundTxHash...",
    "amount": "1000000",
    "currency": "USDC",
    "refundTo": "0xOriginalPayerAddress"
  }
}

Response (402 — payment failed):

json
{
  "error": "Refund payment failed",
  "reason": "Invalid signature"
}

Response (410 — already settled or expired):

json
{
  "error": "Refund has already been settled",
  "status": "settled"
}

List refunds

List all refund records for the current user.

bash
curl https://walletapi.fluxapay.xyz/api/payment-links/refunds?limit=20&offset=0 \
  -H "Authorization: Bearer $JWT_TOKEN"

Query parameters

ParamTypeDefaultDescription
limitnumber20Max results (max 100)
offsetnumber0Pagination offset

Response (200)

json
{
  "refunds": [
    {
      "refundId": "plr_abc123xyz456",
      "refundUrl": "https://walletapi.fluxapay.xyz/refundlink/plr_abc123xyz456",
      "paymentId": 43,
      "linkId": "pl_abc123",
      "sourceType": "payment_link",
      "amount": "1000000",
      "currency": "USDC",
      "network": "base",
      "refundFrom": "0xLinkOwnerAddress",
      "refundTo": "0xOriginalPayerAddress",
      "status": "settled",
      "refundTxHash": "0xRefundTxHash...",
      "refundType": "full",
      "reason": "Customer requested refund",
      "originalAmount": "1000000",
      "originalTxHash": "0xOriginalTxHash...",
      "agentName": "My Agent",
      "expiresAt": "2026-04-14T12:00:00.000Z",
      "createdAt": "2026-04-13T12:00:00.000Z"
    }
  ]
}

Response fields

FieldTypeDescription
refundIdstringRefund ID (plr_* format)
refundUrlstring | nullRefund link URL (only returned when pending)
paymentIdnumberOriginal payment record ID
linkIdstring | nullAssociated Payment Link ID; null for UPL
sourceTypestringpayment_link or unify_payment_link
amountstringRefund amount in atomic units
currencystringCurrency
networkstringNetwork
refundFromstringRefund initiator address (link owner)
refundTostringRefund recipient address (original payer)
statusstringpending, settled, failed, or expired
refundTxHashstring | nullOnchain refund transaction hash
refundTypestringfull or partial
reasonstring | nullRefund reason
originalAmountstringOriginal payment amount
originalTxHashstring | nullOriginal payment tx hash
agentNamestring | nullName of the agent that initiated the refund
expiresAtstringRefund link expiry (ISO 8601)
createdAtstringCreated timestamp (ISO 8601)

Get refund detail

Get a single refund record.

bash
curl https://walletapi.fluxapay.xyz/api/payment-links/refunds/plr_abc123xyz456 \
  -H "Authorization: Bearer $JWT_TOKEN"

Response (200): same shape as a single entry in the list response.

StatusDescription
404Refund not found or not authorized to view

Cancel refund

Cancel a pending refund. Only pending refunds can be cancelled.

bash
curl -X POST https://walletapi.fluxapay.xyz/api/payment-links/refunds/plr_abc123xyz456/cancel \
  -H "Authorization: Bearer $JWT_TOKEN"

Response (200):

json
{
  "success": true,
  "refundId": "plr_abc123xyz456",
  "status": "cancelled"
}
StatusDescription
400Refund is not in pending status
404Refund not found or not authorized

Released under the MIT License.