Onboarding users to a Web3 app still pushes most people away at the first step. Seed phrases, browser extensions, and gas fees turn a two-tap signup into a ten-minute struggle. The Privy API fixes that gap by handing every new user an embedded wallet behind a familiar login: email, SMS, Google, Apple, or an existing wallet like MetaMask. You get a crypto-native user without asking anyone to install a browser extension.
Privy now backs wallets for Blackbird, Friend.tech, OpenSea, and thousands of other apps, and the product spans Ethereum, Solana, and any EVM chain. This guide walks through the full developer workflow: creating a Privy app, wiring the React SDK, verifying tokens on the server, signing transactions with embedded wallets, and shipping webhooks. If you also want to compare options like MetaMask’s developer toolkit, keep this page open and jump back and forth.
TL;DR
- Privy combines embedded wallets with email, SMS, social, and external wallet login under one SDK.
- The React SDK exposes
PrivyProvider,useLogin,useWallets, andusePrivyhooks to cover the full auth and signing flow. @privy-io/server-authverifies access tokens on your backend so you can trust the user ID on every request.- Wallets support Ethereum, Solana, and other EVM chains, with optional exportability and authorization signatures for key operations.
- Webhooks fire on user creation, login, and wallet events, so your database stays in sync without polling.
- The Privy policy engine adds MFA, allowlists, and transaction rules without forking your app code.
What is the Privy API?
Privy is an authentication and wallet infrastructure platform. It gives your app three things: a login UI, an embedded wallet provisioned per user, and a set of REST endpoints for server-side checks. The embedded wallet lives in a secure enclave, so Privy never sees the private key and neither does your backend. Users can export their key later if they want to move to a self-custody wallet; that optionality is a big part of the pitch.
The platform charges per monthly active wallet, which means you can ship a prototype for free and scale pricing as traction grows. The free tier covers 1,000 monthly active users, Pro starts at $149 per month, and Enterprise handles custom SLAs.
Authentication and setup
Start at privy.io and create a new app from the dashboard. You get two values you care about:
- App ID (
clxxxxx...) for the client SDK - App secret for the server SDK
Set allowed login methods (email, SMS, Google, Apple, Farcaster, wallet), pick your default chain, and drop your domain into the allowed origins list. For React, install the SDK:
npm install @privy-io/react-auth
Wrap your app in PrivyProvider:
import { PrivyProvider } from '@privy-io/react-auth';
export default function App({ Component, pageProps }) {
return (
<PrivyProvider
appId={process.env.NEXT_PUBLIC_PRIVY_APP_ID}
config={{
loginMethods: ['email', 'wallet', 'google'],
embeddedWallets: { createOnLogin: 'users-without-wallets' },
defaultChain: { id: 8453 }, // Base
supportedChains: [{ id: 1 }, { id: 8453 }, { id: 137 }],
}}
>
<Component {...pageProps} />
</PrivyProvider>
);
}
The createOnLogin flag provisions an embedded wallet the first time a user logs in without one. You control the supported chains; Solana lives under a separate solanaClusters config.
Core endpoints and SDK calls
Privy’s React SDK handles most flows so you rarely hit raw REST. Still, the server SDK and webhook payloads use the same token format, so knowing the underlying API helps when things go sideways.
Triggering login and reading the user
import { usePrivy, useWallets } from '@privy-io/react-auth';
function LoginButton() {
const { ready, authenticated, login, logout, user } = usePrivy();
const { wallets } = useWallets();
if (!ready) return <p>Loading...</p>;
if (!authenticated) return <button onClick={login}>Sign in</button>;
const embedded = wallets.find((w) => w.walletClientType === 'privy');
return (
<div>
<p>Hi {user.email?.address ?? user.id}</p>
<p>Wallet: {embedded?.address}</p>
<button onClick={logout}>Log out</button>
</div>
);
}
useWallets returns every wallet the user has linked, and the walletClientType field tells you which one Privy created. This is the pattern you follow for Privy embedded wallets.
Signing a transaction
const { wallets } = useWallets();
const wallet = wallets.find((w) => w.walletClientType === 'privy');
async function sendTx() {
const provider = await wallet.getEthereumProvider();
const hash = await provider.request({
method: 'eth_sendTransaction',
params: [{
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb2',
value: '0x38d7ea4c68000', // 0.001 ETH
}],
});
console.log('tx hash', hash);
}
For Solana, swap getEthereumProvider for getSolanaProvider and pass a serialized transaction. If you want to mirror data access patterns from providers like Alchemy, Privy plays nicely alongside them; Privy handles the key, Alchemy handles the RPC.
Verifying tokens on the server
Install the server SDK:
npm install @privy-io/server-auth
Every authenticated request from your frontend carries a Privy access token (JWT). Verify it on the server before you trust any user ID:
import { PrivyClient } from '@privy-io/server-auth';
const privy = new PrivyClient(
process.env.PRIVY_APP_ID,
process.env.PRIVY_APP_SECRET
);
export async function GET(req) {
const auth = req.headers.get('authorization')?.replace('Bearer ', '');
try {
const claims = await privy.verifyAuthToken(auth);
// claims.userId is the Privy user DID
return Response.json({ userId: claims.userId });
} catch (err) {
return new Response('Unauthorized', { status: 401 });
}
}
You can also fetch the full user object (privy.getUser(userId)) to check linked accounts, wallet addresses, and custom metadata.
Exporting an embedded wallet
Privy lets users export their private key any time. The UX is a single hook:
import { useExportWallet } from '@privy-io/react-auth';
const { exportWallet } = useExportWallet();
<button onClick={() => exportWallet()}>Export private key</button>;
Privy shows a secure iframe modal; your app never touches the key material.
Authorization signatures and the policy engine
For sensitive operations (large transfers, new device logins), Privy supports authorization signatures. You define a policy in the dashboard, attach it to your app, and Privy enforces MFA, allowlists, or server-signed approvals before a transaction goes through. Details live in the Privy authorization key guide. Combined with MFA options (TOTP, SMS, passkey), this closes most of the account takeover surface that plain wallets leave open.
Webhooks
Privy posts JSON events to your endpoint on user and wallet lifecycle changes:
curl -X POST https://yourapp.com/webhooks/privy \
-H "Content-Type: application/json" \
-H "svix-id: msg_..." \
-H "svix-signature: v1,..." \
-d '{
"type": "user.created",
"user": { "id": "did:privy:...", "email": { "address": "a@b.com" } }
}'
Verify the svix-signature header with the webhook secret from the dashboard before you write anything to your database.
Common errors and rate limits
A handful of errors show up over and over:
invalid_token: the frontend JWT expired. CallgetAccessToken()fromusePrivyright before you fetch; tokens live for one hour.403 origin_not_allowed: your deploy URL is not in the Privy dashboard allowlist. Addhttps://yourapp.comand any preview domains.wallet_not_ready: you readuseWalletsbeforereadyflipped true. Gate all wallet calls on thereadyflag.- Rate limits: REST endpoints allow 100 requests per second per app on the free tier. Most apps never hit this; if you do, batch
getUsercalls or cache by user ID.
Use Apidog to replay a failing webhook locally. Paste the raw payload into a request, edit the signature header, and hit your dev server repeatedly until the handler passes.
Privy pricing
- Free: up to 1,000 monthly active wallets, core login methods, embedded wallets on EVM + Solana.
- Pro: $149 per month, higher MAW limits, full webhook suite, staging app.
- Enterprise: custom SLA, dedicated support, on-call engineering, custom policy engine rules.
Check privy.io/pricing for current numbers; the tiers shift as the product grows.
Testing the Privy API with Apidog
Privy’s client SDK hides the HTTPS calls, but every token verification, user lookup, and webhook your backend handles is a regular REST request. That is where Apidog earns its keep. Create a Privy collection in Apidog, drop in your app ID and secret as environment variables, and hit endpoints like GET /api/v1/users/{userId} or POST /api/v1/users/{userId}/wallets without leaving the tool.
You can also record webhook payloads from the dashboard, save them as Apidog requests, and replay them against a local tunnel. Set up automated tests that verify a valid JWT returns a user object and an expired one returns 401; run them on every deploy. Download Apidog free and skip the cURL gymnastics. If you already moved off Postman, the side-by-side workflow guide covers the full migration.
FAQ
How is Privy different from Web3Auth or Magic?All three offer embedded wallets, but Privy leans harder on mixed auth (email + wallet + social) and a policy engine that larger apps need. Web3Auth focuses on MPC key splitting; Magic ships a broader magic-link product. Pick Privy when you want both a clean onboarding UI and fine-grained control over what wallets can do.
Does Privy support Solana?Yes. Embedded wallets work on Solana mainnet and devnet, and the React SDK exposes getSolanaProvider() for signing and sending transactions. You can configure both EVM and Solana in the same app.
Can users bring their own wallet?Yes. MetaMask, Coinbase Wallet, WalletConnect, Phantom, and dozens of others work out of the box. Privy treats external wallets as linked accounts, so the same user DID owns both the embedded and external keys.
What happens if Privy goes down?Users keep access to exported wallets, since the key lives in the user’s browser enclave. For production apps, turn on wallet exportability and document a fallback path. See the guide to comparing identity providers for more on vendor risk patterns.
Does Privy support MFA?Yes. TOTP, SMS, and passkeys are all built in, and you can require MFA for specific actions (sending tokens, exporting wallets) via the policy engine.
Is my app’s code running server-side or client-side?Both. Client SDK handles login UI and signing; server SDK verifies tokens and fetches user data. Never ship your app secret to the browser.



