To integrate Payoneer with V0 by Vercel, generate a payment or payout dashboard UI with V0, create a Next.js API route that calls Payoneer's REST API using OAuth2 client credentials, store credentials in Vercel environment variables, and deploy. Your app can send cross-border payments to freelancers and marketplaces without exposing API secrets to the browser.
Build International Payout Systems with Payoneer and V0
Payoneer is the payment infrastructure behind some of the world's largest marketplaces — Airbnb, Fiverr, Upwork, and Amazon Marketplace all use Payoneer to pay their global seller and freelancer networks. For founders building platforms with contractor payments, creator payouts, or marketplace seller disbursements, integrating Payoneer directly gives access to the same cross-border payment rails those major platforms use. V0 by Vercel can generate the payout management dashboard, payee onboarding flows, and transaction history views — all connected to Payoneer's REST API through secure Next.js API routes.
Payoneer's integration model is program-based: you create a Payoneer program that represents your platform, and payees (your freelancers or sellers) link their Payoneer accounts to your program. Payments flow from your program balance to payees' Payoneer accounts, which they can then withdraw to their local bank, mobile money, or prepaid card. The REST API handles three main workflows: payee management (registering payees and checking their account status), payment creation (initiating disbursements to registered payees), and reporting (transaction history and program balance).
The V0 + Payoneer combination works well for marketplace founders who have the technical vision but need to move quickly — V0 generates the frontend in minutes while Next.js API routes handle the payment business logic securely. For production deployments handling real payouts, thorough testing in Payoneer's sandbox environment is critical before switching to live credentials.
Integration method
Payoneer integrates with V0-generated Next.js apps through server-side API routes that authenticate via OAuth2 client credentials and call Payoneer's REST API for initiating payments and checking payee status. Your Payoneer program credentials are stored as server-only Vercel environment variables and never reach the browser. V0 generates the payout management UI that communicates with your API routes.
Prerequisites
- A Payoneer partner account — apply at payoneer.com/partners or through Payoneer's API program registration; individual accounts cannot access the payment API
- Payoneer API credentials (client ID and client secret) — provided after your API program is approved by Payoneer's partnership team
- Your Payoneer program ID — assigned when your API program is created, used in all API calls to identify your platform
- Access to Payoneer's sandbox environment for testing — sandbox credentials are separate from production and must be requested from Payoneer
- A V0 account at v0.dev and a Vercel account for deployment
Step-by-step guide
Generate the Payout Dashboard UI with V0
Generate the Payout Dashboard UI with V0
Open V0 at v0.dev and describe the payout management interface you want to build. For a marketplace payout system, you likely need two views: an admin view showing all payees and their payout status, and a payee-facing view showing the payee's own earning history and payout settings. Tell V0 specifically what data to display — payee names, Payoneer account status, pending balances, payment amounts — and describe the actions available (pay now button, confirm modal, bulk select for batch payments). V0 generates clean React components with Tailwind CSS and shadcn/ui that handle complex table interactions, modal dialogs, and status indicators out of the box. The financial nature of the payout dashboard means the design should convey trust and clarity — ask V0 for clear status badges (using green for active/paid, yellow for pending, red for failed), confirmation dialogs before irreversible payment actions, and readable transaction histories with proper currency formatting. After generating the initial layout, iterate with V0 to add loading states for payment submission, error handling displays, and success confirmation flows. Push the finalized code to GitHub via V0's Git panel.
Create a payout management dashboard with a top navigation bar showing 'Payout Manager' and tabs for Dashboard, Payees, Transactions, and Settings. The Dashboard shows three summary cards: Program Balance ($12,450 with a wallet icon), Payouts This Month (48 with an arrow-up icon), and Pending Payouts ($3,200 with a clock icon). Below, a Payees table with columns: Name, Email, Country flag + name, Status badge (green Active, yellow Pending Setup, gray Not Registered), Earned This Month, and a Pay button. The Pay button opens a slide-over panel with the payee name, amount input, currency selector, and a Confirm Payment button that calls POST /api/payoneer/payments. Use a clean fintech style with indigo primary color.
Paste this in V0 chat
Pro tip: Add currency formatting to the V0 prompt using Intl.NumberFormat — tell V0 to format all dollar amounts as currency strings (e.g., '$1,234.56') rather than plain numbers, which is important for financial dashboards.
Expected result: A polished payout management dashboard renders in V0's preview with payee table, status badges, payment modal, and transaction history. All actions are wired to /api/payoneer/* endpoints.
Implement OAuth2 Authentication and the Payments API Route
Implement OAuth2 Authentication and the Payments API Route
Payoneer's REST API uses OAuth2 client credentials flow for authentication. POST to Payoneer's token endpoint with your client ID and client secret to receive an access token valid for a limited time (typically 1 hour). The Payoneer API sandbox base URL is https://api.sandbox.payoneer.com and production is https://api.payoneer.com. All endpoints require the Authorization header with your access token as a Bearer token. The payments creation endpoint at /v2/programs/{programId}/payments/payout accepts a JSON body with the payee's Payoneer ID or email, the payment amount, currency, and payment description. Payoneer payments are processed asynchronously — the API response confirms the payment was accepted but not yet processed. Implement token caching using a module-level variable to avoid fetching a new token on every request. For production implementations, use Upstash Redis (available as a Vercel Marketplace integration) for token caching across multiple serverless function instances — module-level variables are not shared between instances. Always validate payment amounts are positive, non-zero, and meet Payoneer's minimum payout requirements (typically $1 USD equivalent) before sending to the API.
1// app/api/payoneer/payments/route.ts2import { NextRequest, NextResponse } from 'next/server';34const PAYONEER_CLIENT_ID = process.env.PAYONEER_CLIENT_ID;5const PAYONEER_CLIENT_SECRET = process.env.PAYONEER_CLIENT_SECRET;6const PAYONEER_PROGRAM_ID = process.env.PAYONEER_PROGRAM_ID;7const PAYONEER_BASE_URL = process.env.PAYONEER_SANDBOX === 'true'8 ? 'https://api.sandbox.payoneer.com'9 : 'https://api.payoneer.com';1011// Simple token cache — use Redis for multi-instance production12let tokenCache: { token: string; expiresAt: number } | null = null;1314async function getAccessToken(): Promise<string> {15 const now = Date.now();16 if (tokenCache && tokenCache.expiresAt > now + 60000) {17 return tokenCache.token;18 }1920 const response = await fetch(`${PAYONEER_BASE_URL}/v2/oauth2/token`, {21 method: 'POST',22 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },23 body: new URLSearchParams({24 grant_type: 'client_credentials',25 client_id: PAYONEER_CLIENT_ID!,26 client_secret: PAYONEER_CLIENT_SECRET!,27 scope: 'read write',28 }).toString(),29 });3031 if (!response.ok) {32 throw new Error(`Payoneer auth failed: ${response.status}`);33 }3435 const data = await response.json();36 tokenCache = {37 token: data.access_token,38 expiresAt: now + data.expires_in * 1000,39 };4041 return tokenCache.token;42}4344export async function POST(request: NextRequest) {45 if (!PAYONEER_CLIENT_ID || !PAYONEER_CLIENT_SECRET || !PAYONEER_PROGRAM_ID) {46 return NextResponse.json({ error: 'Payoneer not configured' }, { status: 500 });47 }4849 let body: { payeeId: string; amount: number; currency: string; description: string };50 try {51 body = await request.json();52 } catch {53 return NextResponse.json({ error: 'Invalid request body' }, { status: 400 });54 }5556 if (!body.payeeId || !body.amount || body.amount <= 0) {57 return NextResponse.json(58 { error: 'payeeId and a positive amount are required' },59 { status: 400 }60 );61 }6263 try {64 const token = await getAccessToken();6566 const response = await fetch(67 `${PAYONEER_BASE_URL}/v2/programs/${PAYONEER_PROGRAM_ID}/payments/payout`,68 {69 method: 'POST',70 headers: {71 Authorization: `Bearer ${token}`,72 'Content-Type': 'application/json',73 },74 body: JSON.stringify({75 payee_id: body.payeeId,76 amount: body.amount,77 currency: body.currency || 'USD',78 description: body.description || 'Platform payout',79 client_reference_id: `payout-${Date.now()}`,80 }),81 }82 );8384 if (!response.ok) {85 const error = await response.json();86 throw new Error(error.message || `Payoneer payment failed: ${response.status}`);87 }8889 const payment = await response.json();9091 return NextResponse.json({92 success: true,93 paymentId: payment.payment_id,94 status: payment.status,95 });96 } catch (error) {97 const message = error instanceof Error ? error.message : 'Unknown error';98 console.error('Payoneer payment failed:', message);99 return NextResponse.json(100 { error: 'Payment failed', details: message },101 { status: 500 }102 );103 }104}Pro tip: Always use the client_reference_id field in Payoneer payment requests — this is your internal payment ID that you can use to reconcile payments in your own database. Set it to a unique value like your database payment record ID to avoid duplicate payments.
Expected result: POST /api/payoneer/payments creates a payout to the specified payee and returns { success: true, paymentId: '...', status: 'pending' }. The payment appears in your Payoneer program's transaction history.
Add Payee Status Lookup and Transaction History Routes
Add Payee Status Lookup and Transaction History Routes
Create additional API routes for the common dashboard data needs: checking if a payee has a registered Payoneer account linked to your program, and retrieving transaction history. The payee status endpoint at /v2/programs/{programId}/payees/{payeeId} returns the payee's account status, helping your dashboard indicate whether a payee can receive payments or needs to complete Payoneer onboarding. For new payees who haven't connected their Payoneer account, Payoneer provides a registration URL you can generate via /v2/programs/{programId}/payees/{payeeId}/registration-link — this sends the payee through Payoneer's account linking flow. Transaction history is available at /v2/programs/{programId}/payments with pagination and filtering by date range, status, and payee. The response includes each payment's ID, payee details, amount, currency, status (pending, processed, failed, returned), and timestamps. For the dashboard's transaction table, query this endpoint with reasonable date ranges and implement client-side pagination to avoid loading all historical transactions at once. Implement proper error handling for the case where a payeeId doesn't exist in your program — Payoneer returns a 404 that should be translated to a user-friendly 'Payee not registered' message in your dashboard.
Add a Payee Profile panel that slides out when clicking a payee's name. The panel shows: payee avatar placeholder with initials, name, email, country, Payoneer account status (from GET /api/payoneer/payee-status/{payeeId}), total paid to date, and a payment timeline showing the last 5 transactions. If the payee is not registered, show a 'Send Registration Link' button that calls POST /api/payoneer/send-registration and shows a success toast. Include a Pay This Payee button at the bottom of the panel that opens the payment modal.
Paste this in V0 chat
1// app/api/payoneer/payees/[payeeId]/route.ts2import { NextRequest, NextResponse } from 'next/server';34const PAYONEER_BASE_URL = process.env.PAYONEER_SANDBOX === 'true'5 ? 'https://api.sandbox.payoneer.com'6 : 'https://api.payoneer.com';78async function getAccessToken(): Promise<string> {9 const response = await fetch(`${PAYONEER_BASE_URL}/v2/oauth2/token`, {10 method: 'POST',11 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },12 body: new URLSearchParams({13 grant_type: 'client_credentials',14 client_id: process.env.PAYONEER_CLIENT_ID!,15 client_secret: process.env.PAYONEER_CLIENT_SECRET!,16 scope: 'read write',17 }).toString(),18 });19 const data = await response.json();20 return data.access_token;21}2223export async function GET(24 request: NextRequest,25 { params }: { params: { payeeId: string } }26) {27 try {28 const token = await getAccessToken();29 const programId = process.env.PAYONEER_PROGRAM_ID;3031 const response = await fetch(32 `${PAYONEER_BASE_URL}/v2/programs/${programId}/payees/${params.payeeId}`,33 { headers: { Authorization: `Bearer ${token}` } }34 );3536 if (response.status === 404) {37 return NextResponse.json({ status: 'not_registered' });38 }3940 if (!response.ok) {41 throw new Error(`Payee lookup failed: ${response.status}`);42 }4344 const payee = await response.json();45 return NextResponse.json({ status: payee.status, payee });46 } catch (error) {47 const message = error instanceof Error ? error.message : 'Unknown error';48 return NextResponse.json({ error: message }, { status: 500 });49 }50}Pro tip: Cache payee status lookups for 5-10 minutes in your dashboard — a payee's Payoneer account status changes infrequently, and fetching it on every page load adds unnecessary API calls that count against rate limits.
Expected result: GET /api/payoneer/payees/{payeeId} returns the payee's Payoneer account status and details. The dashboard shows real-time payee status badges and highlights payees who need to complete their Payoneer account setup.
Configure Vercel Environment Variables and Deploy
Configure Vercel Environment Variables and Deploy
Push your project to GitHub and configure Payoneer credentials in Vercel. Open the Vercel Dashboard → your project → Settings → Environment Variables. Add five variables: PAYONEER_CLIENT_ID with your Payoneer API program client ID, PAYONEER_CLIENT_SECRET with the client secret, PAYONEER_PROGRAM_ID with your program ID, and PAYONEER_SANDBOX set to 'true' for your preview and development environments while leaving it unset (or 'false') for production. None of these variables should have the NEXT_PUBLIC_ prefix — Payoneer credentials are server-only secrets that control payment disbursements. Set different values for Preview (sandbox credentials) and Production (live credentials) environments — this ensures your development and staging environments never accidentally trigger real payments to payees. After configuring and saving all variables, trigger a redeployment. Test the deployed app by looking up a test payee's status and initiating a sandbox test payment. Payoneer's sandbox environment uses test payee accounts — Payoneer's sandbox documentation explains how to create test payee registrations. For RapidDev's guidance on setting up production Payoneer programs and handling compliance requirements for marketplace payout platforms, their team can assist with the integration architecture.
Pro tip: Never use production Payoneer credentials in your Vercel Preview environment — sandbox mistakes in development are recoverable, but accidentally sending real money to test payees in production is not. Use Vercel's environment scoping to keep sandbox and production credentials completely separate.
Expected result: The deployed Vercel app displays payee information and payout history from Payoneer's sandbox. Test payments show up in the Payoneer program dashboard as pending transactions.
Common use cases
Freelancer Payout Management Dashboard
An admin dashboard for a freelance marketplace where operators can see all registered payees, their Payoneer account status, pending payout amounts, and can trigger individual or batch payments with a single click. Transaction history shows every payout with status tracking.
Create a freelancer payout management dashboard with a searchable table of freelancers showing their name, email, Payoneer status badge (Active/Pending/Unregistered), total earned this month, and an amount owed value. Each row has a Pay Now button that opens a modal to confirm the payment amount before submitting to POST /api/payoneer/payments. At the top, show a summary card with total payouts this month, number of active payees, and program account balance. Include a Transactions tab showing payment history with date, payee name, amount, currency, and status. Use a clean fintech dashboard style with green for success states.
Copy this prompt to try it in V0
Creator Platform Mass Payout System
A monthly payout processor for a content creator platform that calculates each creator's earnings, validates their Payoneer account is active, and initiates batch payments. Creators see their own payout dashboard showing earnings, payout schedule, and payment history.
Build a creator earnings dashboard with a left panel showing the current month's earnings breakdown by content category (chart), total earnings badge, and estimated payout date. The right panel shows payout history in a timeline format with dates, amounts in the creator's local currency, and status indicators. Add a Payout Settings section where creators can verify their Payoneer account is linked (status from GET /api/payoneer/payee-status). Include a green 'Payoneer Account Connected' banner or a yellow 'Connect Your Payoneer Account' CTA if not linked.
Copy this prompt to try it in V0
Marketplace Seller Disbursement Interface
A seller-facing portal where marketplace vendors can view their account balance, request immediate payouts, and see upcoming scheduled disbursements. The backend validates minimum payout thresholds before allowing a payout request.
Design a seller portal with a balance card showing available balance in large text with currency, a 'Request Payout' button that validates minimum $50 threshold before calling POST /api/payoneer/payments, and a Recent Transactions table. Add a Payout Schedule section explaining the platform's payout terms (weekly, net-30, etc.). Include a 'How Payoneer Works' explainer section for new sellers who haven't connected their account yet with a Connect Payoneer CTA button. Use a trustworthy marketplace style with your brand colors and plenty of whitespace.
Copy this prompt to try it in V0
Troubleshooting
OAuth2 token request returns 401 with 'invalid_client' error
Cause: The client ID or client secret is incorrect, the credentials are for a different Payoneer environment (sandbox vs. production), or the API program has not been fully approved by Payoneer.
Solution: Verify the credentials from your Payoneer API program dashboard. Sandbox credentials only work against https://api.sandbox.payoneer.com and production credentials only work against https://api.payoneer.com. Ensure PAYONEER_SANDBOX is set correctly in your environment variables to route to the right base URL.
Payment API returns 422 Unprocessable Entity when creating a payment
Cause: The payment request body is missing required fields, the amount is below Payoneer's minimum threshold, or the currency is not supported for the payee's country.
Solution: Check the full error response body — Payoneer returns detailed validation errors explaining exactly which field failed and why. Ensure the amount is above the minimum (typically $1 USD or equivalent), the currency is supported by Payoneer for that payee's country, and the payee_id matches a registered payee in your program.
Payee status returns 'not_registered' for a payee who says they have a Payoneer account
Cause: Having a Payoneer account is not the same as being registered to your specific Payoneer program. Payees must go through a program-specific registration flow to link their Payoneer account to your platform.
Solution: Generate a registration link for the payee using the /v2/programs/{programId}/payees/{payeeId}/registration-link endpoint and send it to them. They'll go through a brief Payoneer flow to link their existing account (or create one) to your program. After completing this flow, their status will change to 'Active' in your program.
Payments show as 'pending' for hours without updating to 'processed'
Cause: This is normal behavior for cross-border Payoneer payments — processing times vary by destination country and payment method. Sandbox payments may take longer or not update at all.
Solution: Set up Payoneer webhooks to receive payment status updates rather than polling the API. Configure a webhook endpoint at /api/payoneer/webhooks in your Vercel app and register it in your Payoneer program settings. Payoneer sends webhook events when payment status changes to processed, failed, or returned.
Best practices
- Always test thoroughly in Payoneer's sandbox environment before enabling production credentials — payment errors with real money cannot be easily reversed
- Store the client_reference_id you send with each payment in your own database before sending — this allows reconciliation and prevents duplicate payments if your API route retries on network error
- Implement idempotency in your payment route using the client_reference_id — if the same client_reference_id is sent twice, Payoneer rejects the duplicate rather than sending a double payment
- Use Payoneer webhooks rather than polling the payments API for status updates — webhooks deliver status changes in real time and reduce API call volume
- Separate sandbox and production credentials using Vercel's environment scoping — Preview environments should always use sandbox credentials to prevent accidental real money movement
- Display the client_reference_id (your internal payment ID) in the dashboard UI alongside Payoneer's payment_id so your support team can look up payments in both systems
- Validate payee Payoneer account status before showing a Pay button — if the payee is not registered, show a 'Send Registration Link' option instead to avoid payment failures
Alternatives
Use Stripe Connect instead of Payoneer if your marketplace is primarily US/EU-focused and you want instant payouts with Stripe's superior developer experience and documentation.
Choose Stripe instead for simpler direct payment collection — Stripe's Payouts feature handles contractor payments within Stripe's ecosystem for businesses already using Stripe for payment collection.
Consider Adyen for enterprise marketplace payouts if you need deeper financial infrastructure integration — Adyen's MarketPay product handles complex multi-party payment flows for large marketplaces.
Frequently asked questions
Does Payoneer require approval before I can use its API?
Yes — Payoneer's mass payout API is not self-serve. You must apply for a Payoneer Partner/API program through their partnership team, describe your use case (marketplace payouts, contractor payments, etc.), and pass a review process. Access to the API is granted after approval. Individual Payoneer account holders cannot directly access the payout API — you need an approved API program.
What is the difference between a Payoneer payee and a Payoneer account holder?
A Payoneer account holder has a regular Payoneer account for receiving money. A payee in your context is someone registered to your specific API program. Even if someone already has a Payoneer account, they must go through your program's registration flow (via a link you generate) to be a payee in your program and receive payments through your platform. Once registered, their program payee account links to their existing Payoneer account.
Can I use Payoneer to collect payments from customers, or only to send payouts?
Payoneer is primarily a payout platform — it excels at sending money to payees worldwide. For collecting payments from customers, Payoneer has some billing solutions but they're not as developer-friendly as Stripe or Adyen. Most platforms use Stripe or another payment gateway for customer-facing payment collection and Payoneer specifically for cross-border contractor or seller disbursements.
What currencies does Payoneer support for payouts?
Payoneer supports payouts in 150+ currencies and to 190+ countries. The most commonly used payout currencies are USD, EUR, GBP, CAD, AUD, and JPY. Payoneer handles the currency conversion when a payee in a local-currency country receives a USD payment — the conversion happens at Payoneer's rates when the payee withdraws to their local bank. Specify the currency in your payment request and Payoneer validates that the currency-country combination is supported.
How long does a Payoneer payout take to reach the recipient?
Processing time depends on the payee's withdrawal method and country. Payoneer account-to-account transfers are typically instant. Bank withdrawals in major markets (US, EU, UK) take 2-5 business days. Bank withdrawals in emerging markets can take 3-10 business days. The initial payment from your program to the payee's Payoneer account is usually confirmed within minutes — the longer processing time is for the payee's withdrawal from Payoneer to their bank.
Is there a minimum amount for Payoneer payouts?
Payoneer typically enforces a minimum payout of $1 USD or equivalent for API-initiated payments, though this can vary by program type and your specific agreement with Payoneer. Payees also have minimum withdrawal thresholds when withdrawing from their Payoneer account to their bank (typically $20-50 depending on the method). Always validate amounts in your API route before sending to Payoneer to catch below-minimum errors before they reach the API.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation