Potential settlement-ordering issue: handlers run before settlement in middleware flow
Hi, I noticed a possible settlement-ordering issue in the current middleware flow.
In packages/core/src/middleware/core.ts, successful verification causes the middleware to continue to the protected handler:
286: async processPreHandle(request: PaymentContext): Promise<PaymentMiddlewareResult> {
305: const verification = await this.httpServer.processVerification(
306: this.config.facilitator.url,
307: payment.payload,
308: requirements,
309: )
319: if (verification.isValid) {
320: this.recordPayment(payment, request, verification)
322: return {
323: action: "continue",
324: payment,
325: verification,
326: }
Settlement is handled later in processAfterHandle:
375: async processAfterHandle(
376: payment: X402DecodedPayment,
377: request: PaymentContext,
378: response?: any,
379: ): Promise<PaymentMiddlewareResult> {
388: const settlement = await this.httpServer.processSettlement(
389: this.config.facilitator.url,
390: payment.payload,
391: requirements,
392: )
If settlement succeeds, response headers are attached. If settlement fails, the code records failure, but this occurs after the handler phase:
418: } catch (error) {
419: this.recordFailedSettlement(payment, request, error as Error)
421: return {
422: action: "error",
423: response: this.createPaymentRequiredResponse(
424: request,
425: "Settlement failed",
426: ),
427: error: error as Error,
428: }
429: }
The Express adapter shows the same high-level ordering. It calls handlePaymentMiddleware, and when that result is continue, it overrides res.send so settlement can run after the route handler produces a response:
152: if (result.action === "continue" && result.payment) {
153: // Store payment info on request for later settlement
154: req.payment = {
155: payload: result.payment,
156: verification: result.verification,
157: }
160: const originalSend = res.send
161: res.send = function (body: any) {
162: // Process settlement before sending response
163: core.processAfterHandle(result.payment!, context, body)
The protected handler is then invoked through next():
The concern is the current source-to-sink chain:
source: incoming X-PAYMENT / X-PAYMENT-SIGNATURE header
transform: verification returns action: continue
sink: framework handler runs before processAfterHandle settlement completes
For pure response generation this may be an acceptable design because Express response sending is intercepted. The risk is paid handler side effects: database writes, external API calls, tool execution, compute work, or irreversible state changes can happen before settlement is attempted. If settlement later fails, the middleware can alter the response path, but it cannot necessarily undo side effects that already occurred inside the handler.
Possible hardening directions:
- Offer a blocking-settlement mode that settles before invoking the protected handler.
- Document that handlers protected by the current mode should avoid irreversible side effects before settlement.
- Expose a pre-settlement "verified only" state distinct from a finalized paid state.
- Add adapter tests where the handler mutates state before
res.send and settlement fails, to make the ordering explicit.
Potential settlement-ordering issue: handlers run before settlement in middleware flow
Hi, I noticed a possible settlement-ordering issue in the current middleware flow.
In
packages/core/src/middleware/core.ts, successful verification causes the middleware to continue to the protected handler:Settlement is handled later in
processAfterHandle:If settlement succeeds, response headers are attached. If settlement fails, the code records failure, but this occurs after the handler phase:
The Express adapter shows the same high-level ordering. It calls
handlePaymentMiddleware, and when that result iscontinue, it overridesres.sendso settlement can run after the route handler produces a response:The protected handler is then invoked through
next():The concern is the current source-to-sink chain:
For pure response generation this may be an acceptable design because Express response sending is intercepted. The risk is paid handler side effects: database writes, external API calls, tool execution, compute work, or irreversible state changes can happen before settlement is attempted. If settlement later fails, the middleware can alter the response path, but it cannot necessarily undo side effects that already occurred inside the handler.
Possible hardening directions:
res.sendand settlement fails, to make the ordering explicit.