Skip to main content
This page walks through building a complete checkout on the Headless SDK, with React / Next.js and JavaScript examples side by side. New to the SDK? Read How it works first for the architecture and the role of each seam. Wallet connection is now zero-config: the SDK owns the entire Reown AppKit setup. You install only the @walletconnect/pay-* packages and never touch @reown/*, wagmi, or viem directly. You build three things:
  1. A server proxy — routes that forward to the Engine with your secret key.
  2. A browser transport — points the runtime at those routes.
  3. The AppKit provider — one component (<PayAppKitProvider> in React) or one factory call (createPayAppKit in JavaScript).
The wallet seam, the signer, and the clock all come from the SDK. Then usePaymentSession (React) or createPaymentController (JavaScript) ties everything together and gives you a snapshot to render.
The browser never holds the Engine API key. It talks to your server, and your server talks to the WalletConnect Pay Engine — see The Engine API key never reaches the browser.

Prerequisites

  • Node 18+. The React example uses Next.js (App Router); the JavaScript example is framework-neutral.
  • A Reown Project ID — create one at dashboard.reown.com. Enable the headless feature on the project.
  • A WalletConnect Pay Gateway API key for the Engine (server-side). Talk to us to get onboarded.

Install

Install only the Headless SDK. Wallet connectivity (@reown/appkit, wagmi, viem, @solana/web3.js, @tanstack/react-query) comes transitively through @walletconnect/pay-appkit — you don’t add or configure any of it.
@walletconnect/pay-react is the React hook binding — omit it if you’re not using React.

Step 1 — Server proxy (keep the API key server-side)

Create a server-only module that constructs the Engine client once and forwards calls. The key comes from server env and never ships to the browser. This is framework-agnostic — any server works; the example uses Next.js Route Handlers.
lib/server/engine.ts
Then expose one route per Engine call under /api/wcp/payment/[id]. The browser transport (Step 2) calls exactly these paths:
app/api/wcp/payment/[id]/options/route.ts
app/api/wcp/payment/[id]/status/route.ts
Create the same handler for each route the transport uses:
These proxy routes are a starting point, not production-ready — add your own origin allowlist, rate limiting, and auth before shipping. Their only job here is to keep the Engine key off the browser.

Step 2 — Browser transport

On the client, point the runtime at your proxy. createHttpTransport issues requests to ${baseUrl}/payment/:id/..., matching the routes above.
That’s the entire Transport seam. It speaks the same five methods as the server client, but routes through your origin — no key, no CORS.

Step 3 — Set up AppKit (zero-config)

The SDK constructs the AppKit instance, the Wagmi/Solana adapters, and the WalletConnect-owned network set for you, in headless mode (no built-in modal — you render your own wallet picker). You supply only your projectId and metadata. In React, render <PayAppKitProvider> once near the root. It owns AppKit’s client-only construction, the WagmiProvider + QueryClientProvider tree, and an SSR-safe context. In JavaScript, call createPayAppKit and await its async construction.
<PayAppKitProvider> accepts an optional queryClient (a host with its own passes it to share one cache; omit it for a fully internal one) and optional themeVariables (e.g. a host font). Both createPayAppKit and the provider load the Reown modules through a client-only dynamic import, so AppKit’s UI never enters your SSR bundle.

Step 4 — Build the checkout

Assemble the seams and drive the session. The wallet seam comes from the SDK’s wallet-list hook/controller, and the signer is a single built-in call — createAppKitSigner(wallet) — so you no longer wire up signing strategies by hand. clock is browserClock. In React, useAppKitWalletProvider turns the AppKit instance into the WalletProvider seam and a ready-made picker controller (list, search, pagination, the pairing QR URI). Read the instance from getPayAppKitInstance() once usePayAppKit().isReady is true. In JavaScript, createAppKitWalletList is the framework-neutral equivalent.
In React, render the checkout from a route wrapped in your providers:
app/[paymentId]/page.tsx

Step 5 — Render the snapshot

snapshot.state is a single string you switch on. Each state maps to one piece of UI; the named actions advance the flow. The logic is the same for React and JavaScript — the only difference is where the snapshot comes from (usePaymentSession vs controller.getSnapshot()).
That’s a full gateway. Connect → options → (optional KYC) → confirm → sign → settle, all driven by the runtime; you only render and call actions. Once a wallet is connected, disconnectWallet(namespace?) drops one namespace or all of them.

Environment variables

.env.local
In a Vite / non-Next.js host, expose the project ID under that toolchain’s client env convention (e.g. VITE_APPKIT_PROJECT_ID) and keep WCP_WALLET_API_KEY on the server only.

Reference apps

headless-checkout (Next.js)

The full React checkout — <PayAppKitProvider> + usePaymentSession, no @reown/* in the app.

headless-checkout-vanilla

The same checkout with no framework — createPaymentController + manual subscribe + imperative render.

Next steps

Packages Reference

The full public API of pay-core, pay-state, pay-react, and pay-appkit.

API Reference

The Gateway and Payments endpoints behind the SDK.