x402 is great until it isn't.
The protocol is simple: you call an API, it returns 402 Payment Required, you sign a payment, and you get access. Fast, trustless, no subscriptions. But there's a catch that nobody talks about enough: what happens when the endpoint is broken?
Your wallet signs. USDC leaves your account. The endpoint returns a 500. You paid for nothing.
This isn't a theoretical problem. Servers crash. Deployments go wrong. Databases run out of connections. External dependencies go offline. In the traditional API world, you get a refund or your subscription covers it. In x402, the payment is already settled on-chain. There is no chargeback button.
We decided to fix this at the SDK level.
The problem in detail
Here's the timeline of a bad x402 request:
- Buyer calls
GET /api/data - Server returns
402 Payment Required— price: $0.01 - Buyer's wallet signs the payment
- Facilitator settles USDC on-chain — money is gone
- Server processes the request… and crashes
- Buyer gets a 500 error
- Buyer has no recourse
This is a fundamental trust problem. If buyers can't trust that they'll get what they pay for, x402 adoption stalls.
Four plugins, four layers of protection
We built four plugins for the @relai-fi/x402 SDK that address this at different stages of the request lifecycle. They can be used individually or stacked together.
1. Shield — global health gate
Shield checks whether your service is healthy before requesting payment. If your database is down or your Redis cache is unreachable, Shield returns 503 immediately — no payment required.
import { shield } from '@relai-fi/x402/plugins'
shield({
healthUrl: 'https://my-api.com/health',
cacheTtlMs: 10000,
})
How it works: On each request, Shield pings your health URL (or runs a custom function). If unhealthy, the beforePaymentCheck hook returns reject: true with a 503 status. The buyer never sees a 402, never signs, never pays.
Results are cached (default 10s) so the health check doesn't add latency to every request.
Best for: Global outages — DB down, external API down, service in maintenance mode.2. Preflight — per-endpoint liveness probe
Preflight goes one level deeper. Instead of checking a global health endpoint, it probes the actual endpoint the buyer wants to call — before asking for payment.
import { preflight } from '@relai-fi/x402/plugins'
preflight({
baseUrl: 'https://my-api.com',
})
How it works: Before returning 402, Preflight sends a lightweight HEAD request with an X-Preflight: true header to the same path the buyer requested. If the endpoint doesn't respond (timeout, 5xx, connection refused), the buyer gets 503 instead of 402.
Per-path results are cached (default 5s), so a broken /api/translate won't affect a working /api/weather.
3. Circuit Breaker — failure history tracking
Shield and Preflight make real-time HTTP probes. That adds a small amount of latency and network traffic. Circuit Breaker takes a different approach: zero extra network requests.
Instead of probing, it watches the outcomes of actual paid requests via the afterSettled hook. If an endpoint fails repeatedly, the circuit "opens" and blocks new payments until the endpoint recovers.
import { circuitBreaker } from '@relai-fi/x402/plugins'
circuitBreaker({
failureThreshold: 5,
resetTimeMs: 30000,
})
How it works:
The circuit has three states:
| State | What happens | Transition |
|---|---|---|
| Closed | Normal operation — requests flow through | Opens after N consecutive failures |
| Open | All requests rejected with 503 | Half-opens after resetTimeMs |
| Half-open | A few test requests allowed through | Closes on success, re-opens on failure |
X-Circuit-State header so clients can observe the state. When open, a Retry-After header tells clients when to try again.
Circuits can be scoped globally or per-path:
circuitBreaker({
failureThreshold: 5,
scope: 'per-path', // default — each endpoint has its own circuit
})
Best for: Endpoints with intermittent failures. Zero-latency protection without extra HTTP requests.
4. Refund — compensate after the fact
Sometimes failure happens after payment settles. The endpoint was up when the buyer paid, but the actual processing failed. Shield, Preflight, and Circuit Breaker can't catch this — the failure happens downstream.
Refund handles this case. It watches afterSettled outcomes and automatically compensates buyers when paid requests fail.
import { refund } from '@relai-fi/x402/plugins'
refund({
triggerCodes: [500, 502, 503],
mode: 'credit',
onRefund: (event) => {
console.log(Refund: $${event.amount} to ${event.payer})
},
})
How it works:
Two modes:
- credit — records an in-memory credit per buyer. On their next request,
beforePaymentCheckskips payment automatically. The buyer gets a free call as compensation. Response includesX-Refund-Credit: applied.
- log — only calls the
onRefundcallback. You handle compensation yourself (database credit, Stripe refund, email notification, etc.).
onRefund callback receives a RefundEvent with full context: payer, transactionId, amount, path, and reason.
Best for: Post-payment failures. Compensating buyers when the endpoint was "up" but the actual logic failed.
Stacking all four together
The plugins run in array order. Here's the full protection stack:
import Relai from '@relai-fi/x402/server'
import { shield, preflight, circuitBreaker, refund } from '@relai-fi/x402/plugins'
const relai = new Relai({
network: 'base',
plugins: [
shield({ healthUrl: 'https://my-api.com/health' }),
preflight({ baseUrl: 'https://my-api.com' }),
circuitBreaker({ failureThreshold: 5 }),
refund({ triggerCodes: [500, 502, 503] }),
],
})
app.get('/api/data', relai.protect({
payTo: '0xYourWallet',
price: 0.01,
}), (req, res) => {
res.json({ data: 'premium content' })
})
What this does, in order:
- Shield — is the whole service up? If not → 503, no payment.
- Preflight — is this specific endpoint alive? If not → 503, no payment.
- Circuit Breaker — has this endpoint been failing repeatedly? If yes → 503, no payment.
- Refund — if everything looked fine but the response still fails → grant a credit for next time.
When to use what
| Scenario | Plugin |
|---|---|
| Database is down | Shield |
| One endpoint is broken | Preflight |
| Endpoint fails intermittently | Circuit Breaker |
| Processing fails after payment | Refund |
| Maximum protection | All four |
- Simple API with one endpoint? — Shield + Refund is probably enough.
- Multi-endpoint service? — Add Preflight for per-route protection.
- Endpoints with flaky dependencies? — Circuit Breaker catches patterns Shield and Preflight miss.
- Want belt-and-suspenders? — Stack all four. The overhead is minimal.
The bigger picture
x402 is a protocol for permissionless payments. That's powerful, but it also means there's no customer support to call when something goes wrong. The protocol doesn't have chargebacks. It doesn't have refund mechanisms. It settles and moves on.
That's by design — and it's what makes x402 fast and trustless. But it creates a gap: who protects the buyer?
These plugins are our answer. The SDK itself becomes the buyer protection layer. It's not a separate service, not a third-party escrow, not a governance vote. It's code that runs in the same middleware stack, at the same speed, with zero external dependencies.
If you're building paid APIs with x402, your buyers deserve to know they won't pay for broken endpoints. These four plugins make that guarantee.
Get started
All four plugins ship with @relai-fi/x402:
npm install @relai-fi/x402
Full documentation: relai.fi/documentation/sdk-plugins
x402 payments are instant and irreversible. Your endpoint protection should be too.
