Dodo Payments

Dodo Payments is a global Merchant-of-Record platform that lets AI, SaaS and digital businesses sell in 150+ countries without touching tax, fraud, or compliance. A single, developer-friendly API powers checkout, billing, and payouts so you can launch worldwide in minutes.

Get support on Dodo Payments' Discord

This plugin is maintained by the Dodo Payments team.
Have questions? Our team is available on Discord to assist you anytime.

Features

  • Automatic customer creation on sign-up
  • Type-safe checkout flows with product slug mapping
  • Self-service customer portal
  • Metered usage ingestion and recent usage reporting
  • Real-time webhook event processing with signature verification

Get started with Dodo Payments

You need a Dodo Payments account and API keys to use this integration.

Installation

Run the following command in your project root:

npm install @dodopayments/better-auth dodopayments better-auth zod

Add these to your .env file:

DODO_PAYMENTS_API_KEY=your_api_key_here
DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here

Create or update src/lib/auth.ts:

import { betterAuth } from "better-auth";
import {
  dodopayments,
  checkout,
  portal,
  webhooks,
  usage,
} from "@dodopayments/better-auth";
import DodoPayments from "dodopayments";

export const dodoPayments = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
  environment: "test_mode"
});

export const auth = betterAuth({
  plugins: [
    dodopayments({
      client: dodoPayments,
      createCustomerOnSignUp: true,
      use: [
        checkout({
          products: [
            {
              productId: "pdt_xxxxxxxxxxxxxxxxxxxxx",
              slug: "premium-plan",
            },
          ],
          successUrl: "/dashboard/success",
          authenticatedUsersOnly: true,
        }),
        portal(),
        webhooks({
          webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_SECRET!,
          onPayload: async (payload) => {
            console.log("Received webhook:", payload.event_type);
          },
        }),
        usage(),
      ],
    }),
  ],
});

Set environment to live_mode for production.

Create or update src/lib/auth-client.ts:

import { dodopaymentsClient } from "@dodopayments/better-auth";

export const authClient = createAuthClient({
  baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000",
  plugins: [dodopaymentsClient()],
});

Usage

Creating a Checkout Session (preferred)

Use authClient.dodopayments.checkoutSession for new integrations. It maps one-to-one with Dodo Payments' Create Checkout Session API and honors the product slugs you configured on the server.

Using a configured slug

const { data: session, error } = await authClient.dodopayments.checkoutSession({
  slug: "premium-plan",
  referenceId: "order_123",
});

if (session) {
  window.location.href = session.url;
}

Using a product cart

const { data: session, error } = await authClient.dodopayments.checkoutSession({
  product_cart: [
    {
      product_id: "pdt_xxxxxxxxxxxxxxxxxxxxx",
      quantity: 1,
    },
  ],
  referenceId: "order_123",
});

if (session) {
  window.location.href = session.url;
}

Checkout Sessions automatically pull the customer's email and name from their Better Auth session. Override them (or supply billing details upfront) by passing customer or billing in the request body when necessary.

The redirect URL comes from the successUrl configured in the server-side checkout plugin, so you do not need to send a return_url from the client.

Legacy Checkout (deprecated)

The authClient.dodopayments.checkout helper remains for backward compatibility only. Prefer checkoutSession for new builds.

const { data: checkout, error } = await authClient.dodopayments.checkout({
  slug: "premium-plan",
  customer: {
    email: "customer@example.com",
    name: "John Doe",
  },
  billing: {
    city: "San Francisco",
    country: "US",
    state: "CA",
    street: "123 Market St",
    zipcode: "94103",
  },
  referenceId: "order_123",
});

if (checkout) {
  window.location.href = checkout.url;
}

Accessing the Customer Portal

const { data: customerPortal, error } = await authClient.dodopayments.customer.portal();
if (customerPortal && customerPortal.redirect) {
  window.location.href = customerPortal.url;
}

Listing Customer Data

// Get subscriptions
const { data: subscriptions, error } =
  await authClient.dodopayments.customer.subscriptions.list({
    query: {
      limit: 10,
      page: 1,
      active: true,
    },
  });

// Get payment history
const { data: payments, error } = await authClient.dodopayments.customer.payments.list({
  query: {
    limit: 10,
    page: 1,
    status: "succeeded",
  },
});

Tracking Metered Usage

Enable the usage() plugin (shown in the server setup) to ingest metered events for the signed-in, email-verified customer and expose recent usage history.

const { error: ingestError } = await authClient.dodopayments.usage.ingest({
  event_id: crypto.randomUUID(),
  event_name: "api_request",
  metadata: {
    route: "/reports",
    method: "GET",
  },
  timestamp: new Date(),
});

if (ingestError) {
  console.error("Failed to record usage", ingestError);
}

const { data: usage, error: usageError } =
  await authClient.dodopayments.usage.meters.list({
    query: {
      page_size: 20,
      meter_id: "mtr_yourMeterId",
    },
  });

if (usage?.items) {
  usage.items.forEach((event) => {
    console.log(event.event_name, event.timestamp, event.metadata);
  });
}

Usage timestamps older than one hour or more than five minutes in the future are rejected.

Omit meter_id to return all meters tied to the customer's active subscriptions.

Webhooks

The webhooks plugin processes real-time payment events from Dodo Payments with secure signature verification. The default endpoint is /api/auth/dodopayments/webhooks.

Generate a webhook secret for your endpoint URL (e.g., https://your-domain.com/api/auth/dodopayments/webhooks) in the Dodo Payments Dashboard and set it in your .env file:

DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here

Example handler:

webhooks({
  webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_SECRET!,
  onPayload: async (payload) => {
    console.log("Received webhook:", payload.event_type);
  },
});

Configuration Reference

Plugin Options

  • client (required): DodoPayments client instance
  • createCustomerOnSignUp (optional): Auto-create customers on user signup
  • use (required): Array of plugins to enable (checkout, portal, usage, webhooks)

Checkout Plugin Options

  • products: Array of products or async function returning products
  • successUrl: URL to redirect after successful payment
  • authenticatedUsersOnly: Require user authentication (default: false)

If you encounter any issues, please refer to the Dodo Payments documentation for troubleshooting steps.

On this page