# Onramp Funds to Embedded Wallet APIs with Coinbase

> Step-by-step guide to let users buy crypto with Coinbase Onramp and fund an embedded smart wallet.

> For the complete documentation index, see [llms.txt](/docs/llms.txt).

This recipe demonstrates how to integrate the Coinbase Onramp into an app that uses **embedded smart wallets**. It uses the [Coinbase Developer Platform](https://docs.cdp.coinbase.com/onramp/docs/api-onramp-initializing) and assumes you've configured your Wallet APIs integration using `@account-kit/react`.

> **Goal**: Let users seamlessly buy crypto (e.g. ETH) via card and fund their smart wallet directly.

**Framework**: This recipe shows a **front-end** integration in **Next.js**. The same component can be dropped into any React app.

**Heads-up**: Coinbase Onramp only works on mainnet. Running this demo will purchase **real USDC on Base**. Use a payment method you're comfortable with.

***

<Warning>
  Starting **July 31, 2025**, all Coinbase Onramp and Offramp URLs must be
  securely initialized with the `sessionToken` parameter.
</Warning>

## Prerequisites

* Wallet APIs integrated in your app (see the [React quickstart](/docs/wallets/react/quickstart) or [Core quickstart](/docs/wallets/quickstart)).
* Obtain the necessary API keys all created in the [Coinbase Developer Platform](https://portal.cdp.coinbase.com/)

  * **CDP Project ID**
    * `NEXT_PUBLIC_CDP_PROJECT_ID`
  * **CDP API Credentials** *(used for Secure Initialization with Session Tokens)*
    * `CDP_API_KEY`
    * `CDP_API_SECRET`

***

## Step-by-Step Integration

### 1. Install dependencies

```bash
npm install @coinbase/onchainkit
npm install @coinbase/cdp-sdk
```

### 2. Create the onramp component

This opens a Coinbase-hosted onramp UI in a popup where the user can complete the transaction.

```tsx
// components/on-ramp.tsx
import { useEffect, useState } from "react";
import {
  setupOnrampEventListeners,
  getOnrampBuyUrl,
} from "@coinbase/onchainkit/fund";
import { useSmartAccountClient } from "@account-kit/react";
import type { SuccessEventData, OnrampError } from "@coinbase/onchainkit/fund";

function OnRampPartnerCard() {
  const { address } = useSmartAccountClient({});
  const [onrampUrl, setOnrampUrl] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [isComplete, setIsComplete] = useState(false);
  const [popupWindow, setPopupWindow] = useState<Window | null>(null);

  useEffect(() => {
    if (!address) return;

    // Generate session token via API
    const generateSessionToken = async () => {
      try {
        const response = await fetch("/api/session", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            addresses: [
              {
                address: address,
                blockchains: ["base"],
              },
            ],
            assets: ["USDC"], // or ["ETH"]
          }),
        });

        if (!response.ok) {
          console.log(response);
          throw new Error("Failed to generate session token");
        }

        const { token } = await response.json();

        if (!token) return;

        setIsLoading(true);
        setError(null);

        try {
          const url = getOnrampBuyUrl({
            sessionToken: token,
            presetFiatAmount: 1,
            fiatCurrency: "USD",
          });
          setOnrampUrl(url);
        } catch (err) {
          setError(
            err instanceof Error
              ? err.message
              : "Failed to generate onramp URL",
          );
        } finally {
          setIsLoading(false);
        }
      } catch (err) {
        setError(
          err instanceof Error
            ? err.message
            : "Failed to generate session token",
        );
      }
    };

    generateSessionToken();
  }, [address]);

  useEffect(() => {
    if (!onrampUrl) return;

    const unsubscribe = setupOnrampEventListeners({
      onSuccess: (data?: SuccessEventData) => {
        console.log("Onramp purchase successful:", data);
        setIsComplete(true);
        if (popupWindow && !popupWindow.closed) popupWindow.close();
      },
      onExit: (err?: OnrampError) => {
        if (err) setError("Transaction was cancelled or failed");
        if (popupWindow && !popupWindow.closed) popupWindow.close();
      },
    });

    return unsubscribe;
  }, [onrampUrl, popupWindow]);

  const openOnrampPopup = () => {
    if (!onrampUrl) return;
    const popup = window.open(
      onrampUrl,
      "coinbase-onramp",
      "width=500,height=700,scrollbars=yes,resizable=yes,status=yes,location=yes,toolbar=no,menubar=no",
    );
    if (popup) setPopupWindow(popup);
  };

  if (!address) {
    return (
      <div className="text-center">
        <p className="text-gray-600 mb-4">
          Please connect your wallet to buy crypto.
        </p>
        <button
          disabled
          className="px-6 py-3 bg-gray-300 text-gray-500 rounded-lg cursor-not-allowed"
        >
          Buy Crypto
        </button>
      </div>
    );
  }

  if (isComplete) {
    return (
      <div className="text-center">
        <div className="text-green-600 mb-4">
          <svg
            className="w-12 h-12 mx-auto mb-2"
            fill="currentColor"
            viewBox="0 0 20 20"
          >
            <path
              fillRule="evenodd"
              d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
              clipRule="evenodd"
            />
          </svg>
          <p className="text-lg font-semibold">Purchase Complete!</p>
          <p className="text-sm text-gray-600 mb-4">
            Your transaction has been processed successfully.
          </p>
        </div>
        <button
          onClick={() => setIsComplete(false)}
          className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
        >
          Buy More Crypto
        </button>
      </div>
    );
  }

  if (error) {
    return (
      <div className="text-center">
        <p className="text-red-600 mb-4">Error: {error}</p>
        <button
          onClick={() => window.location.reload()}
          className="px-6 py-3 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors"
        >
          Retry
        </button>
      </div>
    );
  }

  return (
    <div className="text-center">
      <p className="text-gray-600 mb-4">
        Purchase cryptocurrency directly to your wallet using Coinbase Onramp.
      </p>
      <button
        onClick={openOnrampPopup}
        disabled={isLoading || !onrampUrl}
        className={`px-6 py-3 rounded-lg font-semibold transition-colors ${
          isLoading || !onrampUrl
            ? "bg-gray-300 text-gray-500 cursor-not-allowed"
            : "bg-blue-600 text-white hover:bg-blue-700"
        }`}
      >
        {isLoading ? (
          <div className="flex items-center gap-2">
            <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
            Loading...
          </div>
        ) : (
          "Buy Crypto"
        )}
      </button>
    </div>
  );
}

export default OnRampPartnerCard;
```

### 3. Generate session token

```tsx
import { generateJwt } from "@coinbase/cdp-sdk/auth";

const CDP_API_KEY = process.env.CDP_API_KEY;
const CDP_API_SECRET = process.env.CDP_API_SECRET;

export async function POST(request: Request) {
  try {
    const body = await request.json();

    const requestMethod = "POST";
    const requestHost = "api.developer.coinbase.com";
    const requestPath = "/onramp/v1/token";
    let jwt = "";

    try {
      // Use the CDP SDK to generate the JWT
      const token = await generateJwt({
        apiKeyId: CDP_API_KEY!,
        apiKeySecret: CDP_API_SECRET!,
        requestMethod: requestMethod,
        requestHost: requestHost,
        requestPath: requestPath,
        expiresIn: 120, // optional (defaults to 120 seconds)
      });

      jwt = token;
    } catch (error) {
      console.error("Error generating JWT:", error);
      throw error;
    }

    // Generate session token using CDP API with JWT
    const response = await fetch(
      "https://api.developer.coinbase.com/onramp/v1/token",
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${jwt}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(body),
      },
    );

    if (!response.ok) {
      const errorData = await response.text();
      return new Response(
        JSON.stringify({
          error: `Failed to generate session token: ${response.status} ${errorData}`,
        }),
        {
          status: response.status,
          headers: { "Content-Type": "application/json" },
        },
      );
    }

    const data = await response.json();
    return new Response(JSON.stringify({ token: data.token }), {
      status: 200,
      headers: { "Content-Type": "application/json" },
    });
  } catch (error) {
    console.error("Session token generation error:", error);
    return new Response(JSON.stringify({ error: "Internal server error" }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
}
```

### 4. Add to Your App

```tsx
// app/page.tsx
import OnRampPartnerCard from "./components/on-ramp";

export default function Home() {
  return (
    <div className="container mx-auto p-4">
      {/* Your other components */}

      <div className="bg-white rounded-lg shadow-lg p-6">
        <h2 className="text-xl font-semibold mb-4">Buy Crypto</h2>
        <OnRampPartnerCard />
      </div>
    </div>
  );
}
```

***

## Best practices

* Let users change networks/assets in the Coinbase modal (default here is card to ETH).
* For user-level analytics, pass `partnerUserId` when generating the onramp URL.

## Success

Users can now click "Buy Crypto", complete their purchase in a popup, and immediately spend the ETH that lands in their smart wallet.

## Next steps

* **Sponsor their first transaction** -- set up [Gas Manager](/docs/wallets/transactions/sponsor-gas) so new users don't pay gas.

## Resources

* [Coinbase Onramp API](https://docs.cdp.coinbase.com/onramp/docs/api-onramp-initializing)
* [Alchemy Wallet APIs Quickstart](https://www.alchemy.com/docs/wallets/react/quickstart)
* [Coinbase Onchainkit](https://www.npmjs.com/package/@coinbase/onchainkit)
* [Coinbase getOnrampBuyUrl](https://docs.base.org/onchainkit/fund/get-onramp-buy-url)
* [JWT and Session Token Example](https://github.com/coinbase/onramp-demo-application/blob/51733031e49ed4b505291ee7acbdbee429dceb3c/app/utils/sessionTokenApi.ts)