Skip to main content
Hooks allow you to intercept and modify payment lifecycle events on clients, servers and facilitators.

Server Hooks

x402ResourceServer (Transport-agnostic)

Register hooks on the core resource server for verification and settlement lifecycle events. Use cases include logging payments, recording analytics, implementing custom access control or recovering from transient failures.
  • onBeforeVerify — Runs before payment verification. Return { abort: true, reason } to reject, or { skip: true, result } to use a locally produced verification result.
  • onAfterVerify — Runs after successful verification. Return { skipHandler: true, response? } to settle without invoking the resource handler.
  • onVerifyFailure — Runs on verification failure. Return { recovered: true, result } to override.
  • onBeforeSettle — Runs before settlement. Return { abort: true, reason } to reject, or { skip: true, result } to use a locally produced settlement result.
  • onAfterSettle — Runs after successful settlement.
  • onSettleFailure — Runs on settlement failure. Return { recovered: true, result } to override.
  • onVerifiedPaymentCanceled — Runs when a verified payment is not settled because the protected handler throws or returns an error response.
These server-level hooks run for every payment regardless of which extensions are active. Extensions and schemes can also contribute lifecycle hook adapters that run only when their extension key or scheme/network is used. See Extensions Overview for extension-level behavior.
import { x402ResourceServer } from "@x402/core";

const server = new x402ResourceServer(facilitatorClient);

server.onAfterSettle(async (context) => {
  await recordPayment({
    payer: context.result.payer,
    transaction: context.result.transaction,
    amount: context.requirements.amount,
    network: context.requirements.network,
  });
});

x402HTTPResourceServer (HTTP)

Register hooks for HTTP-specific request handling before payment processing. Use cases include bypassing payment for API key holders, granting access to subscribers or blocking specific clients.
  • onProtectedRequest — Runs on every request to a protected route.
    • Return { grantAccess: true } to bypass payment.
    • Return { abort: true, reason } to return 403.
    • Return void to continue to payment flow.
import { x402ResourceServer, x402HTTPResourceServer } from "@x402/core";

const server = new x402ResourceServer(facilitatorClient);
const httpServer = new x402HTTPResourceServer(server, routes);

httpServer.onProtectedRequest(async (context, routeConfig) => {
  const apiKey = context.adapter.getHeader("X-API-Key");

  if (apiKey && await isValidApiKey(apiKey)) {
    return { grantAccess: true };
  }

  // No valid API key — continue to payment flow
});

Client Hooks

x402Client (Transport-agnostic)

Register hooks on the core client for payment payload creation lifecycle events. Common use cases include enforcing spending limits, requiring approval for large payments or logging outgoing transactions.
  • onBeforePaymentCreation — Runs before creating a payment payload. Return { abort: true, reason } to cancel.
  • onAfterPaymentCreation — Runs after successful payload creation.
  • onPaymentCreationFailure — Runs on failure. Return { recovered: true, payload } to provide fallback.
  • onPaymentResponse — Runs after a paid request completes. Return { recovered: true } to tell the transport to retry with a fresh payload.
import { x402Client } from "@x402/core";

const client = new x402Client();

client.onBeforePaymentCreation(async (context) => {
  const maxAmount = BigInt("10000000"); // 10 USDC
  const requestedAmount = BigInt(context.selectedRequirements.amount);

  if (requestedAmount > maxAmount) {
    return { abort: true, reason: "Payment exceeds spending limit" };
  }
});

x402HTTPClient (HTTP)

Register hooks for HTTP-specific payment required handling. Use cases include trying API key authentication before paying or prompting users for payment confirmation.
  • onPaymentRequired — Runs when a 402 response is received.
    • Return { headers } to retry with alternate headers before paying.
    • Return void to proceed directly to payment.
import { x402Client, x402HTTPClient } from "@x402/core";

const client = new x402Client();
const httpClient = new x402HTTPClient(client);

httpClient.onPaymentRequired(async ({ paymentRequired }) => {
  // Try API key authentication first
  const apiKey = process.env.API_KEY;
  if (apiKey) {
    return { headers: { "Authorization": `Bearer ${apiKey}` } };
  }
  // Return void to proceed with payment
});

Facilitator Hooks

x402Facilitator

Register hooks on the facilitator for verification and settlement lifecycle events. Use cases include populating a bazaar discovery catalog, compliance checks or collecting metrics across all processed payments.
  • onBeforeVerify / onAfterVerify / onVerifyFailure — Same pattern as server hooks.
  • onBeforeSettle / onAfterSettle / onSettleFailure — Same pattern as server hooks.
import { x402Facilitator } from "@x402/core";
import { extractDiscoveryInfo } from "@x402/extensions/bazaar";

const facilitator = new x402Facilitator();

facilitator.onAfterVerify(async (context) => {
  // Extract resource info from payment for bazaar catalog
  const discovered = extractDiscoveryInfo(
    context.paymentPayload,
    context.requirements,
    true, // validate
  );

  if (discovered) {
    bazaarCatalog.add({
      resource: discovered.resourceUrl,
      description: discovered.description,
      mimeType: discovered.mimeType,
      x402Version: discovered.x402Version,
      accepts: [context.requirements],
      lastUpdated: new Date().toISOString(),
    });
  }
});

MCP Hooks

x402MCPClient (MCP client)

Register hooks on the MCP client for payment lifecycle events specific to tool calls. Use cases include logging payments, enforcing per-tool spending limits or auditing payment receipts.
  • onPaymentRequired — Runs when a 402 payment required response is received from a tool call. First hook to return a result wins. Return { abort: true } to cancel, { payment } to supply a pre-built payload, or void to proceed with normal payment flow.
  • onBeforePayment — Runs after payment approval but before the payment payload is created.
  • onAfterPayment — Runs after the payment payload is submitted and the tool result is received.
import { x402MCPClient } from "@x402/mcp";

const client = new x402MCPClient(mcpClient, paymentClient);

client
  .onPaymentRequired(async ({ toolName, paymentRequired }) => {
    if (blocklist.has(toolName)) {
      return { abort: true };
    }
    // Return undefined to proceed with normal payment flow
  })
  .onBeforePayment(async ({ toolName, paymentRequired }) => {
    console.log(`Creating payment for tool: ${toolName}`);
  })
  .onAfterPayment(async ({ toolName, paymentPayload, result, settleResponse }) => {
    await auditLog.record({
      tool: toolName,
      transaction: settleResponse?.transaction,
    });
  });

x402MCPServer payment wrapper (MCP server)

Register hooks in the PaymentWrapperConfig.hooks object when creating a payment wrapper. Use cases include rate limiting, logging tool executions or sending payment receipts.
  • onBeforeExecution — Runs after payment verification, before the tool handler executes. Return false to abort execution.
  • onAfterExecution — Runs after the tool handler returns, before settlement.
  • onAfterSettlement — Runs after successful payment settlement.
import { createPaymentWrapper } from "@x402/mcp";

const paid = createPaymentWrapper(resourceServer, {
  accepts,
  hooks: {
    onBeforeExecution: async ({ toolName, paymentPayload }) => {
      if (await isRateLimited(paymentPayload.payer)) {
        return false; // Abort execution
      }
    },
    onAfterExecution: async ({ toolName, result }) => {
      console.log(`Tool ${toolName} executed successfully`);
    },
    onAfterSettlement: async ({ settlement }) => {
      await sendReceipt({ transaction: settlement.transaction });
    },
  },
});

Hook Chaining

Hooks can be chained when registering multiple handlers:
server
  .onBeforeVerify(validatePayment)
  .onAfterVerify(logVerification)
  .onBeforeSettle(checkBalance)
  .onAfterSettle(recordTransaction);

Next, explore: