@zendfi/sdk
v0.8.4
Published
Zero-config TypeScript SDK for ZendFi crypto payments
Maintainers
Readme
@zendfi/sdk
The only crypto payment API built for the AI era
Accept SOL, USDC, and USDT payments in 7 lines of code. Built for e-commerce. Ready for AI agents.
import { zendfi } from '@zendfi/sdk';
const payment = await zendfi.createPayment({
amount: 50,
description: 'Premium subscription',
});
console.log(payment.payment_url); // Send customer hereThat's it. Works for traditional payments AND AI agents. Same API.
Why ZendFi?
| Feature | Stripe | PayPal | ZendFi | |---------|--------|--------|------------| | Fees | 2.9% + $0.30 | 3.5% + $0.49 | 0.6% flat | | Settlement | 7 days | 3-5 days | Instant | | Crypto Native | Via 3rd party | Via 3rd party | Built-in | | AI Agent Ready | ACP | NO | Native | | Setup Time | 30 min | 30 min | 5 min |
Save 81% on fees. Get paid instantly. Scale to AI when ready.
Features
Core Payments (Start Here)
- Embedded Checkout — Drop-in checkout component for your website/app
- Simple Payments — QR codes, payment links, instant settlements
- Payment Links — Reusable checkout pages for social/email
- Webhooks — Real-time notifications with auto-verification
- Test Mode — Free devnet testing with no real money
- Type-Safe — Full TypeScript support with auto-completion
Scale Up (When You Grow)
- Subscriptions — Recurring billing with trials
- Installments — Buy now, pay later flows
- Invoices — Professional invoicing with email
- Payment Splits — Revenue sharing for marketplaces
AI-Ready (Optional Advanced)
- Agent Keys — Scoped API keys for AI with spending limits
- Session Keys — Pre-funded wallets for autonomous payments
- Payment Intents — Two-phase commit for reliable checkout
- PPP Pricing — Auto-adjust prices for 27+ countries
- Smart Payments — AI-optimized payment routing
Don't need AI features? Ignore them. The SDK works perfectly for traditional payments.
Installation
npm install @zendfi/sdk
# or
pnpm add @zendfi/sdk
# or
yarn add @zendfi/sdkQuick Start
1. Get your API key
Sign up at zendfi.tech to get your API keys.
2. Set environment variables
# .env.local or .env
# For testing (free devnet SOL, no real money)
ZENDFI_API_KEY=zfi_test_your_test_key_here
# For production (real crypto on mainnet)
# ZENDFI_API_KEY=zfi_live_your_live_key_here3. Create your first payment
import { zendfi } from '@zendfi/sdk';
// That's it! Auto-configured from ZENDFI_API_KEY
const payment = await zendfi.createPayment({
amount: 50,
description: 'Premium subscription',
customer_email: '[email protected]',
});
// Send customer to checkout
console.log(payment.payment_url);
// => https://pay.zendfi.tech/abc123...Response includes:
{
id: "pay_abc123...",
amount: 50,
currency: "USD",
status: "Pending",
payment_url: "https://pay.zendfi.tech/abc123...",
qr_code: "data:image/png;base64,...",
expires_at: "2025-11-08T20:00:00Z",
mode: "test", // or "live"
}Embedded Checkout
Skip redirects entirely—embed the checkout directly into your website or app. Perfect for seamless user experiences.
Quick Example
import { ZendFiEmbeddedCheckout } from '@zendfi/sdk';
const checkout = new ZendFiEmbeddedCheckout({
linkCode: 'your-payment-link-code',
containerId: 'checkout-container',
mode: 'test',
onSuccess: (payment) => {
console.log('Payment successful!', payment.transactionSignature);
// Redirect to success page or show confirmation
},
onError: (error) => {
console.error('Payment failed:', error.message);
},
});
// Mount the checkout
await checkout.mount();HTML Setup
<div id="checkout-container"></div>
<script type="module">
import { ZendFiEmbeddedCheckout } from '@zendfi/sdk';
// ... (setup code above)
</script>Features
- Drop-in Integration — Works with React, Vue, Next.js, or vanilla JS
- QR Code Generation — Automatic mobile wallet support
- Wallet Connect — Phantom, Solflare, Backpack support
- Real-time Updates — Live payment confirmation polling
- Gasless Transactions — Optional backend-signed payments
- Customizable Theme — Match your brand colors & styles
- TypeScript First — Full type safety and autocomplete
Complete Documentation
For comprehensive guides, React examples, theming, and advanced usage:
- Quick Start:
EMBEDDED_CHECKOUT_QUICKSTART.md - Full Guide:
EMBEDDED_CHECKOUT.md - Implementation Details:
EMBEDDED_CHECKOUT_IMPLEMENTATION.md - React Example:
examples/embedded-checkout-react.tsx - Vanilla JS Example:
examples/embedded-checkout-vanilla.html
API Key Modes
ZendFi uses smart API keys that automatically route to the correct network:
| Mode | API Key Prefix | Network | Gas Costs | Purpose |
|------|---------------|---------|-----------|---------|
| Test | zfi_test_ | Solana Devnet | Free | Development & testing |
| Live | zfi_live_ | Solana Mainnet | ~$0.0001 | Production |
Pro Tip: The SDK auto-detects the mode from your API key prefix. No configuration needed!
Getting Test SOL
For devnet testing:
- Use your
zfi_test_API key - Get free SOL from sol-faucet.com
- All transactions use test tokens (zero value)
- Devnet SOL is NOT real SOL (zero value)
- Use devnet-compatible wallets (Phantom, Solflare support devnet)
- Switch network in wallet: Settings → Developer Settings → Change Network → Devnet
Going Live
When ready for production:
- Switch to your
zfi_live_API key - That's it! The SDK handles everything else automatically
Pricing (The Good News!)
Platform Fee: 0.6% (all-inclusive)
This covers:
- Network transaction fees (~$0.0001 per transaction)
- Payment processing
- Automatic settlements
- Webhook delivery
- No hidden costs
Example:
- Customer pays: $100 USDC
- You receive: $99.40 USDC
- ZendFi fee: $0.60 (covers all network fees + platform)
AI-Ready Features (Optional Advanced)
Building AI agents? ZendFi has native support for autonomous payments with cryptographic security and spending limits.
When Do I Need This?
Use traditional payments if:
- Building e-commerce, SaaS, or creator tools
- User clicks "Pay" button for each transaction
- Standard checkout flow
Use AI features if:
- Building AI agents that make purchases
- Need autonomous payments without per-transaction approval
- Want spending limits and scoped permissions
Quick Example: AI Agent Payment
import { zendfi } from '@zendfi/sdk';
// 1. Create agent key with limited permissions
const agentKey = await zendfi.agent.createKey({
name: 'Shopping Assistant',
agent_id: 'shopping-assistant-v1',
scopes: ['create_payments'],
rate_limit_per_hour: 100,
});
// 2. Create device-bound session key (one-time setup with PIN)
const sessionKey = await zendfi.sessionKeys.create({
userWallet: 'Hx7B...abc',
agentId: 'shopping-assistant-v1',
agentName: 'Shopping Assistant',
limitUSDC: 200,
durationDays: 1,
pin: '123456',
});
// 3. Unlock for payments (client-side)
await zendfi.sessionKeys.unlock(sessionKey.sessionKeyId, '123456');
// 4. AI agent makes payments autonomously (within limits)
const payment = await zendfi.sessionKeys.makePayment(
sessionKey.sessionKeyId,
{
recipientWallet: 'merchant-wallet',
amountUSD: 25.00,
description: 'Coffee order',
}
);
// Done! User approved once, AI pays within limitsLearn more: AI Payments Documentation
Core API Reference
Namespaced APIs
import { zendfi, ZendFiEmbeddedCheckout } from '@zendfi/sdk';
// Embedded Checkout (New!)
const checkout = new ZendFiEmbeddedCheckout({...});
// Traditional Payments (Most Common)
zendfi.createPayment(...)
zendfi.getPayment(...)
zendfi.listPayments(...)
// Payment Links
zendfi.createPaymentLink(...)
// Subscriptions
zendfi.createSubscription(...)
zendfi.cancelSubscription(...)
// AI Features (Optional)
zendfi.agent.createKey(...) // Scoped API keys
zendfi.agent.createSession(...) // Spending limits
zendfi.agent.pay(...) // Autonomous payments
zendfi.intents.create(...) // Two-phase checkout
zendfi.sessionKeys.create(...) // Pre-funded wallets
zendfi.autonomy.enable(...) // User-granted delegation
zendfi.pricing.getPPPFactor(...) // Global pricingCore Payments
Create scoped API keys for AI agents with limited permissions:
// Create an agent API key (prefixed with zai_)
const agentKey = await zendfi.agent.createKey({
name: 'Shopping Assistant',
agent_id: 'shopping-assistant-v1',
scopes: ['create_payments', 'read_analytics'],
rate_limit_per_hour: 500,
});
// IMPORTANT: Save the full_key now - it won't be shown again!
console.log(agentKey.full_key); // => "zai_test_abc123..."
// List agent keys
const keys = await zendfi.agent.listKeys();
// Revoke a key
await zendfi.agent.revokeKey(keyId);Available Scopes:
full- Full access to all APIsread_only- Read-only accesscreate_payments- Create new paymentscreate_subscriptions- Create subscriptionsmanage_escrow- Manage escrow transactionsmanage_installments- Manage installment plansread_analytics- Access analytics data
Agent Sessions
Create sessions with spending limits for user-approved agent actions:
// Create a session with spending limits
const session = await zendfi.agent.createSession({
agent_id: 'shopping-assistant-v1',
user_wallet: 'Hx7B...abc',
limits: {
max_per_transaction: 50, // $50 max per payment
max_per_day: 200, // $200 daily limit
allowed_merchants: ['merchant_123'], // Optional whitelist
},
duration_hours: 24,
});
// Make payments within the session (spending limits enforced!)
const payment = await zendfi.agent.pay({
session_token: session.session_token,
amount: 29.99,
description: 'Premium widget',
auto_gasless: true,
});
if (payment.requires_signature) {
// Device-bound: user must sign
console.log('Sign transaction:', payment.unsigned_transaction);
console.log('Submit to:', payment.submit_url);
} else {
// Auto-signed: payment complete
console.log('Payment confirmed:', payment.transaction_signature);
}
// List active sessions
const sessions = await zendfi.agent.listSessions();
// Get specific session
const session = await zendfi.agent.getSession(sessionId);
// Revoke session
await zendfi.agent.revokeSession(sessionId);Payment Intents
Modern two-phase payment flow for reliable checkout:
// Step 1: Create intent when user starts checkout
const intent = await zendfi.intents.create({
amount: 99.99,
description: 'Premium subscription',
capture_method: 'automatic', // or 'manual' for auth-only
});
// Step 2: Pass client_secret to frontend for confirmation
console.log(intent.client_secret); // cs_abc123...
// Step 3: Confirm when user clicks "Pay"
const confirmed = await zendfi.intents.confirm(intent.id, {
client_secret: intent.client_secret,
customer_wallet: 'Hx7B...abc',
});
// Or cancel if user abandons checkout
await zendfi.intents.cancel(intent.id);Intent Statuses:
requires_payment- Waiting for confirmationprocessing- Payment in progresssucceeded- Payment completecanceled- Canceled by user/merchantfailed- Payment failed
PPP Pricing (Purchasing Power Parity)
Automatically adjust prices based on customer location:
// Get PPP factor for a country
const factor = await zendfi.pricing.getPPPFactor('BR');
// {
// country_code: 'BR',
// country_name: 'Brazil',
// ppp_factor: 0.35,
// adjustment_percentage: 35.0,
// currency_code: 'BRL'
// }
// Calculate localized price
const basePrice = 100;
const localPrice = basePrice * factor.ppp_factor;
console.log(`$${localPrice} for Brazilian customers`); // $35
// List all supported countries
const factors = await zendfi.pricing.listFactors();
// Get AI pricing suggestion
const suggestion = await zendfi.pricing.getSuggestion({
agent_id: 'my-agent',
base_price: 99.99,
user_profile: {
location_country: 'BR',
},
});Supported Countries (27+): Argentina, Australia, Brazil, Canada, China, Colombia, Egypt, France, Germany, Ghana, Hong Kong, Hungary, India, Indonesia, Israel, Japan, Kenya, Mexico, Nigeria, Philippines, Poland, South Africa, Thailand, Turkey, Ukraine, United Kingdom, Vietnam, and more.
Optional Helper Utilities
Production-ready utilities to simplify common integration patterns. All helpers are optional, tree-shakeable, and zero-config.
import {
SessionKeyCache,
WalletConnector,
TransactionPoller,
DevTools
} from '@zendfi/sdk/helpers';Why Use Helpers?
- Optional: Import only what you need
- Tree-shakeable: Unused code eliminated by bundlers
- Zero config: Sensible defaults, works out of the box
- Pluggable: Bring your own storage/AI/PIN providers
- Production-ready: Full TypeScript types, error handling
Available Helpers
| Helper | Purpose | Use Case |
|--------|---------|----------|
| SessionKeyCache | Cache encrypted session keys | Avoid re-prompting for PIN |
| WalletConnector | Detect & connect Solana wallets | Phantom, Solflare, Backpack |
| PaymentIntentParser | Parse natural language to payments | AI chat interfaces |
| PINValidator | Validate PIN strength | Device-bound security |
| TransactionPoller | Poll for confirmations | Wait for on-chain finality |
| RetryStrategy | Exponential backoff retries | Handle network failures |
| SessionKeyLifecycle | High-level session key manager | One-liner setup |
| DevTools | Debug mode & test utilities | Development & testing |
Session Key Cache
Cache encrypted session keys to avoid re-prompting users for their PIN:
import { SessionKeyCache, QuickCaches } from '@zendfi/sdk/helpers';
// Use presets
const cache = QuickCaches.persistent(); // 1 hour localStorage
// Use with device-bound session keys
const keypair = await cache.getCached(
sessionKeyId,
async () => {
const pin = await promptUserForPIN();
return await decryptKeypair(pin);
}
);Wallet Connector
Auto-detect and connect to Solana wallets:
import { WalletConnector } from '@zendfi/sdk/helpers';
const wallet = await WalletConnector.detectAndConnect();
console.log(wallet.address);
const signedTx = await wallet.signTransaction(transaction);Transaction Polling
Wait for confirmations with exponential backoff:
import { TransactionPoller } from '@zendfi/sdk/helpers';
const poller = new TransactionPoller({ connection: rpcConnection });
const result = await poller.waitForConfirmation(signature);Session Key Lifecycle
High-level wrapper for complete session key management:
import { SessionKeyLifecycle } from '@zendfi/sdk/helpers';
const lifecycle = new SessionKeyLifecycle(zendfi, {
cache: QuickCaches.persistent(),
autoCleanup: true,
});
await lifecycle.createAndFund({
userWallet: userAddress,
agentId: 'my-agent',
limitUsdc: 100,
});
await lifecycle.pay(5.00, 'Coffee');Full documentation: See Helper Utilities Guide for complete API reference and examples.
Autonomous Delegation
Enable agents to make payments without per-transaction approval:
// Enable autonomous mode for a wallet
await zendfi.autonomy.enable(sessionKeyId, {
max_amount_usd: 100, // Total amount, not per-day
duration_hours: 24, // Duration
delegation_signature: sig, // Required signature
});
// Check autonomy status
const status = await zendfi.autonomy.getStatus(walletAddress);
// Revoke delegation
await zendfi.autonomy.revoke(delegateId);Session Keys (Device-Bound Non-Custodial)
Session keys are TRUE non-custodial wallets where:
- Client generates keypair (backend NEVER sees private key)
- PIN encryption using Argon2id + AES-256-GCM
- Device fingerprint binding for security
- Autonomous payments within spending limits
The Flow:
- Create - Client generates keypair, encrypts with PIN (SDK handles this)
- Unlock - Decrypt with PIN once, enable auto-signing
- Pay - Make payments instantly without re-entering PIN
// Create a device-bound session key
const key = await zendfi.sessionKeys.create({
userWallet: 'Hx7B...abc',
agentId: 'shopping-assistant-v1',
agentName: 'AI Shopping Assistant',
limitUSDC: 100,
durationDays: 7,
pin: '123456', // SDK encrypts keypair with this
generateRecoveryQR: true,
});
console.log(`Session key: ${key.sessionKeyId}`);
console.log(`Session wallet: ${key.sessionWallet}`);
console.log(`Recovery QR: ${key.recoveryQR}`);
// Session key is auto-unlocked after create()
// Make payments without PIN!
const payment = await zendfi.sessionKeys.makePayment(
key.sessionKeyId,
{
recipientWallet: '8xYZA...',
amountUSD: 5.0,
description: 'Coffee purchase',
}
);
// Or unlock an existing session key
await zendfi.sessionKeys.unlock(key.sessionKeyId, '123456');
// Check status
const status = await zendfi.sessionKeys.getStatus(key.sessionKeyId);
console.log(`Active: ${status.isActive}`);
console.log(`Remaining: $${status.remainingUSDC}`);
console.log(`Spent: $${status.usedAmountUSDC}`);
// Revoke when done
await zendfi.sessionKeys.revoke(key.sessionKeyId);Security Features:
- Backend cannot decrypt - Keys encrypted client-side
- Device fingerprint - Binds key to specific device
- Recovery QR - Migrate to new device
- Auto-signing cache - Instant payments after unlock
Smart Payments
AI-powered payments that automatically apply optimizations:
// Create a smart payment with automatic PPP
const payment = await zendfi.smart.execute({
agent_id: 'my-agent',
user_wallet: 'Hx7B...abc',
amount_usd: 99.99,
country_code: 'BR', // Apply PPP automatically
auto_detect_gasless: true,
description: 'Pro subscription',
});
// Response includes discount applied
console.log(`Original: $${payment.original_amount_usd}`);
console.log(`Final: $${payment.final_amount_usd}`);
// Original: $99.99
// Final: $64.99 (35% PPP discount applied)Device-Bound Flow
For payments requiring user signatures:
// After user signs the transaction locally
const result = await zendfi.smart.submitSigned(
'pay_123...',
signedTransactionBase64
);
console.log(result.status); // "confirmed"
console.log(result.transaction_signature);Tip:
zendfi.smartPayment()is also available as an alias forzendfi.smart.execute().
Complete API Reference
Payments
Create Payment
const payment = await zendfi.createPayment({
amount: 99.99,
currency: 'USD', // Optional, defaults to 'USD'
token: 'USDC', // 'SOL', 'USDC', or 'USDT'
description: 'Annual subscription',
customer_email: '[email protected]',
redirect_url: 'https://yourapp.com/success',
metadata: {
orderId: 'ORD-123',
userId: 'USR-456',
tier: 'premium',
},
});
// Redirect customer to payment page
window.location.href = payment.payment_url;Get Payment Status
const payment = await zendfi.getPayment('pay_abc123...');
console.log(payment.status);
// => "Pending" | "Confirmed" | "Failed" | "Expired"List Payments (with filters)
const payments = await zendfi.listPayments({
page: 1,
limit: 50,
status: 'Confirmed',
from_date: '2025-01-01',
to_date: '2025-12-31',
});
console.log(`Found ${payments.pagination.total} payments`);
payments.data.forEach(payment => {
console.log(`${payment.id}: $${payment.amount} - ${payment.status}`);
});Payment Links
Create shareable checkout URLs that can be reused multiple times.
Create Payment Link
const link = await zendfi.createPaymentLink({
amount: 29.99,
description: 'Premium Course',
max_uses: 100, // Optional: limit usage
expires_at: '2025-12-31T23:59:59Z', // Optional
metadata: {
product_id: 'course-123',
},
});
// Share this URL with customers
console.log(link.hosted_page_url);
// => https://pay.zendfi.tech/link/abc123
// Or use the QR code
console.log(link.qr_code);Get Payment Link
const link = await zendfi.getPaymentLink('link_abc123');
console.log(`Used ${link.uses_count}/${link.max_uses} times`);List Payment Links
const links = await zendfi.listPaymentLinks();
links.forEach(link => {
console.log(`${link.description}: ${link.hosted_page_url}`);
});Subscriptions
Recurring crypto payments made easy.
Create Subscription Plan
const plan = await zendfi.createSubscriptionPlan({
name: 'Pro Plan',
description: 'Premium features + priority support',
amount: 29.99,
interval: 'monthly', // 'daily', 'weekly', 'monthly', 'yearly'
interval_count: 1, // Bill every X intervals
trial_days: 7, // Optional: free trial
metadata: {
features: ['analytics', 'api-access', 'priority-support'],
},
});Subscribe a Customer
const subscription = await zendfi.createSubscription({
plan_id: plan.id,
customer_email: '[email protected]',
customer_wallet: '6DSVnyAQrd9jUWGivzT18kvW5T2nsokmaBtEum63jovN',
metadata: {
user_id: '12345',
},
});
console.log(subscription.current_period_end);Cancel Subscription
const cancelled = await zendfi.cancelSubscription(subscription.id);
console.log(`Cancelled. Active until ${cancelled.current_period_end}`);Installment Plans
Split large purchases into scheduled payments.
Create Installment Plan
const plan = await zendfi.createInstallmentPlan({
total_amount: 500,
num_installments: 5, // 5 payments of $100 each
interval_days: 30, // One payment every 30 days
customer_email: '[email protected]',
customer_wallet: '6DSVnyAQrd9jUWGivzT18kvW5T2nsokmaBtEum63jovN',
description: 'MacBook Pro - Installment Plan',
metadata: {
product_id: 'macbook-pro-16',
},
});
console.log(`Created plan with ${plan.num_installments} installments`);Get Installment Plan
const plan = await zendfi.getInstallmentPlan(plan.id);
console.log(`Status: ${plan.status}`);
// => "active" | "completed" | "defaulted" | "cancelled"List Customer's Installment Plans
const customerPlans = await zendfi.listCustomerInstallmentPlans(
'6DSVnyAQrd9jUWGivzT18kvW5T2nsokmaBtEum63jovN'
);Cancel Installment Plan
await zendfi.cancelInstallmentPlan(plan.id);Invoices
Professional invoices with crypto payment options.
Create Invoice
const invoice = await zendfi.createInvoice({
customer_email: '[email protected]',
customer_name: 'Acme Corp',
due_date: '2025-12-31',
line_items: [
{
description: 'Website Design',
quantity: 1,
unit_price: 2500,
},
{
description: 'Logo Design',
quantity: 3,
unit_price: 500,
},
],
notes: 'Payment due within 30 days',
metadata: {
project_id: 'proj-456',
},
});
console.log(`Invoice #${invoice.invoice_number} created`);Send Invoice via Email
const sent = await zendfi.sendInvoice(invoice.id);
console.log(`Invoice sent to ${sent.sent_to}`);
console.log(`Payment URL: ${sent.payment_url}`);List Invoices
const invoices = await zendfi.listInvoices();
invoices.forEach(inv => {
console.log(`${inv.invoice_number}: $${inv.total_amount} - ${inv.status}`);
});Webhooks
Get notified when payments are confirmed, subscriptions renew, etc.
Supported Events
'payment.created'
'payment.confirmed'
'payment.failed'
'payment.expired'
'subscription.created'
'subscription.activated'
'subscription.canceled'
'subscription.payment_failed'
'split.completed'
'split.failed'
'installment.due'
'installment.paid'
'installment.late'
'escrow.funded'
'escrow.released'
'escrow.refunded'
'escrow.disputed'
'invoice.sent'
'invoice.paid'Next.js App Router (Recommended)
// app/api/webhooks/zendfi/route.ts
import { createNextWebhookHandler } from '@zendfi/sdk/nextjs';
export const POST = createNextWebhookHandler({
secret: process.env.ZENDFI_WEBHOOK_SECRET!,
handlers: {
'payment.confirmed': async (payment) => {
// Payment is verified and typed!
console.log(`💰 Payment confirmed: $${payment.amount}`);
// Update your database
await db.orders.update({
where: { id: payment.metadata.orderId },
data: {
status: 'paid',
transaction_signature: payment.transaction_signature,
},
});
// Send confirmation email
await sendEmail({
to: payment.customer_email,
subject: 'Payment Confirmed!',
template: 'payment-success',
});
},
'subscription.activated': async (subscription) => {
console.log(`✅ Subscription activated for ${subscription.customer_email}`);
await grantAccess(subscription.customer_email);
},
'escrow.released': async (escrow) => {
console.log(`🔓 Escrow released: $${escrow.amount}`);
await notifySeller(escrow.seller_email);
},
},
});Next.js Pages Router
// pages/api/webhooks/zendfi.ts
import { createPagesWebhookHandler } from '@zendfi/sdk/nextjs';
export default createPagesWebhookHandler({
secret: process.env.ZENDFI_WEBHOOK_SECRET!,
handlers: {
'payment.confirmed': async (payment) => {
await fulfillOrder(payment.metadata.orderId);
},
},
});
// IMPORTANT: Disable body parser for webhook signature verification
export const config = {
api: { bodyParser: false },
};Express
import express from 'express';
import { createExpressWebhookHandler } from '@zendfi/sdk/express';
const app = express();
app.post(
'/api/webhooks/zendfi',
express.raw({ type: 'application/json' }), // Preserve raw body!
createExpressWebhookHandler({
secret: process.env.ZENDFI_WEBHOOK_SECRET!,
handlers: {
'payment.confirmed': async (payment) => {
console.log('Payment confirmed:', payment.id);
},
},
})
);Webhook Deduplication (Production)
The handlers use in-memory deduplication by default (fine for development). For production, use Redis or your database:
import { createNextWebhookHandler } from '@zendfi/sdk/nextjs';
import { redis } from './lib/redis';
export const POST = createNextWebhookHandler({
secret: process.env.ZENDFI_WEBHOOK_SECRET!,
// Check if webhook was already processed
isProcessed: async (eventId) => {
const exists = await redis.exists(`webhook:${eventId}`);
return exists === 1;
},
// Mark webhook as processed
onProcessed: async (eventId) => {
await redis.set(`webhook:${eventId}`, '1', 'EX', 86400); // 24h TTL
},
handlers: {
'payment.confirmed': async (payment) => {
// This will only run once, even if webhook retries
await processPayment(payment);
},
},
});Manual Webhook Verification
For custom implementations:
import { verifyNextWebhook } from '@zendfi/sdk/webhooks';
export async function POST(request: Request) {
const payload = await verifyNextWebhook(request, process.env.ZENDFI_WEBHOOK_SECRET);
if (!payload) {
return new Response('Invalid signature', { status: 401 });
}
// Handle verified payload
if (payload.event === 'payment.confirmed') {
await handlePayment(payload.data);
}
return new Response('OK');
}Available verifiers:
verifyNextWebhook(request, secret?)— Next.js App RouterverifyExpressWebhook(req, secret?)— ExpressverifyWebhookSignature(payload, signature, secret)— Low-level
Configuration
Environment Variables
| Variable | Required | Description | Example |
|----------|----------|-------------|---------|
| ZENDFI_API_KEY | Yes* | Your ZendFi API key | zfi_test_abc123... |
| ZENDFI_WEBHOOK_SECRET | For webhooks | Webhook signature verification | whsec_abc123... |
| ZENDFI_API_URL | No | Override base URL (for testing) | http://localhost:3000 |
| ZENDFI_ENVIRONMENT | No | Force environment | development |
*Required unless you pass apiKey directly to ZendFiClient
Custom Client Configuration
import { ZendFiClient } from '@zendfi/sdk';
const client = new ZendFiClient({
apiKey: 'zfi_test_abc123...',
baseURL: 'https://api.zendfi.tech', // Optional
timeout: 30000, // 30 seconds (default)
retries: 3, // Auto-retry attempts (default)
idempotencyEnabled: true, // Auto idempotency (default)
debug: false, // Log requests/responses (default: false)
});
// Use custom client
const payment = await client.createPayment({
amount: 50,
description: 'Test payment',
});Using Multiple Clients (Test + Live)
import { ZendFiClient } from '@zendfi/sdk';
// Test client for development
const testClient = new ZendFiClient({
apiKey: process.env.ZENDFI_TEST_API_KEY,
});
// Live client for production
const liveClient = new ZendFiClient({
apiKey: process.env.ZENDFI_LIVE_API_KEY,
});
// Use the appropriate client based on environment
const client = process.env.NODE_ENV === 'production' ? liveClient : testClient;Error Handling
The SDK throws typed errors that you can catch and handle appropriately:
import {
ZendFiError,
AuthenticationError,
ValidationError,
PaymentError,
NetworkError,
RateLimitError,
} from '@zendfi/sdk';
try {
const payment = await zendfi.createPayment({
amount: 50,
description: 'Test',
});
} catch (error) {
if (error instanceof AuthenticationError) {
// Invalid API key
console.error('Authentication failed. Check your API key.');
} else if (error instanceof ValidationError) {
// Invalid request data
console.error('Validation error:', error.message);
} else if (error instanceof PaymentError) {
// Payment-specific error
console.error('Payment failed:', error.message);
} else if (error instanceof NetworkError) {
// Network/timeout error
console.error('Network error. Retrying...');
// SDK auto-retries by default
} else if (error instanceof RateLimitError) {
// Rate limit exceeded
console.error('Rate limit hit. Please slow down.');
} else {
// Generic error
console.error('Unexpected error:', error);
}
}Error Types
| Error Type | When It Happens | How to Handle |
|------------|----------------|---------------|
| AuthenticationError | Invalid API key | Check your API key |
| ValidationError | Invalid request data | Fix request parameters |
| PaymentError | Payment processing failed | Show user-friendly message |
| NetworkError | Network/timeout issues | Retry automatically (SDK does this) |
| RateLimitError | Too many requests | Implement exponential backoff |
| ApiError | Generic API error | Log and investigate |
Testing
Using Test Mode
// .env.local
ZENDFI_API_KEY=zfi_test_your_test_key
// Your code
const payment = await zendfi.createPayment({
amount: 100,
description: 'Test payment',
});
// Payment created on Solana devnet (free test SOL)
console.log(payment.mode); // "test"Getting Test SOL
- Go to sol-faucet.com
- Paste your Solana wallet address
- Click "Airdrop" to get free devnet SOL
- Use this wallet for testing payments
Test Payment Flow
// 1. Create payment
const payment = await zendfi.createPayment({
amount: 10,
description: 'Test $10 payment',
});
// 2. Open payment URL in browser (or send to customer)
console.log('Pay here:', payment.payment_url);
// 3. Customer pays with devnet SOL/USDC
// 4. Check status
const updated = await zendfi.getPayment(payment.id);
console.log('Status:', updated.status); // "Confirmed"
// 5. Webhook fires automatically
// Your webhook handler receives 'payment.confirmed' eventExamples
Embedded Checkout Integration
import { ZendFiEmbeddedCheckout } from '@zendfi/sdk';
// React Component
function CheckoutPage({ linkCode }) {
const containerRef = useRef(null);
useEffect(() => {
const checkout = new ZendFiEmbeddedCheckout({
linkCode,
containerId: 'checkout-container',
mode: 'test',
onSuccess: (payment) => {
// Payment successful - redirect or show confirmation
router.push(`/success?payment=${payment.paymentId}`);
},
onError: (error) => {
// Handle errors
setError(error.message);
},
theme: {
primaryColor: '#8b5cf6',
borderRadius: '16px',
},
});
checkout.mount();
return () => checkout.unmount();
}, [linkCode]);
return <div id="checkout-container" ref={containerRef} />;
}E-commerce Checkout
// 1. Customer adds items to cart
const cart = {
items: [
{ name: 'T-Shirt', price: 25 },
{ name: 'Hoodie', price: 45 },
],
total: 70,
};
// 2. Create payment
const payment = await zendfi.createPayment({
amount: cart.total,
description: `Order: ${cart.items.map(i => i.name).join(', ')}`,
customer_email: user.email,
redirect_url: 'https://yourstore.com/orders/success',
metadata: {
cart_id: cart.id,
user_id: user.id,
items: cart.items,
},
});
// 3. Redirect to checkout
window.location.href = payment.payment_url;
// 4. Handle webhook (payment.confirmed)
// - Mark order as paid
// - Send confirmation email
// - Trigger fulfillmentSaaS Subscription
// 1. Create subscription plan (one-time setup)
const plan = await zendfi.createSubscriptionPlan({
name: 'Pro Plan',
amount: 29.99,
interval: 'monthly',
trial_days: 14,
});
// 2. Subscribe user
const subscription = await zendfi.createSubscription({
plan_id: plan.id,
customer_email: user.email,
customer_wallet: user.wallet,
metadata: {
user_id: user.id,
},
});
// 3. Handle webhooks
// - subscription.activated → Grant access
// - subscription.payment_failed → Send reminder
// - subscription.canceled → Revoke accessMarketplace Escrow
// 1. Buyer purchases from seller
const escrow = await zendfi.createEscrow({
amount: 500,
buyer_email: buyer.email,
seller_email: seller.email,
buyer_wallet: buyer.wallet,
seller_wallet: seller.wallet,
description: 'Freelance project milestone',
metadata: {
project_id: project.id,
milestone: 'design-complete',
},
});
// 2. Buyer pays into escrow
// Funds held securely
// 3. When work is delivered:
await zendfi.approveEscrow(escrow.id, {
approved_by: buyer.email,
});
// Funds released to seller
// OR if there's an issue:
await zendfi.disputeEscrow(escrow.id, {
dispute_reason: 'Work incomplete',
raised_by: buyer.email,
});
// ZendFi team mediatesTroubleshooting
"Authentication failed" error
Problem: Invalid API key
Solution:
# Check your .env file
cat .env | grep ZENDFI_API_KEY
# Make sure it starts with zfi_test_ or zfi_live_
# Get fresh API key from: https://dashboard.zendfi.techWebhook signature verification fails
Problem: Body parser consuming raw request body
Solutions:
Next.js App Router: No action needed ✅
Next.js Pages Router:
export const config = {
api: { bodyParser: false }, // Add this!
};Express:
app.post(
'/webhooks',
express.raw({ type: 'application/json' }), // Use raw() not json()
webhookHandler
);"Payment not found" error
Problem: Using test API key to query live payment (or vice versa)
Solution: Make sure your API key mode matches the payment's mode:
- Test payments: use
zfi_test_key - Live payments: use
zfi_live_key
Payments stuck in "Pending"
Possible causes:
- Customer hasn't paid yet
- Insufficient funds in customer wallet
- Transaction failed on-chain
Debug:
const payment = await zendfi.getPayment(payment_id);
console.log('Status:', payment.status);
console.log('Expires:', payment.expires_at);
// Payments expire after 15 minutes
// Check if it expired before customer paidTypeScript errors with imports
Problem: Module resolution issues
Solution:
// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "bundler", // or "node16"
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}Contributing
We welcome contributions! Here's how to get started:
# Clone the repo
git clone https://github.com/zendfi/zendfi-toolkit.git
cd zendfi-toolkit
# Install dependencies
pnpm install
# Build the SDK
cd packages/sdk
pnpm build
# Run tests (if available)
pnpm test
# Make your changes, then open a PR!Resources
- Documentation: docs.zendfi.tech
- API Reference: docs.zendfi.tech/api
- Dashboard: dashboard.zendfi.tech
- GitHub: github.com/zendfi/zendfi-toolkit
- Discord: discord.gg/zendfi
- Email: [email protected]
License
MIT © ZendFi
Support
Need help? We're here for you!
- Discord: discord.gg/zendfi
- Email: [email protected]
- Bug Reports: GitHub Issues
- Docs: docs.zendfi.tech
Built with ❤️ by the ZendFi team
Making crypto payments as easy as traditional payments.
