Add a settlement failure hook to x402 middleware to prevent orphaned side effects
Summary
- Resource servers currently run business logic after
/verifybut before/settle; if settlement fails, irreversible side effects (NFT mint, physical shipment, partner API call) persist without payment. - Official Go and TypeScript middleware buffer HTTP responses yet expose no post-settlement hook, so integrators must re-wrap the response stream themselves to defer actions.
- Providing an optional failure hook lets integrators centralize compensation logic without breaking existing integrations.
Background
- Go middleware calls
c.Next()immediately after successful verification, then invokesSettleafterwards, replaying the buffered response on success.- https://github.com/coinbase/x402/blob/main/go/pkg/gin/middleware.go#L181-L251
- TypeScript Express middleware follows the same pattern:
await next()before runningsettle, patchingres.endto delay writing the response until settlement finishes.- https://github.com/coinbase/x402/blob/main/typescript/packages/x402-express/src/index.ts#L266-L344
- As soon as verification succeeds the handler runs, so if
/settlelater fails there is no built-in way to prevent side effects (NFT mint, fulfillment request, irreversible API call) from persisting without compensation.
Example failure scenario
- Buyer submits a request with
X-PAYMENT; middleware verifies and passes control to the protected handler. - Handler mints an NFT and returns
200 OK. -
/settlelater fails because the payer reused the nonce elsewhere. Middleware rewrites the outgoing HTTP response to 402. - The NFT remains minted; the application had no hook or shared state to schedule compensation or delay the mint.
Impact
- Facilitator outages, blockchain reorgs, or signature replay can cause
/settleto fail. In those cases the customer is not charged, yet the resource server may already have delivered irreversible value. - A strict policy of "only serve content with no side effects" would technically avoid the issue, but it would severely limit x402 to static content delivery and undercuts use cases like physical fulfillment or token issuance that the ecosystem is already exploring.
Proposal
- Add an optional settlement failure hook
- Go: extend
PaymentMiddlewareOptionswith a callback such asOnSettlementFailure(ctx, payload, err). - TypeScript: accept an
onSettlementFailurehandler (sync or async) through middleware config. - The hook fires after
/settleresolves with an error or unsuccessful response, allowing integrators to enqueue compensation, notify operators, or mark the request for reconciliation.
- Go: extend
API Design
router.Use(x402gin.PaymentMiddleware(amount, payTo,
x402gin.WithOnSettlementFailure(func(ctx *gin.Context, payload *types.PaymentPayload, err error) {
handleCompensation(ctx, payload, err)
}),
))
paymentMiddleware(payTo, routes, facilitator, {
onSettlementFailure: ({ request, payment, error }) => {
logger.error("settle failed", { path: request.path, error });
handleCompensation(payment, error);
},
});
If there is a strong reliance on payment finality prior to serving the data, any reasons why not reordering the sequence to:
- Verify
- Settle
- Compute & Return
@fabrice-cheng thank you for your reply Just to confirm my understanding:
-
The spec defines /verify and /settle, but leaves how and when they’re invoked to the application layer (resource server)?
-
And the current middleware is intentionally designed for handlers that don’t perform side effects — meaning it assumes the main logic has no irreversible operations before settlement?
If that’s correct, I agree it makes sense to handle this on the resource-server side by adjusting the call order. Otherwise, it might be worth considering a small addition like a failure hook to make this more robust across use cases.
-
That's correct
-
the middleware is ordering the application logic prior to the settlement to prevent payment occurring despite the app logic failing
I don't think it has a strong assumptions of assuming main logic has no irreversible operations. I'll bring up the idea of a failure hook internally and will keep you posted.
Thanks for clarifying. I understand now that the middleware orders the app logic before settlement mainly to prevent payments from happening when the app logic fails, not because it assumes the app has no side effects.
Appreciate you bringing up the failure hook idea internally. that sounds like a great direction to make error handling and compensation cleaner without changing the flow itself.
Hey @winor30,
I think this is a great idea. In fact, I think the idea of registering hooks/handlers in general is lacking from our SDK's beyond this single use case.
After reviewing it, I personally see value in the following six hooks:
-
beforeVerification -
afterVerification -
beforeSettlement -
afterSettlement -
onVerificationFailure -
onSettlementFailure
With onSettlementFailure being the most critical one builders need. However, I could see the others as being necessary to unlock the ability to add features such as whitelisting/blacklisting buyer addresses, adding conditional settlement logic, adding custom error handling, or logging/analytics.
We have a lot of SDK refactors lined up for #446, and I will add hooks to the list of refactors' to discuss building alongside the v2 upgrade. Thank you for the thoughtful feedback