Documentation Index
Fetch the complete documentation index at: https://docs.x402.org/llms.txt
Use this file to discover all available pages before exploring further.
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.
- onAfterVerify — Runs after successful verification.
- onVerifyFailure — Runs on verification failure. Return
{ recovered: true, result } to override.
- onBeforeSettle — Runs before settlement. Return
{ abort: true, reason } to reject.
- onAfterSettle — Runs after successful settlement.
- onSettleFailure — Runs on settlement failure. Return
{ recovered: true, result } to override.
These server-level hooks run for every payment regardless of which extensions are active. Extensions can also register the same hooks scoped to their own key via the hooks property on ResourceServerExtension — those run only when the extension is declared on the route. See Extensions Overview for details.
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,
});
});
from x402 import x402ResourceServer
server = x402ResourceServer(facilitator_client)
async def record_payment(context):
await db.record_payment(
payer=context.result.payer,
transaction=context.result.transaction,
amount=context.requirements.amount,
network=context.requirements.network,
)
server.on_after_settle(record_payment)
For synchronous applications, use x402ResourceServerSync:from x402 import x402ResourceServerSync
server = x402ResourceServerSync(facilitator_client)
def record_payment(context):
db.record_payment(
payer=context.result.payer,
transaction=context.result.transaction,
)
server.on_after_settle(record_payment)
import x402 "github.com/x402-foundation/x402/go"
server := x402.Newx402ResourceServer(facilitatorClient)
server.OnAfterSettle(func(ctx x402.SettleResultContext) error {
return db.RecordPayment(Payment{
Payer: ctx.Result.Payer,
Transaction: ctx.Result.Transaction,
Amount: ctx.Requirements.Amount,
Network: ctx.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
});
Coming soon.
import (
"context"
x402 "github.com/x402-foundation/x402/go"
x402http "github.com/x402-foundation/x402/go/http"
)
// Create resource server
resourceServer := x402.Newx402ResourceServer(
x402.WithFacilitatorClient(facilitatorClient),
)
// Wrap with HTTP server and register hook
httpServer := x402http.Wrappedx402HTTPResourceServer(routes, resourceServer).
OnProtectedRequest(func(ctx context.Context, reqCtx x402http.HTTPRequestContext, routeConfig x402http.RouteConfig) (*x402http.ProtectedRequestHookResult, error) {
apiKey := reqCtx.Adapter.GetHeader("X-API-Key")
if apiKey != "" && isValidApiKey(apiKey) {
return &x402http.ProtectedRequestHookResult{GrantAccess: true}, nil
}
// No valid API key — continue to payment flow
return nil, nil
})
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.
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" };
}
});
from x402 import x402Client
from x402.types import AbortResult
client = x402Client()
async def enforce_spending_limit(context):
max_amount = 10_000_000 # 10 USDC
requested_amount = int(context.selected_requirements.amount)
if requested_amount > max_amount:
return AbortResult(abort=True, reason="Payment exceeds spending limit")
client.on_before_payment_creation(enforce_spending_limit)
For synchronous applications, use x402ClientSync:from x402 import x402ClientSync
from x402.types import AbortResult
client = x402ClientSync()
def enforce_spending_limit(context):
max_amount = 10_000_000 # 10 USDC
requested_amount = int(context.selected_requirements.amount)
if requested_amount > max_amount:
return AbortResult(abort=True, reason="Payment exceeds spending limit")
client.on_before_payment_creation(enforce_spending_limit)
import x402 "github.com/x402-foundation/x402/go"
client := x402.Newx402Client()
client.OnBeforePaymentCreation(func(ctx x402.PaymentCreationContext) (*x402.BeforePaymentCreationResult, error) {
maxAmount := big.NewInt(10_000_000) // 10 USDC
requestedAmount := new(big.Int)
requestedAmount.SetString(ctx.Requirements.Amount, 10)
if requestedAmount.Cmp(maxAmount) > 0 {
return &x402.BeforePaymentCreationResult{
Abort: true,
Reason: "Payment exceeds spending limit",
}, nil
}
return nil, nil
})
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
});
Coming soon.
Coming soon.
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(),
});
}
});
from x402 import x402Facilitator
facilitator = x402Facilitator()
async def update_bazaar_catalog(context):
discovered = extract_discovery_info(
context.payment_payload,
context.requirements,
validate=True,
)
if discovered:
await bazaar_catalog.add(
resource=discovered.resource_url,
description=discovered.description,
mime_type=discovered.mime_type,
accepts=[context.requirements],
)
facilitator.on_after_verify(update_bazaar_catalog)
For synchronous applications, use x402FacilitatorSync:from x402 import x402FacilitatorSync
facilitator = x402FacilitatorSync()
def log_verification(context):
print(f"Verified payment from {context.result.payer}")
facilitator.on_after_verify(log_verification)
import x402 "github.com/x402-foundation/x402/go"
facilitator := x402.Newx402Facilitator()
facilitator.OnAfterVerify(func(ctx x402.FacilitatorVerifyResultContext) error {
discovered := extractDiscoveryInfo(ctx.PaymentPayload, ctx.Requirements)
if discovered != nil {
return bazaarCatalog.Add(BazaarEntry{
Resource: discovered.ResourceURL,
Description: discovered.Description,
MimeType: discovered.MimeType,
Accepts: []PaymentRequirements{ctx.Requirements},
})
}
return nil
})
Hook Chaining
All SDKs support method chaining for registering multiple hooks:
server
.onBeforeVerify(validatePayment)
.onAfterVerify(logVerification)
.onBeforeSettle(checkBalance)
.onAfterSettle(recordTransaction);
(
server
.on_before_verify(validate_payment)
.on_after_verify(log_verification)
.on_before_settle(check_balance)
.on_after_settle(record_transaction)
)
server.
OnBeforeVerify(validatePayment).
OnAfterVerify(logVerification).
OnBeforeSettle(checkBalance).
OnAfterSettle(recordTransaction)
Next, explore: