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 zodAdd these to your .env file:
DODO_PAYMENTS_API_KEY=your_api_key_here
DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_hereCreate 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_hereExample 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.