This Recipe demonstrates how to integrate the Coinbase Onramp into an app that uses Alchemy Embedded Smart Wallets. It uses the Coinbase Developer Platform and assumes you've configured your smart wallet 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.
Starting July 31, 2025, all Coinbase Onramp and Offramp URLs must be
securely initialized with the sessionToken parameter.
-
Smart wallets integrated in your app (see the React quickstart or Core quickstart).
-
Obtain the necessary API keys all created in the Coinbase Developer Platform
- CDP Project ID
NEXT_PUBLIC_CDP_PROJECT_ID
- CDP API Credentials (used for Secure Initialization with Session Tokens)
CDP_API_KEYCDP_API_SECRET
- CDP Project ID
npm install @coinbase/onchainkit
npm install @coinbase/cdp-sdkThis opens a Coinbase-hosted onramp UI in a popup where the user can complete the transaction.
// 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;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" },
});
}
}// 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>
);
}- Let users change networks/assets in the Coinbase modal (default here is card ➜ ETH).
- For user-level analytics you can pass
partnerUserIdwhen generating the onramp URL.
Users can now click "Buy Crypto", complete their purchase in a popup, and immediately spend the ETH that lands in their smart wallet.
- Sponsor their first transaction – set up Gas Manager so new users don't pay gas.