Plaidly Docs

Next.js Integration

Accept crypto payments in Next.js App Router and Pages Router applications

Next.js Integration

Integrate Plaidly into your Next.js app using the official Node SDK.

npm install @plaidly/node

Environment variables

Add to your .env.local:

PLAIDLY_API_KEY=pk_live_...
PLAIDLY_WEBHOOK_SECRET=whsec_...

Create a checkout API route

// app/api/checkout/route.ts
import { PlaidlyClient } from '@plaidly/node';
import { NextRequest } from 'next/server';

const plaidly = new PlaidlyClient({
  apiKey: process.env.PLAIDLY_API_KEY!,
});

export async function POST(req: NextRequest) {
  const { amount, currency, chain, orderId } = await req.json();

  const session = await plaidly.sessions.create({
    amount,
    currency,
    chain,
    network: 'mainnet',
    callbackUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/api/webhook/plaidly`,
    metadata: { orderId },
  });

  return Response.json({
    sessionId: session.id,
    walletAddress: session.walletAddress,
    expiresAt: session.expiresAt,
  });
}

Webhook handler

// app/api/webhook/plaidly/route.ts
import { verifyWebhookSignature } from '@plaidly/node/webhooks';
import { NextRequest } from 'next/server';

export async function POST(req: NextRequest) {
  const body = await req.text();
  const sig = req.headers.get('x-plaidly-signature') ?? '';

  let event;
  try {
    event = verifyWebhookSignature(
      body,
      sig,
      process.env.PLAIDLY_WEBHOOK_SECRET!,
    );
  } catch {
    return new Response('Invalid signature', { status: 400 });
  }

  if (event.event === 'payment.confirmed') {
    const { orderId } = event.metadata;
    await fulfillOrder(orderId);
  }

  return new Response('OK');
}

async function fulfillOrder(orderId: string) {
  // Your fulfillment logic here
}

Poll session status (client component)

// components/PaymentStatus.tsx
'use client';

import { useEffect, useState } from 'react';

interface Props {
  sessionId: string;
}

export function PaymentStatus({ sessionId }: Props) {
  const [status, setStatus] = useState<string>('pending');

  useEffect(() => {
    const interval = setInterval(async () => {
      const res = await fetch(`/api/sessions/${sessionId}`);
      const data = await res.json();
      setStatus(data.status);
      if (data.status === 'confirmed' || data.status === 'expired') {
        clearInterval(interval);
      }
    }, 3000);

    return () => clearInterval(interval);
  }, [sessionId]);

  return <div>Payment status: {status}</div>;
}

Session status API route

// app/api/sessions/[id]/route.ts
import { PlaidlyClient } from '@plaidly/node';
import { NextRequest } from 'next/server';

const plaidly = new PlaidlyClient({
  apiKey: process.env.PLAIDLY_API_KEY!,
});

export async function GET(
  _req: NextRequest,
  { params }: { params: Promise<{ id: string }> },
) {
  const { id } = await params;
  const session = await plaidly.sessions.get(id);

  return Response.json({
    status: session.status,
    amount: session.amount,
    currency: session.currency,
  });
}

Pages Router

If you are using the Pages Router instead of the App Router:

// pages/api/checkout.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { PlaidlyClient } from '@plaidly/node';

const plaidly = new PlaidlyClient({
  apiKey: process.env.PLAIDLY_API_KEY!,
});

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse,
) {
  if (req.method !== 'POST') {
    return res.status(405).end();
  }

  const { amount, currency, chain, orderId } = req.body;
  const session = await plaidly.sessions.create({
    amount,
    currency,
    chain,
    network: 'mainnet',
    metadata: { orderId },
  });

  res.json({ sessionId: session.id, walletAddress: session.walletAddress });
}

Sandbox testing

Set network: 'testnet' and use the sandbox simulate endpoint to trigger instant confirmations without real transactions:

// Trigger a simulated confirmation in sandbox
await plaidly.sandbox.simulate(session.id);

On this page