Middleware hooks
Customize the merchant middleware's behavior at well-defined extension points.
@nerochain/x402-server exposes its core as two callable functions, gateRequest and settleAfterHandler. The four framework adapters (Express, Hono, Fastify, Next) are thin wrappers around these. If you need behavior the adapters do not provide directly, drop down to the core and assemble your own pipeline.
When to use the core directly
Common reasons:
- You want to log every verified payload before invoking the handler (custom audit trail, analytics).
- You want to skip settlement on cache hits (return
304 Not Modifiedwithout paying). - You want to invoke the handler twice — once for verification's
payerto scope the response, once after settlement. - You're writing a new framework adapter for a stack the SDK does not ship support for.
The core API
import { gateRequest, settleAfterHandler } from "@nerochain/x402-server";
// Stage 1: gate
const gate = await gateRequest(
{ method: req.method, url: req.url, headers: req.headers },
options,
);
if (gate.kind === "challenge")
return respondWith(402, gate.body, gate.headers);
if (gate.kind === "verify_failed")
return respondWith(402, gate.body, gate.headers);
if (gate.kind === "facilitator_error")
return respondWith(502, gate.body, gate.headers);
// gate.kind === "proceed"
// gate carries: paymentPayload, paymentRequirements, verifyResult.payer
// Run merchant handler
const handlerResponse = await runHandler(req, gate.verifyResult.payer);
// Stage 2: settle (if handler succeeded)
if (handlerResponse.status >= 200 && handlerResponse.status < 300) {
const settle = await settleAfterHandler(
{
paymentPayload: gate.paymentPayload,
paymentRequirements: gate.paymentRequirements,
verifyResult: gate.verifyResult,
},
options,
);
if (settle.kind === "ok") {
handlerResponse.headers.set(
"PAYMENT-RESPONSE",
settle.paymentResponseHeader,
);
} else {
return respondWith(502, settle.body, settle.headers);
}
}
return handlerResponse;The returned objects carry their own status, headers, and body. The adapter's job is to translate them into the framework's response API.
Lifecycle hooks via wrapping
The SDK doesn't expose explicit pre/post hooks; instead, customize by wrapping the handler:
const customHandler = async (req: Request) => {
const start = Date.now();
const res = await innerHandler(req);
await metrics.observe({
duration: Date.now() - start,
status: res.status,
payer: req.x402Payer,
});
return res;
};For pre-handler hooks, run code between gate returning proceed and calling the handler. For post-settle hooks, run code between settleAfterHandler returning ok and returning the response.
Skipping settlement
If your handler decides the response is a cache hit and the agent should not be charged:
const handlerResponse = await runHandler(...);
if (handlerResponse.status === 304) {
// Return without calling settleAfterHandler.
// The response has no PAYMENT-RESPONSE header.
return handlerResponse;
}
// Otherwise, settle as usual.The agent will receive a 304 with no settlement receipt. This is wire-format compliant; the agent's readSettlementReceipt(res) returns null and the caller can handle it as "no payment was needed".
Settling before the handler runs
Set settleBeforeHandler: true in the middleware options. The default is false (handler first, settle after); the alternative settles up front and lets the handler run knowing the payment is final.
This is the right default for handlers with side effects you don't want to roll back if settlement fails (sending email, writing to external systems). The trade-off is that a handler that throws after settlement leaves the agent paying for nothing.