npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zendfi/sdk

v0.8.4

Published

Zero-config TypeScript SDK for ZendFi crypto payments

Readme

@zendfi/sdk

The only crypto payment API built for the AI era

npm version License: MIT

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 here

That'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/sdk

Quick 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_here

3. 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:


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:

  1. Use your zfi_test_ API key
  2. Get free SOL from sol-faucet.com
  3. All transactions use test tokens (zero value)
  4. Devnet SOL is NOT real SOL (zero value)
  5. Use devnet-compatible wallets (Phantom, Solflare support devnet)
  6. Switch network in wallet: Settings → Developer Settings → Change Network → Devnet

Going Live

When ready for production:

  1. Switch to your zfi_live_ API key
  2. 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 limits

Learn 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 pricing

Core 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 APIs
  • read_only - Read-only access
  • create_payments - Create new payments
  • create_subscriptions - Create subscriptions
  • manage_escrow - Manage escrow transactions
  • manage_installments - Manage installment plans
  • read_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 confirmation
  • processing - Payment in progress
  • succeeded - Payment complete
  • canceled - Canceled by user/merchant
  • failed - 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:

  1. Create - Client generates keypair, encrypts with PIN (SDK handles this)
  2. Unlock - Decrypt with PIN once, enable auto-signing
  3. 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 for zendfi.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 Router
  • verifyExpressWebhook(req, secret?) — Express
  • verifyWebhookSignature(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

  1. Go to sol-faucet.com
  2. Paste your Solana wallet address
  3. Click "Airdrop" to get free devnet SOL
  4. 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' event

Examples

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 fulfillment

SaaS 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 access

Marketplace 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 mediates

Troubleshooting

"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.tech

Webhook 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:

  1. Customer hasn't paid yet
  2. Insufficient funds in customer wallet
  3. 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 paid

TypeScript 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


License

MIT © ZendFi


Support

Need help? We're here for you!


Built with ❤️ by the ZendFi team

Making crypto payments as easy as traditional payments.