Accept USDC on Solana in 5 Minutes
Parrot is a production-ready, non-custodial payment gateway. Payments flow wallet-to-wallet โ no smart contracts, no custody, zero deployment.
How it works in your SaaS
Three on-chain steps, zero smart contract deployment.
Create Checkout
Your backend creates a payment via API or no-code link in the dashboard.
Customer Pays USDC
Customer opens the hosted checkout, connects Phantom / Solflare, and signs an SPL transfer.
Webhook & Settle
Our indexer detects on-chain confirmation and fires payment.succeeded to your endpoint.
The Only 3 Things You Need
Just like Stripe โ API key, webhook URL, and a hosted checkout page.
1. Secret API Key โ sk_test_... / sk_live_...
Authorization: Bearer sk_.... Keys map directly to your business in the database. Test keys target Solana Devnet; live keys settle real USDC on Mainnet. Generate and manage them in Developers โ API Keys.2. Webhook Endpoint URL
https://yoursaas.com/api/webhooks/parrot). Parrot signs every payload with HMAC-SHA256 and delivers it instantly after confirmation. Add yours in Developers โ Webhooks.3. Hosted Checkout Page
/checkout/[id] or /pay/[code]). Redirect your customer there โ they connect their wallet and sign the transfer. Preview yours from Payment Links.Zero Smart-Contract Deployment
Unlike EVM chains, you never write or audit a contract. USDC already exists on Solana.
Devnet โ Mainnet โ how the toggle works
- โUSDC is pre-deployed by Circle. Devnet mint:
4zMMC9srt5...DU. Mainnet mint:EPjFWdd5Au...1v. Parrot hardcodes both โ you pick neither. - โInstant switch. Use
sk_test_keys โ Devnet RPC + Devnet USDC. Usesk_live_keys โ Mainnet RPC + real USDC. Flip the Test / Live pill in the top-right of your dashboard. - โDirect wallet settlement. Every payment is a native SPL token transfer from the customer's wallet directly into your merchant ATA. No escrow, no custody, no smart-contract attack surface.
| Test Mode | Live Mode | |
|---|---|---|
| Key prefix | sk_test_... | sk_live_... |
| Network | Solana Devnet | Solana Mainnet |
| USDC mint | 4zMMC9srt5...DU | EPjFWdd5Au...1v |
| Real money | No โ faucet USDC | Yes โ real USDC |
| How to get USDC | faucet.circle.com | Any DEX / CEX |
Node / TypeScript SDK
Install the npm package or call the REST API directly. The SDK handles idempotency, USDC unit conversion, and typed responses.
npm install @parrotpay/sdk
import { Parrot } from '@parrotpay/sdk';
// sk_test_... for Devnet ยท sk_live_... for Mainnet
const parrot = new Parrot(process.env.PARROT_SECRET_KEY!);
export async function createCheckoutSession(user: User) {
const payment = await parrot.payments.create(
{
amount: 29.99, // dollars โ auto-converted to 29_990_000 raw units
customer_email: user.email,
metadata: { userId: user.id, plan: 'pro_monthly' },
success_url: 'https://yoursaas.com/app?upgraded=true',
},
{ idempotencyKey: `order_${user.id}` } // safe retries
);
// Redirect the user to the hosted checkout
return payment.checkout_url;
}Verifying Webhook Signatures
Every event is signed with HMAC-SHA256 using your endpoint secret (whsec_...). Replay attacks are rejected if the event is older than 5 minutes.
import { verifyWebhook } from '@parrotpay/sdk';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(req: NextRequest) {
const rawBody = await req.text();
const signature = req.headers.get('x-signature') ?? '';
const isValid = verifyWebhook(rawBody, signature, process.env.PARROT_WEBHOOK_SECRET!);
if (!isValid) return NextResponse.json({ error: 'bad signature' }, { status: 400 });
const event = JSON.parse(rawBody);
if (event.event === 'payment.succeeded') {
const { metadata, tx_signature } = event.data;
// โ
Grant access ยท activate subscription ยท send receipt
await activateSubscription(metadata.userId, metadata.plan);
}
return NextResponse.json({ received: true });
}payment.created | A new payment session was created |
payment.succeeded | On-chain transfer confirmed โ grant access now |
payment.failed | Session expired without a matching transfer |
payment.refunded | Merchant issued a refund transfer |
subscription.created | New subscription started |
subscription.canceled | Subscription was canceled |
invoice.paid | Invoice marked paid after on-chain settle |
Subscriptions & Gating
In Web3, wallets can't be auto-charged. Here's the robust pattern used by production SaaS apps.
Store period end in your DB
When payment.succeeded fires, set expires_at = now() + 30d for that user.
Gate access in middleware
Check expires_at > Date.now(). If expired, redirect to your renewal payment link.
Parrot background indexer
Our always-on worker polls the merchant wallet, confirms finality, writes the ledger, and retries webhook delivery with backoff.