Refund Protection

Automatically refund users when your service fails after payment.

If you're using OpenFacilitator—whether through pay.openfacilitator.io or your own white-labeled domain—you should be using our middleware to accept payments on your endpoints. It's the easiest way to handle x402 payments, and adding refund protection is just one config option.

Express Middleware

The SDK provides createPaymentMiddleware for Express:

import express from 'express';
import { createPaymentMiddleware } from '@openfacilitator/sdk';
 
const app = express();
 
const paymentMiddleware = createPaymentMiddleware({
  facilitator: 'pay.openfacilitator.io',
  getRequirements: (req) => ({
    scheme: 'exact',
    network: 'base',
    maxAmountRequired: '1000000',
    asset: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
    payTo: '0xYourWalletAddress',
  }),
  refundProtection: {
    apiKey: process.env.REFUND_API_KEY!,
  },
});
 
app.post('/api/generate', paymentMiddleware, async (req, res, next) => {
  try {
    const result = await generateSomething();
    res.json({ result });
  } catch (error) {
    next(error); // This triggers refund reporting
  }
});

Access Payment Context

app.post('/api/resource', paymentMiddleware, async (req, res) => {
  const payment = req.paymentContext;
  console.log('Paid by:', payment.userWallet);
  // ...
});

Not using the middleware yet? You should be. It handles all the x402 protocol details so you can focus on your business logic. See installation.


Setup Steps

1. Get an API Key

Visit your claims setup page to:

  • Register your server and generate an API key
  • Create a refund wallet on your target network(s)
  • Fund the wallet with USDC to pay refunds

2. Add the Config

Add the refundProtection object to your middleware config:

refundProtection: {
  apiKey: process.env.REFUND_API_KEY!,
}

That's it. The facilitatorUrl defaults to your middleware's facilitator.

3. You're Done

When your handler throws an error after payment settles, the SDK automatically reports it. Claims appear in your dashboard where you can review and approve them.


How It Works

1. User sends payment to your endpoint
2. Middleware verifies and settles the payment ✓
3. Your handler runs
4. Handler throws an error (database timeout, API failure, etc.)
5. Middleware catches the error
6. Middleware calls reportFailure() with all the payment details
7. Claim appears in your dashboard
8. You review and approve → user gets refunded

The SDK knows the transaction hash, user wallet, amount, asset, and network from the settlement—you don't need to track any of it.


Advanced Options

Filter Which Errors to Report

Not all errors deserve refunds. Use shouldReport to filter:

refundProtection: {
  apiKey: process.env.REFUND_API_KEY!,
  shouldReport: (error) => {
    // Don't refund for user-caused errors
    if (error.message.includes('Invalid input')) return false;
    if (error.message.includes('Rate limited')) return false;
    return true;
  },
}

Callbacks

Track what's happening:

refundProtection: {
  apiKey: process.env.REFUND_API_KEY!,
  onReport: (claimId, error) => {
    console.log(`Refund claim ${claimId} created for: ${error.message}`);
  },
  onReportError: (reportError, originalError) => {
    console.error(`Failed to report refund: ${reportError.message}`);
    // Alert your team
  },
}

Error Reference

ErrorMeaningFix
"Invalid API key"Key is wrong or revokedCheck dashboard
"Server is not active"Registration inactiveRe-register
"Refunds are not enabled"Facilitator configContact admin
"Claim already exists"Already reported this txSafe to ignore
"No refund wallet configured"Missing wallet for networkAdd in dashboard

FAQ

Do I need to track payment details for refunds?

No. The middleware tracks everything. When your handler throws, it already knows the transaction hash, user wallet, amount, and network.

What if the same transaction is reported twice?

You get a "Claim already exists" error. No duplicate refunds—safe to ignore in retry logic.

Can I do partial refunds?

Yes, with the manual reportFailure function. Set amount to any value up to the original payment.

How fast are refunds processed?

Claims appear instantly. After you approve, payout happens within minutes (depends on network).