NERONERO 402
Advanced

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 Modified without paying).
  • You want to invoke the handler twice — once for verification's payer to 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.

On this page