Skip to main content
The Client SDK provides methods to subscribe to blockchain events in real-time using viem’s watchContractEvent under the hood. All subscription methods return an object with an unsubscribe function that you should call when you no longer need the watcher.

watchPaymentState

Watch for state changes on a specific payment. This subscribes to ReleaseExecuted, RefundInEscrowExecuted, and RefundPostEscrowExecuted events on the PaymentOperator contract.
const { unsubscribe } = client.watchPaymentState(
  paymentInfoHash,
  (event) => {
    console.log(`Payment event: ${event.eventName}`);

    switch (event.eventName) {
      case 'ReleaseExecuted':
        console.log('Funds released to merchant');
        console.log('Amount:', event.args.amount);
        break;
      case 'RefundInEscrowExecuted':
        console.log('Funds refunded from escrow');
        break;
      case 'RefundPostEscrowExecuted':
        console.log('Funds refunded after escrow period');
        break;
    }
  }
);

// Stop watching when done
unsubscribe();

Signature

watchPaymentState(
  paymentInfoHash: `0x${string}`,
  callback: (event: PaymentOperatorEventLog) => void
): { unsubscribe: () => void }

PaymentOperatorEventLog Type

interface PaymentOperatorEventLog {
  eventName:
    | 'ReleaseExecuted'
    | 'RefundInEscrowExecuted'
    | 'RefundPostEscrowExecuted'
    | 'AuthorizationCreated'
    | 'ChargeExecuted';
  args: {
    paymentInfoHash?: `0x${string}`;
    payer?: `0x${string}`;
    receiver?: `0x${string}`;
    amount?: bigint;
  };
  address: `0x${string}`;
  blockNumber: bigint;
  transactionHash: `0x${string}`;
  logIndex: number;
}

watchRefundRequests

Watch for refund request lifecycle events. This subscribes to RefundRequested, RefundRequestStatusUpdated, and RefundRequestCancelled events on the RefundRequest contract.
const { unsubscribe } = client.watchRefundRequests((event) => {
  switch (event.eventName) {
    case 'RefundRequested':
      console.log('New refund request submitted');
      console.log('Payment:', event.args.paymentInfoHash);
      console.log('Amount:', event.args.amount);
      break;
    case 'RefundRequestStatusUpdated':
      console.log('Refund status changed:', event.args.status);
      // 1 = Approved, 2 = Denied
      break;
    case 'RefundRequestCancelled':
      console.log('Refund request cancelled');
      break;
  }
});

// Stop watching when done
unsubscribe();

Signature

watchRefundRequests(
  callback: (event: RefundRequestEventLog) => void
): { unsubscribe: () => void }
Requires refundRequestAddress to be configured on the client. Throws an error if not set.

RefundRequestEventLog Type

interface RefundRequestEventLog {
  eventName:
    | 'RefundRequested'
    | 'RefundRequestStatusUpdated'
    | 'RefundRequestCancelled';
  args: {
    paymentInfoHash?: `0x${string}`;
    payer?: `0x${string}`;
    receiver?: `0x${string}`;
    amount?: bigint;
    nonce?: bigint;
    status?: number;
  };
  address: `0x${string}`;
  blockNumber: bigint;
  transactionHash: `0x${string}`;
  logIndex: number;
}

watchMyPayments

Watch for new payment authorizations where the connected wallet is the payer. This subscribes to AuthorizationCreated events on the PaymentOperator contract, filtered by the wallet’s address.
const { unsubscribe } = client.watchMyPayments((event) => {
  console.log('New payment authorized!');
  console.log('Event:', event.eventName);        // 'AuthorizationCreated'
  console.log('Hash:', event.args.paymentInfoHash);
  console.log('Receiver:', event.args.receiver);
  console.log('Amount:', event.args.amount);
});

// Stop watching when done
unsubscribe();

Signature

watchMyPayments(
  callback: (event: PaymentOperatorEventLog) => void
): { unsubscribe: () => void }
Requires a walletClient with an account to be configured, since the events are filtered by the payer address.

watchFreezeEvents

Watch for freeze and unfreeze events on a specific Freeze contract. This subscribes to PaymentFrozen and PaymentUnfrozen events.
const freezeAddress = '0x...'; // Freeze contract address

const { unsubscribe } = client.watchFreezeEvents(
  freezeAddress,
  (event) => {
    if (event.eventName === 'PaymentFrozen') {
      console.log('Payment frozen:', event.args.paymentInfoHash);
      console.log('Frozen by:', event.args.caller);
    } else if (event.eventName === 'PaymentUnfrozen') {
      console.log('Payment unfrozen:', event.args.paymentInfoHash);
      console.log('Unfrozen by:', event.args.caller);
    }
  }
);

// Stop watching when done
unsubscribe();

Signature

watchFreezeEvents(
  freezeAddress: `0x${string}`,
  callback: (event: FreezeEventLog) => void
): { unsubscribe: () => void }

FreezeEventLog Type

interface FreezeEventLog {
  eventName: 'PaymentFrozen' | 'PaymentUnfrozen';
  args: {
    paymentInfoHash?: `0x${string}`;
    caller?: `0x${string}`;
  };
  address: `0x${string}`;
  blockNumber: bigint;
  transactionHash: `0x${string}`;
  logIndex: number;
}

Event Types Reference

MethodEvents WatchedContractUse Case
watchPaymentStateReleaseExecuted, RefundInEscrowExecuted, RefundPostEscrowExecutedPaymentOperatorTrack a single payment’s lifecycle
watchRefundRequestsRefundRequested, RefundRequestStatusUpdated, RefundRequestCancelledRefundRequestMonitor refund request workflow
watchMyPaymentsAuthorizationCreated (filtered by payer)PaymentOperatorTrack new payments for your wallet
watchFreezeEventsPaymentFrozen, PaymentUnfrozenFreezeMonitor dispute freeze activity
All subscription methods use viem’s watchContractEvent under the hood. For reliable real-time delivery, configure your publicClient with a WebSocket transport.

Next Steps

Client Quickstart

Review the complete client setup.

Refund Operations

Request and manage refunds.

Escrow Management

Freeze payments and check escrow timing.

Merchant SDK

Learn about the merchant side of X402r.