Client Path
Lesson 6 of 8
15 min

Lifecycle Hooks & Observability

Learning Objectives

  • Understand the three lifecycle hooks
  • Log payments for debugging
  • Abort payments that exceed cost limits
  • Recover from payment failures

Why Hooks?

In production, you need to:

Log payments for auditing
Monitor costs and track spending
Abort payments that exceed budgets
Recover from transient failures

Lifecycle hooks let you insert custom logic at key points in the payment flow.

The Three Hooks

1.onBeforePaymentCreation — Called before creating payment (can abort)
2.onAfterPaymentCreation — Called after successful payment creation
3.onPaymentCreationFailure — Called when payment creation fails (can recover)
typescript
import { x402Client } from "@x402/core/client";
import { registerExactEvmScheme } from "@x402/evm/exact/client";

const client = new x402Client()
  .onBeforePaymentCreation(async (context) => {
    console.log('About to create payment:', {
      network: context.selectedRequirements.network,
      amount: context.selectedRequirements.amount,
      payTo: context.selectedRequirements.payTo,
    });

    // Abort if price exceeds $0.10
    const amountUSDC = parseInt(context.selectedRequirements.amount) / 1e6;
    if (amountUSDC > 0.10) {
      return {
        abort: true,
        reason: 'Price exceeds maximum ($0.10)',
      };
    }

    return undefined; // Continue with payment
  })
  .onAfterPaymentCreation(async (context) => {
    console.log('✅ Payment created successfully');
    console.log('Signature:', context.paymentPayload.signature?.slice(0, 20) + '...');

    // Send to analytics
    await analytics.track('payment_created', {
      network: context.paymentPayload.network,
      amount: context.selectedRequirements.amount,
    });

    return undefined;
  })
  .onPaymentCreationFailure(async (context) => {
    console.error('❌ Payment failed:', context.error.message);

    // Retry on nonce errors
    if (context.error.message.includes('nonce')) {
      console.log('Nonce conflict, retrying...');
      return {
        recovered: true,
        payload: undefined, // Will regenerate
      };
    }

    return undefined; // Don't recover
  });

registerExactEvmScheme(client, { signer });
onBeforePaymentCreation is perfect for implementing cost controls and budget limits!

Real Use Cases

Cost Tracking:

typescript
let totalSpent = 0;

client.onAfterPaymentCreation(async (ctx) => {
  const amountUSDC = parseInt(ctx.selectedRequirements.amount) / 1e6;
  totalSpent += amountUSDC;
  console.log(`Total spent: $${totalSpent.toFixed(4)}`);
  return undefined;
});

Budget Limits:

typescript
const MAX_PER_REQUEST = 0.05; // $0.05

client.onBeforePaymentCreation(async (ctx) => {
  const amountUSDC = parseInt(ctx.selectedRequirements.amount) / 1e6;
  if (amountUSDC > MAX_PER_REQUEST) {
    return { abort: true, reason: 'Exceeds per-request limit' };
  }
  return undefined;
});

Logging to File:

typescript
import fs from 'fs';

client.onAfterPaymentCreation(async (ctx) => {
  fs.appendFileSync('payments.log', `[${new Date().toISOString()}] Paid ${ctx.selectedRequirements.amount} to ${ctx.selectedRequirements.payTo}\n`);
  return undefined;
});

Add cost tracking to a client

Complete the TODOs to track total spending and abort expensive requests

Requirements:

Tracks spending

totalSpent variable is updated after payments

Aborts expensive requests

Payments that exceed budget are aborted

Your Solution