# How to Add Payments to a Next.js App in 2026

> Complete guide to integrating payments in Next.js using Dodo Payments. Covers App Router, Server Components, overlay checkout, webhooks, and subscription billing.
- **Author**: Ayush Agarwal
- **Published**: 2026-03-23
- **Category**: Payments, Developer Tools, How-To
- **URL**: https://dodopayments.com/blogs/add-payments-nextjs-app

---

Next.js powers most new SaaS applications in 2026. Its combination of speed, developer experience, and the robust App Router makes it the default choice for founders. However, setting up payments still trips developers up. Between App Router quirks, server and client component boundaries, and the nightmare of global tax compliance, there is a lot to get wrong.

Integrating a payment gateway used to mean managing complex state, handling redirects, and building custom webhook listeners from scratch. In 2026, the landscape has shifted toward more integrated solutions. Dodo Payments offers a Merchant of Record (MoR) model that handles the heavy lifting of taxes and compliance while providing a developer-first integration for Next.js.

This guide provides a comprehensive walkthrough of adding payments to a Next.js App Router application. We will cover everything from the initial SDK setup to handling complex subscription lifecycles and protecting routes based on payment status.

## Why Next.js and Dodo Payments?

Next.js has evolved significantly with the introduction of Server Components and Route Handlers. Traditional payment integrations often struggle with these boundaries, requiring messy workarounds to pass data between the server and the client. Dodo Payments is designed with these modern patterns in mind. This means you can leverage the full power of the App Router without compromising on the user experience or security of your payment flow.

> Tax compliance is not a one-time setup. It is a moving target. Rates change, thresholds change, and new jurisdictions add digital services taxes every year. Automating this is not optional if you sell globally.
>
> \- Rishabh Goel, Co-founder & CEO at Dodo Payments

The [overlay checkout](https://docs.dodopayments.com/developer-resources/overlay-checkout) is a client component that works perfectly within the App Router. It allows you to trigger a checkout flow without forcing a full page redirect, maintaining your application's state. This is particularly useful for single-page applications (SPAs) or dashboards where a full page reload would disrupt the user's workflow. On the server side, Dodo's Next.js adapter provides pre-built Route Handlers for webhooks, making it easy to sync payment events with your database. This separation of concerns ensures that your frontend remains fast and responsive while your backend handles the critical business logic of payment processing.

The [overlay checkout](https://docs.dodopayments.com/developer-resources/overlay-checkout) is a client component that works perfectly within the App Router. It allows you to trigger a checkout flow without forcing a full page redirect, maintaining your application's state. On the server side, Dodo's Next.js adapter provides pre-built Route Handlers for webhooks, making it easy to sync payment events with your database.

Beyond the technical integration, using a [Merchant of Record for SaaS](https://dodopayments.com/blogs/merchant-of-record-for-saas) is a strategic choice. Instead of just being a payment gateway, Dodo Payments acts as the legal seller. This means they handle global sales tax (VAT, GST, etc.), compliance, and even chargebacks on your behalf. For a Next.js founder, this means you can focus on building features instead of studying international tax law.

## Architecture Overview

Before we dive into the code, it is important to understand how payments fit into a modern Next.js architecture. A typical setup involves three main parts: the client-side UI, the server-side webhook handler, and the access control layer. This architecture is designed to be both secure and scalable, allowing you to handle thousands of transactions without breaking a sweat.

The client-side UI uses the Dodo Payments SDK to initialize and open the checkout overlay. This is the part of your application that the user interacts with directly. When a user completes a payment, Dodo's servers send a POST request to your webhook Route Handler. This handler is the bridge between the payment gateway and your application's database. It verifies the signature of the incoming request to ensure it is legitimate and then updates your database to reflect the user's new subscription status. Finally, your application uses Middleware or Server Component checks to grant or deny access to paid features. This multi-layered approach ensures that only authorized users can access your premium content.

The client-side UI uses the Dodo Payments SDK to initialize and open the checkout overlay. When a user completes a payment, Dodo's servers send a POST request to your webhook Route Handler. This handler verifies the signature and updates your database to reflect the user's new subscription status. Finally, your application uses Middleware or Server Component checks to grant or deny access to paid features.

```mermaid
flowchart TD
    subgraph Client ["Client (Next.js App)"]
        A[Checkout Button] -->|Initialize SDK| B[Overlay Checkout]
        B -->|User Pays| C[Success/Failure UI]
    end
    subgraph Dodo ["Dodo Payments"]
        B -->|Process Payment| D[Payment Engine]
        D -->|Send Webhook| E[Webhook Event]
    end
    subgraph Server ["Server (Next.js API)"]
        E -->|POST| F[Route Handler]
        F -->|Verify & Process| G[Database]
        H[Middleware/Server Check] -->|Query| G
        H -->|Grant Access| I[Paid Feature]
    end
```

## Step 1: Install the SDK and Adapter

The first step is to install the necessary packages. You will need the client-side checkout SDK and the Next.js adapter for server-side functionality.

```bash
npm install dodopayments-checkout @dodopayments/nextjs
```

The `dodopayments-checkout` package is a lightweight library for the overlay checkout. The `@dodopayments/nextjs` package provides the Route Handlers and utilities specifically designed for the App Router.

## Step 2: Environment Setup

You need to configure your environment variables to securely store your API keys. Create or update your `.env.local` file with the following values.

```env
DODO_PAYMENTS_API_KEY=your_api_key_here
DODO_WEBHOOK_SECRET=your_webhook_secret_here
DODO_PAYMENTS_ENVIRONMENT=test_mode
NEXT_PUBLIC_DODO_PAYMENTS_MODE=test
```

The `DODO_PAYMENTS_API_KEY` is used for server-side requests like creating checkout sessions. The `DODO_WEBHOOK_SECRET` is essential for verifying that incoming webhooks actually come from Dodo Payments. We also set a public variable for the client-side SDK mode.

## Step 3: Create a Checkout Button Component

In Next.js, the checkout UI must be a Client Component because it interacts with the browser's window and the Dodo Payments SDK. We will create a `CheckoutButton` component that handles initialization and opening the overlay.

```tsx
// components/CheckoutButton.tsx
"use client";

import { DodoPayments } from "dodopayments-checkout";
import { useEffect, useState } from "react";

interface CheckoutButtonProps {
  productId: string;
  className?: string;
}

export default function CheckoutButton({
  productId,
  className,
}: CheckoutButtonProps) {
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    // Initialize the SDK once when the component mounts
    DodoPayments.Initialize({
      mode: process.env.NEXT_PUBLIC_DODO_PAYMENTS_MODE as "test" | "live",
      onEvent: (event) => {
        if (event.event_type === "checkout.opened") {
          setIsLoading(false);
        } else if (event.event_type === "checkout.error") {
          setIsLoading(false);
          console.error("Checkout error:", event.data?.message);
        }
      },
    });
  }, []);

  const handleCheckout = async () => {
    setIsLoading(true);
    try {
      // In a real app, you might fetch a session URL from your API here
      // For simplicity, we'll assume you have a checkout URL or use a static one
      const response = await fetch("/api/checkout", {
        method: "POST",
        body: JSON.stringify({ productId }),
      });
      const { checkout_url } = await response.json();

      await DodoPayments.Checkout.open({
        checkoutUrl: checkout_url,
      });
    } catch (error) {
      console.error("Failed to open checkout:", error);
      setIsLoading(false);
    }
  };

  return (
    <button onClick={handleCheckout} disabled={isLoading} className={className}>
      {isLoading ? "Processing..." : "Upgrade Now"}
    </button>
  );
}
```

This component uses the [integration guide](https://docs.dodopayments.com/developer-resources/integration-guide) patterns. It initializes the SDK in a `useEffect` hook and provides a clean interface for triggering payments.

## Step 4: Create the Webhook Route Handler

Webhooks are the most critical part of any payment integration. They ensure your database stays in sync with the actual payment status, even if the user closes their browser before the redirect happens.

With the `@dodopayments/nextjs` adapter, setting up a webhook handler is straightforward. Create a file at `app/api/webhooks/dodo/route.ts`.

```typescript
// app/api/webhooks/dodo/route.ts
import { Webhooks } from "@dodopayments/nextjs";
import { updateSubscriptionStatus } from "@/lib/db";

export const POST = Webhooks({
  webhookKey: process.env.DODO_WEBHOOK_SECRET!,
  onSubscriptionActive: async (payload) => {
    const { customer, subscription_id, metadata } = payload.data;
    const userId = metadata?.userId;

    if (userId) {
      await updateSubscriptionStatus(userId, {
        subscriptionId: subscription_id,
        status: "active",
        email: customer.email,
      });
    }
  },
  onSubscriptionCancelled: async (payload) => {
    const { metadata } = payload.data;
    const userId = metadata?.userId;

    if (userId) {
      await updateSubscriptionStatus(userId, {
        status: "cancelled",
      });
    }
  },
  onPaymentSucceeded: async (payload) => {
    // Handle one-time payments
    console.log("Payment succeeded:", payload.data.payment_id);
  },
});
```

This handler automatically verifies the webhook signature. If the signature is invalid, it returns a 401 Unauthorized response, protecting your endpoint from malicious requests. You can find more details in the [webhooks documentation](https://docs.dodopayments.com/developer-resources/webhooks).

## Step 5: Create the Checkout Session API

To keep your API keys secure, you should never create checkout sessions directly from the client. Instead, create a simple Route Handler that calls the Dodo Payments API. This handler acts as a secure proxy, ensuring that your sensitive credentials never leave your server. It also gives you a central place to add custom logic, such as applying discounts or calculating taxes based on the user's location.

```typescript
// app/api/checkout/route.ts
import { Checkout } from "@dodopayments/nextjs";
import { auth } from "@/lib/auth";

export const POST = Checkout({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
  environment: process.env.DODO_PAYMENTS_ENVIRONMENT as
    | "test_mode"
    | "live_mode",
  type: "session",
});
```

When the client calls this endpoint, the adapter handles the communication with Dodo's servers and returns a `checkout_url`. You can pass additional metadata like `userId` to this endpoint, which will be included in the webhook payloads later. This metadata is incredibly useful for tracking the lifecycle of a payment and ensuring that your database is always up to date. For example, you can use it to link a payment to a specific user account, a marketing campaign, or even a specific version of your product.

```typescript
// app/api/checkout/route.ts
import { Checkout } from "@dodopayments/nextjs";
import { auth } from "@/lib/auth";

export const POST = Checkout({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
  environment: process.env.DODO_PAYMENTS_ENVIRONMENT as
    | "test_mode"
    | "live_mode",
  type: "session",
});
```

When the client calls this endpoint, the adapter handles the communication with Dodo's servers and returns a `checkout_url`. You can pass additional metadata like `userId` to this endpoint, which will be included in the webhook payloads later.

## Step 6: Protect Routes Based on Payment Status

Once you have the payment flow working, you need to restrict access to your application's premium features. In Next.js, you can do this at the page level using Server Components or globally using Middleware.

### Server Component Check

This is the most common way to protect specific pages. Since Server Components can fetch data directly, you can check the user's subscription status before rendering the page.

```tsx
// app/dashboard/premium-feature/page.tsx
import { auth } from "@/lib/auth";
import { getUserSubscription } from "@/lib/db";
import { redirect } from "next/navigation";

export default async function PremiumPage() {
  const session = await auth();
  if (!session) redirect("/login");

  const subscription = await getUserSubscription(session.user.id);

  if (subscription?.status !== "active") {
    redirect("/upgrade");
  }

  return (
    <div>
      <h1>Welcome to the Premium Feature</h1>
      <p>This content is only visible to active subscribers.</p>
    </div>
  );
}
```

### Middleware Check

If you want to protect an entire directory (e.g., `/dashboard/*`), Middleware is a more efficient choice.

```typescript
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { auth } from "@/lib/auth";

export async function middleware(request: NextRequest) {
  const session = await auth();

  if (request.nextUrl.pathname.startsWith("/dashboard")) {
    if (!session) {
      return NextResponse.redirect(new URL("/login", request.url));
    }

    // You would typically check a cookie or a fast-access cache here
    // to avoid hitting your main database on every request.
  }

  return NextResponse.next();
}
```

## Handling Subscription Upgrades and Downgrades

One of the most complex parts of any subscription-based business is handling upgrades and downgrades. Users often want to change their plans as their needs evolve, and your application needs to handle these transitions smoothly. Dodo Payments makes this easy by providing a unified interface for managing subscriptions.

When a user decides to upgrade their plan, you can simply create a new checkout session for the new product. Dodo Payments will automatically handle the transition, including prorating the remaining balance from the old plan and applying it to the new one. This ensures that your users are always charged the correct amount and that there are no gaps in their service.

On the backend, your webhook handler will receive a `subscription.plan_changed` event. You can use this event to update your database and grant the user access to the new features. Similarly, if a user decides to downgrade their plan, you can handle the transition in the same way. The key is to ensure that your application's UI reflects the current state of the user's subscription at all times.

## Global Tax Compliance and the MoR Model

For many developers, the most daunting part of selling software online is dealing with global sales tax. Every country has its own rules and regulations, and keeping track of them all can be a full-time job. This is where the Merchant of Record (MoR) model comes in. By using a [Merchant of Record for SaaS](https://dodopayments.com/blogs/merchant-of-record-for-saas), you are essentially outsourcing the entire tax and compliance process to a third party.

Dodo Payments acts as the legal seller of your products. This means they are responsible for calculating, collecting, and remitting sales tax in every jurisdiction where you have customers. They also handle compliance with local laws, such as the GDPR in Europe or the CCPA in California. This not only saves you a huge amount of time and effort but also reduces your legal risk. You can focus on building your product, knowing that your global sales are being handled by experts.

In addition to tax compliance, the MoR model also provides a layer of protection against fraud and chargebacks. Dodo Payments uses advanced machine learning algorithms to detect and prevent fraudulent transactions before they happen. If a chargeback does occur, they handle the entire dispute process on your behalf, saving you from the headache of dealing with banks and credit card companies.
## Advanced: Subscriptions and Usage-Based Billing

Many SaaS applications in 2026 are moving away from simple flat-rate subscriptions. Dodo Payments natively supports [implement usage-based billing](https://dodopayments.com/blogs/implement-usage-based-billing) and [subscription pricing models](https://dodopayments.com/blogs/subscription-pricing-models) that scale with your users.

To implement usage-based billing, you can use the Dodo Payments SDK to ingest usage events. For example, if you are building an AI writing tool, you might track the number of words generated.

```typescript
import DodoPayments from "dodopayments";

const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
});

await client.usageEvents.ingest({
  external_customer_id: "user_123",
  metric_id: "met_word_count",
  quantity: 500,
});
```

This allows you to charge users based on their actual consumption, which is often more fair and profitable than a single flat fee. You can read more about this in our guide on [how to sell software online](https://dodopayments.com/blogs/how-to-sell-software-online).

## Testing Your Integration

Testing payments can be tricky because webhooks require a publicly accessible URL. During local development, your Next.js app runs on `localhost`, which Dodo's servers cannot reach.

### Using ngrok for Webhooks

The best way to test webhooks locally is to use a tunneling service like ngrok.

1. Start your Next.js app: `npm run dev`
2. Start ngrok: `ngrok http 3000`
3. Copy the forwarding URL (e.g., `abc-123.ngrok-free.app`)
4. Go to the Dodo Payments dashboard and set your webhook URL to `https://abc-123.ngrok-free.app/api/webhooks/dodo`
2. Start ngrok: `ngrok http 3000`
3. Copy the forwarding URL (e.g., `https://abc-123.ngrok-free.app`)
4. Go to the Dodo Payments dashboard and set your webhook URL to `https://abc-123.ngrok-free.app/api/webhooks/dodo`

Now, when you perform a test payment, the webhook will be forwarded to your local machine, allowing you to debug your handler in real time.

### Vercel Preview Deployments

If you are using Vercel, every pull request gets a unique preview URL. This is a great way to test your integration in a production-like environment before merging. Just remember to update your webhook URL in the Dodo dashboard to match your preview URL for that specific test.

## Best Practices for Next.js Payments

- **Use Server Components for Data Fetching**: Always check subscription status on the server to prevent layout shift and improve security.
- **Handle Loading States**: Payments can take a few seconds. Use React state to show loading indicators on your checkout buttons.
- **Metadata is Your Friend**: Pass your internal `userId` or `orderId` as metadata when creating a checkout session. This makes it much easier to link the payment back to your user in the webhook handler.
- **Idempotency**: Ensure your webhook handler is idempotent. Sometimes webhooks are retried, and you don't want to grant the same subscription twice or create duplicate records.
- **Secure Your Webhooks**: Never skip signature verification. It is the only way to ensure the request is legitimate.

Integrating payments doesn't have to be a month-long project. By using the right tools and following modern patterns, you can [how to accept online payments](https://dodopayments.com/blogs/how-to-accept-online-payments) in a matter of hours. Whether you are building a simple micro-SaaS or a complex enterprise platform, Dodo Payments provides the infrastructure you need to scale globally without the compliance headache.

If you are just starting out, check out our [best platform to sell digital products](https://dodopayments.com/blogs/best-platform-sell-digital-products) guide for more context on choosing the right stack. And if you are interested in the latest trends like [vibe coding](https://dodopayments.com/blogs/vibe-coding), you will find that Dodo's developer-first approach fits perfectly into that workflow.

For more technical details, refer to the [SDKs](https://docs.dodopayments.com/developer-resources/dodo-payments-sdks) and [inline checkout](https://docs.dodopayments.com/developer-resources/inline-checkout) documentation. Ready to get started? Check out our [pricing](https://dodopayments.com/pricing) and sign up for a test account today.

## FAQ

### Does Dodo Payments work with Next.js App Router?

Yes, Dodo Payments is fully compatible with the Next.js App Router. We provide a dedicated `@dodopayments/nextjs` adapter that includes Route Handlers for checkouts, customer portals, and webhooks, making integration seamless for modern Next.js applications.

### How do I handle webhooks in Next.js?

You can handle webhooks using a Route Handler in the `app/api` directory. By using the `Webhooks` utility from our Next.js adapter, you can easily verify signatures and route different event types to specific async functions for processing.

### Can I use Dodo Payments with Server Components?

While the checkout overlay itself is a Client Component, you should use Server Components to fetch subscription data and protect your routes. This ensures that sensitive payment checks happen on the server, providing better security and performance.

### How do I protect routes based on payment status in Next.js?

You can protect routes by checking the user's subscription status in a Server Component or Middleware. If the user does not have an active subscription, you can use the `redirect` function from `next/navigation` to send them to an upgrade or pricing page.

### Does Dodo Payments support both one-time and subscription billing in Next.js?

Yes, Dodo Payments supports one-time payments, recurring subscriptions, and even complex usage-based billing models. All of these can be managed through the same Next.js integration using our unified checkout sessions and webhook handlers.

## Conclusion

Adding payments to a Next.js app in 2026 is about more than just a "Buy" button. It is about creating a secure, scalable, and compliant revenue engine. By leveraging the App Router's strengths and using a Merchant of Record like Dodo Payments, you can bypass the most painful parts of global commerce. This allows you to launch faster, scale more efficiently, and focus on what really matters: building a great product for your users.

The combination of Next.js and Dodo Payments provides a powerful foundation for any SaaS business. Whether you are just starting out or looking to scale to millions of users, this stack offers the flexibility and reliability you need to succeed. By following the steps outlined in this guide, you can have a fully functional payment integration up and running in a matter of hours. So what are you waiting for? Start building your next big idea today!

From handling [embedded payments for SaaS](https://dodopayments.com/blogs/embedded-payments-saas) to designing a robust [payments architecture for SaaS](https://dodopayments.com/blogs/payments-architecture-saas), the tools are now available to make this process faster than ever. Focus on your product, let Dodo handle the payments, and start scaling your Next.js application today.
---
- [More Payments articles](https://dodopayments.com/blogs/category/payments)
- [All articles](https://dodopayments.com/blogs)