Skip to main content

Overview

This guide shows you how to implement x402 payments using Dynamic’s wallet infrastructure. We use the x402-fetch library in the example; you can also use x402-axios or other HTTP clients that support x402. Dynamic supports multiple wallet options with x402, from embedded MPC wallets to external wallet integrations.
This recipe uses the Dynamic JavaScript SDK (@dynamic-labs-sdk/client + @dynamic-labs-sdk/evm + @dynamic-labs-sdk/react-hooks). See the React Quickstart for the base setup.

The x402 protocol

The x402 payment protocol defines how onchain payment is negotiated over HTTP 402 responses: the server returns payment requirements; the client signs and retries the request (often with an X-Payment header); a facilitator can verify and help settle onchain. The Fireblocks x402 facilitator runs facilitator infrastructure used by many x402 integrations, including:
  • Zero-fee USDC payments on Base
  • Onchain settlement
  • Compliance and risk screening
  • Integration with official client libraries (x402-fetch, x402-axios—see Installation)
For how 402 flows work in general and when to choose x402 versus Tempo MPP, see the HTTP 402 overview.

Prerequisites

Before implementing x402 with Dynamic, ensure you have:
  • A Dynamic project set up with your environment ID and the JS SDK wired up
  • Basic understanding of HTTP status codes and request/response patterns
  • x402 fetch library installed (or axios/other HTTP library)

Installation

Install the x402 libraries. These libraries add simple request middleware to wrap your HTTP calls with x402 payment handling.
npm install x402-fetch
# or
npm install x402-axios
The x402 libraries are the official packages from Coinbase Developer Platform. You can also use other HTTP libraries with x402, but the official packages provide the best integration experience.

Implementation

x402-fetch expects a viem WalletClient. The JS SDK gives you one via createWalletClientForWalletAccount. Read the active EVM wallet account from useWalletAccounts, narrow it with isEvmWalletAccount, and pass the resulting client to wrapFetchWithPayment.
import { useState } from "react";
import { isEvmWalletAccount } from "@dynamic-labs-sdk/evm";
import { createWalletClientForWalletAccount } from "@dynamic-labs-sdk/evm/viem";
import { useWalletAccounts } from "@dynamic-labs-sdk/react-hooks";
import { wrapFetchWithPayment, decodeXPaymentResponse } from "x402-fetch";

const x402PaymentUrl = "https://example.com/x402-payment";

export function X402PaymentButton() {
  const walletAccount = useWalletAccounts().find(isEvmWalletAccount);
  const [status, setStatus] = useState<string>("");

  const handlePayment = async () => {
    if (!walletAccount) {
      setStatus("Connect an EVM wallet first.");
      return;
    }

    try {
      const walletClient = await createWalletClientForWalletAccount({ walletAccount });

      // Wrap fetch with x402 payment handling
      const fetchWithPayment = wrapFetchWithPayment(fetch, walletClient);

      // Make the request — x402 will handle payment automatically
      const response = await fetchWithPayment(x402PaymentUrl);

      // Optional: decode payment response for debugging
      const paymentResponse = decodeXPaymentResponse(
        response.headers.get("x-payment-response"),
      );
      if (paymentResponse) {
        console.log("Payment details:", paymentResponse);
      }

      setStatus(`Got ${response.status} from ${x402PaymentUrl}`);
    } catch (error) {
      console.error("Payment failed:", error);
      setStatus(`Payment failed: ${(error as Error).message}`);
    }
  };

  return (
    <>
      <button onClick={handlePayment} disabled={!walletAccount}>
        Pay with x402
      </button>
      {status && <p>{status}</p>}
    </>
  );
}
Mount the button anywhere inside your <DynamicProvider> tree. The wallet.address returned by useWalletAccounts is reactive — log out / switch wallets and the component re-renders automatically.