# How to Add Payments to Your Nuxt.js App with Dodo Payments

> A step-by-step guide to integrating payments, subscriptions, and one-time purchases into Nuxt.js applications using Dodo Payments.
- **Author**: Ayush Agarwal
- **Published**: 2026-04-06
- **Modified**: 2026-04-08
- **Category**: Integration, Nuxt.js, How-To
- **URL**: https://dodopayments.com/blogs/accept-payments-nuxt-app

---

Nuxt.js is one of the most productive Vue-based frameworks available today. Its file-based routing, Nitro server engine, and first-class TypeScript support make it a natural fit for SaaS products, digital marketplaces, and subscription tools. However, adding a reliable payment layer to a Nuxt app is still something most tutorials skip over or handle superficially.

This guide covers the complete nuxt payments integration from scratch using [Dodo Payments](https://dodopayments.com). You will add payments to your Nuxt.js app using the Dodo Payments Node SDK directly, giving you full control over the checkout flow, webhook handling, subscription management, and post-payment routing. No magic wrappers, no black boxes.

If you are coming from a React background, we also have a guide for [add payments Next.js](https://dodopayments.com/blogs/add-payments-nextjs-app) and [add payments SvelteKit](https://dodopayments.com/blogs/sveltekit-payments-integration).

## Why Nuxt.js for Payments?

Nuxt.js gives you the ideal architecture for a payment integration. The Nitro server engine lets you define server API routes inside your Nuxt project, which means your backend and frontend live in the same codebase. You get server-side rendering for fast initial page loads, composables for reactive state management, and a clean separation between server-only and client-safe code.

Pairing this with [Dodo Payments](https://dodopayments.com) gives you:

- A Merchant of Record model, so Dodo handles VAT, GST, and global tax compliance
- A unified SDK for one-time payments and recurring subscriptions
- Webhook infrastructure with signature verification
- Support for [overlay checkout](https://docs.dodopayments.com/developer-resources/overlay-checkout) and [inline checkout](https://docs.dodopayments.com/developer-resources/inline-checkout) without custom redirect flows

For teams building a nuxt saas payments setup or a vue payments integration that needs to go global quickly, this combination removes the most painful compliance and infrastructure concerns from day one. See [Dodo Payments pricing](https://dodopayments.com/pricing) to understand what it costs.

> Nuxt's Nitro server engine is one of the cleanest setups for server-side payment logic in the Vue ecosystem. Your API keys never touch the client bundle, and file-based server routes map naturally to payment and webhook endpoints. It is exactly the kind of architecture we had in mind when designing the Node SDK.
>
> - Ayush Agarwal, Co-founder & CPTO at Dodo Payments

## Integration Flow

Before writing any code, here is the full data flow for a nuxt.js payment gateway integration with Dodo Payments:

```mermaid
flowchart TD
    subgraph Client ["Nuxt.js Frontend (Vue)"]
        A[Pricing Page] -->|User clicks Buy| B[CheckoutButton.vue]
        B -->|POST /api/checkout| C[Loading State]
        C -->|Redirect to checkout_url| D[Dodo Hosted Checkout]
        D -->|Payment Complete| E[Success Page]
        D -->|User Cancels| F[Cancel Page]
    end
    subgraph Server ["Nitro Server Routes"]
        G[POST /api/checkout] -->|Create Payment/Subscription| H[Dodo Payments API]
        H -->|Returns checkout_url| G
        I[POST /api/webhooks/dodo] -->|Verify Signature| J[Event Handlers]
        J -->|subscription.active| K[Update DB - Grant Access]
        J -->|payment.succeeded| L[Update DB - One-time]
        J -->|subscription.cancelled| M[Update DB - Revoke Access]
    end
    H -->|Async Webhook| I
```

The client triggers a POST to your Nitro server route, which creates a checkout session using the Dodo Payments Node SDK. The user is redirected to the hosted checkout page. After payment, Dodo sends a webhook to your server route where you update your database. The success and cancel pages handle the post-payment UX.

## Step 1: Project Setup

If you do not have a Nuxt 3 project yet, scaffold one:

```bash
npx nuxi@latest init my-nuxt-app
cd my-nuxt-app
```

Install the Dodo Payments Node SDK. This is the same SDK used across Node.js-based backends. You can find it and other [Dodo Payments SDKs](https://docs.dodopayments.com/developer-resources/dodo-payments-sdks) in the documentation.

```bash
npm install dodopayments
```

Your `package.json` should now include `dodopayments` as a dependency.

## Step 2: Environment Variables

Create a `.env` file in your project root. Never commit this file to version control.

```env
DODO_PAYMENTS_API_KEY=your_api_key_here
DODO_WEBHOOK_SECRET=your_webhook_secret_here
DODO_ENVIRONMENT=test_mode
DODO_RETURN_URL=http://localhost:3000/success
```

Register these variables in `nuxt.config.ts` using `runtimeConfig`. Variables under `runtimeConfig` (without `public`) are server-only and never exposed to the client bundle.

```typescript
// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    dodoApiKey: process.env.DODO_PAYMENTS_API_KEY,
    dodoWebhookSecret: process.env.DODO_WEBHOOK_SECRET,
    dodoEnvironment: process.env.DODO_ENVIRONMENT || "test_mode",
    dodoReturnUrl: process.env.DODO_RETURN_URL,
  },
  compatibilityDate: "2024-11-01",
  devtools: { enabled: true },
});
```

These values are available on the server side via `useRuntimeConfig()` inside Nitro server routes.

## Step 3: Server API Route for Payment Creation

This is the most important server-side piece of the add payments nuxt pattern. You create a Nitro server route that uses the Dodo Payments Node SDK to create a checkout session and returns the hosted checkout URL to the client.

Create the file `server/api/checkout.post.ts`:

```typescript
// server/api/checkout.post.ts
import DodoPayments from "dodopayments";

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const body = await readBody(event);

  const { productId, quantity = 1, customerEmail } = body;

  if (!productId) {
    throw createError({
      statusCode: 400,
      statusMessage: "productId is required",
    });
  }

  const client = new DodoPayments({
    bearerToken: config.dodoApiKey,
    environment: config.dodoEnvironment as "test_mode" | "live_mode",
  });

  try {
    const payment = await client.payments.create({
      billing: {
        city: "",
        country: "US",
        state: "",
        street: "",
        zipcode: "",
      },
      customer: {
        create_new_customer: true,
        email: customerEmail || "",
        name: "",
      },
      product_cart: [
        {
          product_id: productId,
          quantity,
        },
      ],
      return_url: config.dodoReturnUrl,
    });

    return {
      checkout_url: payment.payment_link,
      payment_id: payment.payment_id,
    };
  } catch (error: any) {
    throw createError({
      statusCode: 500,
      statusMessage: error?.message || "Failed to create checkout session",
    });
  }
});
```

For subscriptions, swap `client.payments.create` for `client.subscriptions.create` and pass a `product_id` that maps to a recurring plan. The return structure is the same: you get back a URL to redirect the user.

```typescript
// server/api/subscribe.post.ts
import DodoPayments from "dodopayments";

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const body = await readBody(event);

  const { productId, customerEmail, userId } = body;

  const client = new DodoPayments({
    bearerToken: config.dodoApiKey,
    environment: config.dodoEnvironment as "test_mode" | "live_mode",
  });

  const subscription = await client.subscriptions.create({
    billing: {
      city: "",
      country: "US",
      state: "",
      street: "",
      zipcode: "",
    },
    customer: {
      create_new_customer: true,
      email: customerEmail,
      name: "",
    },
    product_id: productId,
    return_url: config.dodoReturnUrl,
    metadata: {
      userId,
    },
  });

  return {
    checkout_url: subscription.payment_link,
    subscription_id: subscription.subscription_id,
  };
});
```

Passing `metadata.userId` is critical. This value comes back in every webhook event for this subscription, letting you link Dodo events to users in your database without any extra lookup.

## Step 4: Frontend Checkout Button Component

Now create the Vue component that calls your server route and redirects the user to the hosted checkout. This component keeps all sensitive logic on the server.

Create `components/CheckoutButton.vue`:

```vue
<script setup lang="ts">
interface Props {
  productId: string;
  mode: "payment" | "subscription";
  label?: string;
}

const props = withDefaults(defineProps<Props>(), {
  label: "Get Started",
});

const isLoading = ref(false);
const errorMessage = ref("");

async function handleCheckout() {
  isLoading.value = true;
  errorMessage.value = "";

  const endpoint =
    props.mode === "subscription" ? "/api/subscribe" : "/api/checkout";

  try {
    const response = await $fetch<{ checkout_url: string }>(endpoint, {
      method: "POST",
      body: {
        productId: props.productId,
        quantity: 1,
      },
    });

    if (response.checkout_url) {
      window.location.href = response.checkout_url;
    }
  } catch (error: any) {
    errorMessage.value =
      error?.data?.statusMessage || "Something went wrong. Please try again.";
  } finally {
    isLoading.value = false;
  }
}
</script>

<template>
  <div>
    <button
      :disabled="isLoading"
      class="checkout-button"
      @click="handleCheckout"
    >
      <span v-if="isLoading">Processing...</span>
      <span v-else>{{ label }}</span>
    </button>
    <p v-if="errorMessage" class="error-text">{{ errorMessage }}</p>
  </div>
</template>

<style scoped>
.checkout-button {
  background-color: #4f46e5;
  color: white;
  padding: 0.75rem 1.5rem;
  border-radius: 0.5rem;
  font-weight: 600;
  border: none;
  cursor: pointer;
  transition: background-color 0.2s;
}

.checkout-button:hover:not(:disabled) {
  background-color: #4338ca;
}

.checkout-button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.error-text {
  color: #dc2626;
  font-size: 0.875rem;
  margin-top: 0.5rem;
}
</style>
```

Use the component anywhere in your pages:

```vue
<!-- pages/pricing.vue -->
<script setup lang="ts">
// Your pricing page logic
</script>

<template>
  <div class="pricing-page">
    <h1>Choose a Plan</h1>

    <div class="plan-card">
      <h2>Pro Plan</h2>
      <p>$29/month</p>
      <CheckoutButton
        product-id="pdt_your_product_id_here"
        mode="subscription"
        label="Start Pro Plan"
      />
    </div>

    <div class="plan-card">
      <h2>Lifetime Access</h2>
      <p>$199 one-time</p>
      <CheckoutButton
        product-id="pdt_your_lifetime_id_here"
        mode="payment"
        label="Buy Lifetime Access"
      />
    </div>
  </div>
</template>
```

You can also integrate with the [overlay checkout](https://docs.dodopayments.com/developer-resources/overlay-checkout) or [inline checkout](https://docs.dodopayments.com/developer-resources/inline-checkout) if you want to keep the user on your page rather than redirecting.

## Step 5: Webhook Handling with Nitro Server Routes

Webhooks are how Dodo Payments tells your backend what happened after a payment. They fire asynchronously and handle the cases where users close their browser before the redirect completes. This is the backbone of any reliable nuxt saas payments setup.

The full [webhooks documentation](https://docs.dodopayments.com/developer-resources/webhooks) covers the event catalog and signature verification in detail.

Create `server/api/webhooks/dodo.post.ts`:

```typescript
// server/api/webhooks/dodo.post.ts
import DodoPayments from "dodopayments";

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();

  // Read the raw body for signature verification
  const rawBody = await readRawBody(event);
  const signature = getHeader(event, "webhook-signature") || "";
  const webhookId = getHeader(event, "webhook-id") || "";
  const webhookTimestamp = getHeader(event, "webhook-timestamp") || "";

  if (!rawBody) {
    throw createError({
      statusCode: 400,
      statusMessage: "Missing request body",
    });
  }

  const client = new DodoPayments({
    bearerToken: config.dodoApiKey,
    environment: config.dodoEnvironment as "test_mode" | "live_mode",
  });

  let webhookEvent: any;

  try {
    // Verify the webhook signature using the SDK
    webhookEvent = await client.webhooks.constructEvent(
      rawBody,
      `webhook-id=${webhookId}\nwebhook-timestamp=${webhookTimestamp}\nwebhook-signature=${signature}`,
      config.dodoWebhookSecret,
    );
  } catch (err) {
    throw createError({
      statusCode: 401,
      statusMessage: "Invalid webhook signature",
    });
  }

  const eventType = webhookEvent.type;
  const data = webhookEvent.data;

  try {
    switch (eventType) {
      case "payment.succeeded": {
        const { payment_id, customer, metadata } = data;
        // TODO: update your database
        // Example: await db.orders.create({ paymentId: payment_id, userId: metadata?.userId, status: 'completed' })
        console.log(`Payment succeeded: ${payment_id} for ${customer.email}`);
        break;
      }

      case "subscription.active": {
        const { subscription_id, customer, metadata } = data;
        const userId = metadata?.userId;
        // TODO: grant subscription access in your database
        // Example: await db.users.update({ id: userId }, { subscriptionId: subscription_id, plan: 'pro' })
        console.log(
          `Subscription activated: ${subscription_id} for ${customer.email}`,
        );
        break;
      }

      case "subscription.renewed": {
        const { subscription_id, customer, next_billing_date } = data;
        // TODO: update renewal date in your database
        console.log(
          `Subscription renewed: ${subscription_id}, next billing: ${next_billing_date}`,
        );
        break;
      }

      case "subscription.cancelled": {
        const { subscription_id, metadata } = data;
        const userId = metadata?.userId;
        // TODO: revoke access in your database
        // Example: await db.users.update({ id: userId }, { subscriptionId: null, plan: 'free' })
        console.log(`Subscription cancelled: ${subscription_id}`);
        break;
      }

      case "payment.failed": {
        const { payment_id, customer } = data;
        console.log(`Payment failed: ${payment_id} for ${customer.email}`);
        break;
      }

      default:
        console.log(`Unhandled event type: ${eventType}`);
    }
  } catch (err) {
    console.error(`Error processing webhook event ${eventType}:`, err);
    throw createError({
      statusCode: 500,
      statusMessage: "Webhook processing failed",
    });
  }

  return { received: true };
});
```

A few things to note about this implementation:

- Always read the raw body before any other processing. Parsing it as JSON first will break signature verification.
- Make webhook handlers idempotent. Dodo may retry a webhook if your server returns a non-2xx response. Wrap database operations in upsert logic or check whether the event has already been processed.
- The `metadata.userId` passed at checkout creation comes back in every event for that payment or subscription.

> The pattern of reading the raw body before any other processing is critical for webhook signature verification. Nitro makes this straightforward with readRawBody, but developers who parse JSON first and then try to verify the signature will hit issues with whitespace and encoding differences that silently break verification.
>
> - Ayush Agarwal, Co-founder & CPTO at Dodo Payments

Register your webhook endpoint URL (`https://yourdomain.com/api/webhooks/dodo`) in the Dodo Payments dashboard under the Webhooks section.

## Step 6: Subscription Management

Once subscriptions are active in your database, you need to let users manage them. The most common actions are cancelling a subscription and changing a plan. Read the [subscription management](https://docs.dodopayments.com/features/subscription) documentation for the complete event catalog.

Create a server route for cancellation at `server/api/subscriptions/[id]/cancel.post.ts`:

```typescript
// server/api/subscriptions/[id]/cancel.post.ts
import DodoPayments from "dodopayments";

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig();
  const subscriptionId = getRouterParam(event, "id");

  if (!subscriptionId) {
    throw createError({
      statusCode: 400,
      statusMessage: "Subscription ID is required",
    });
  }

  const client = new DodoPayments({
    bearerToken: config.dodoApiKey,
    environment: config.dodoEnvironment as "test_mode" | "live_mode",
  });

  try {
    await client.subscriptions.update(subscriptionId, {
      status: "cancelled",
    });

    // TODO: update your database to reflect the pending cancellation
    // Dodo will send a subscription.cancelled webhook when cancellation takes effect

    return { cancelled: true };
  } catch (error: any) {
    throw createError({
      statusCode: 500,
      statusMessage: error?.message || "Failed to cancel subscription",
    });
  }
});
```

Create a composable `composables/useSubscription.ts` for reactive subscription state in your Vue components:

```typescript
// composables/useSubscription.ts
export interface SubscriptionState {
  plan: "free" | "pro" | "enterprise";
  subscriptionId: string | null;
  status: "active" | "cancelled" | "past_due" | null;
}

export const useSubscription = () => {
  const subscription = useState<SubscriptionState>("subscription", () => ({
    plan: "free",
    subscriptionId: null,
    status: null,
  }));

  const isPro = computed(
    () =>
      subscription.value.plan !== "free" &&
      subscription.value.status === "active",
  );

  async function cancelSubscription() {
    if (!subscription.value.subscriptionId) return;

    await $fetch(
      `/api/subscriptions/${subscription.value.subscriptionId}/cancel`,
      {
        method: "POST",
      },
    );

    subscription.value.status = "cancelled";
  }

  async function refreshSubscription(userId: string) {
    const data = await $fetch<SubscriptionState>(
      `/api/users/${userId}/subscription`,
    );
    subscription.value = data;
  }

  return {
    subscription,
    isPro,
    cancelSubscription,
    refreshSubscription,
  };
};
```

Gate premium features using this composable in any Vue component or page:

```vue
<!-- pages/dashboard.vue -->
<script setup lang="ts">
const { isPro, subscription } = useSubscription();
</script>

<template>
  <div>
    <h1>Dashboard</h1>

    <div v-if="isPro">
      <h2>Pro Features</h2>
      <p>You have access to all premium features.</p>
      <!-- Pro-only content here -->
    </div>

    <div v-else>
      <h2>Upgrade to Pro</h2>
      <p>Get access to all features.</p>
      <CheckoutButton
        product-id="pdt_pro_plan_id"
        mode="subscription"
        label="Upgrade to Pro"
      />
    </div>
  </div>
</template>
```

If you are building a software tool with license-based access rather than subscriptions, Dodo also supports [license keys](https://docs.dodopayments.com/features/license-keys) that you can distribute to users after a one-time purchase.

## Step 7: Success and Cancel Pages

After the user completes or abandons checkout, Dodo redirects them to the `return_url` you configured. You can add a query parameter to distinguish success from cancellation, or use separate URLs.

Update your `.env`:

```env
DODO_RETURN_URL=http://localhost:3000/checkout/result
```

Or use separate URLs:

```env
DODO_SUCCESS_URL=http://localhost:3000/success
DODO_CANCEL_URL=http://localhost:3000/cancel
```

Create `pages/success.vue`:

```vue
<!-- pages/success.vue -->
<script setup lang="ts">
const route = useRoute();

// Dodo appends payment_id or subscription_id as a query parameter
const paymentId = computed(() => (route.query.payment_id as string) || "");
const subscriptionId = computed(
  () => (route.query.subscription_id as string) || "",
);

useHead({
  title: "Payment Successful",
});
</script>

<template>
  <div class="result-page success">
    <div class="icon">✓</div>
    <h1>Payment Successful</h1>
    <p>Thank you for your purchase. Your account has been upgraded.</p>

    <div v-if="paymentId" class="detail">
      <span>Payment ID:</span> {{ paymentId }}
    </div>
    <div v-if="subscriptionId" class="detail">
      <span>Subscription ID:</span> {{ subscriptionId }}
    </div>

    <p class="note">
      You will receive a confirmation email shortly. If you do not see it, check
      your spam folder.
    </p>

    <NuxtLink to="/dashboard" class="cta-button"> Go to Dashboard </NuxtLink>
  </div>
</template>
```

Create `pages/cancel.vue`:

```vue
<!-- pages/cancel.vue -->
<script setup lang="ts">
useHead({
  title: "Checkout Cancelled",
});
</script>

<template>
  <div class="result-page cancel">
    <div class="icon">x</div>
    <h1>Checkout Cancelled</h1>
    <p>No charges were made. You can try again whenever you are ready.</p>

    <NuxtLink to="/pricing" class="cta-button secondary">
      Back to Pricing
    </NuxtLink>
  </div>
</template>
```

Keep the success page simple. The real state update happens via webhook, not the redirect. A user who closes their browser after payment still gets access because the `subscription.active` or `payment.succeeded` webhook fires and updates your database.

## Testing Your Integration Locally

Testing the checkout flow locally requires a few things:

- Set `DODO_ENVIRONMENT=test_mode` and use your test API key from the Dodo dashboard
- For webhooks: use a tunnel like ngrok to expose your local server to the internet

Start your Nuxt dev server:

```bash
npm run dev
```

In a separate terminal, start ngrok:

```bash
ngrok http 3000
```

Copy the HTTPS forwarding URL (e.g., `https://abc123.ngrok-free.app`) and add your webhook endpoint in the Dodo Payments dashboard: `https://abc123.ngrok-free.app/api/webhooks/dodo`.

When you trigger a test checkout and complete it with a test card, you will see the webhook fire in your terminal and the appropriate handler run.

For the complete integration reference including all available events, see the [integration guide](https://docs.dodopayments.com/developer-resources/integration-guide).

## Best Practices

- **Pass `metadata.userId` at checkout creation.** Without it, you cannot link a webhook event back to a specific user without an extra database query.
- **Make webhook handlers idempotent.** Use upsert operations and check if you have already processed a given `payment_id` or `subscription_id` before applying changes.
- **Use Nitro's server-only runtime config.** Never put your API key in `runtimeConfig.public`. Keep it in the top-level `runtimeConfig` object.
- **Log all webhook events.** Even events you do not act on. This gives you an audit trail when something goes wrong.
- **Handle errors in your checkout component.** Network failures happen. Show the user a helpful message and let them retry without a full page reload.
- **Test cancellation and renewal webhooks.** Most developers test the happy path but forget to test what happens when a subscription lapses or renews.

For founders building AI products, the guide on [best payment platform AI startups](https://dodopayments.com/blogs/best-payment-platform-ai-startups) covers the specific billing considerations for usage-based and credit-based pricing models.

## FAQ

### What is the difference between `client.payments.create` and `client.subscriptions.create`?

`client.payments.create` creates a one-time charge. The user pays once and the payment is complete. `client.subscriptions.create` sets up a recurring billing relationship where Dodo charges the customer on your defined interval (monthly, annual, etc.) and sends `subscription.renewed` webhooks on each billing cycle. Use payments for lifetime deals or one-time purchases and subscriptions for SaaS plans.

### Do I need the `@dodopayments/nuxt` module or just the Node SDK?

You can use either. The `@dodopayments/nuxt` module is a convenience wrapper that auto-registers composables and simplifies the server route setup. Using the `dodopayments` Node SDK directly gives you more control and makes it easier to follow the same patterns you use in any Node.js backend. This guide uses the SDK directly so the code is explicit and portable.

### How do I protect routes in Nuxt based on subscription status?

Store the user's subscription status in your database after each relevant webhook event. Create a Nitro server route that returns the current subscription state for a given user. In your Nuxt pages, use a `definePageMeta` route middleware that calls this endpoint and redirects unauthenticated or non-subscribed users. You can also check subscription status in `useAsyncData` at the top of any page and redirect using `navigateTo`.

### Can I use Dodo Payments with Nuxt on Cloudflare Workers?

Yes. Nuxt 3 supports Cloudflare Workers via the Nitro preset. The `dodopayments` SDK is a standard Node.js package that works in edge runtimes as long as the `fetch` API is available, which it is on Cloudflare Workers. Set your `DODO_PAYMENTS_API_KEY` and `DODO_WEBHOOK_SECRET` as Cloudflare Worker secrets and access them via `useRuntimeConfig()` as normal.

### What happens if a webhook fails to process?

Dodo Payments retries failed webhooks with exponential backoff. If your server returns a non-2xx status code, Dodo will retry the request several times over the following hours. This is why idempotency matters: if the same `subscription.active` event is delivered twice, your database update should produce the same result both times. Use upsert operations keyed on `subscription_id` or `payment_id` to handle retries safely.

## Wrapping Up

Adding payments to your Nuxt.js app with Dodo Payments involves four main pieces: a server API route that creates checkout sessions using the Node SDK, a Vue component that calls that route and redirects the user, a Nitro webhook handler that verifies signatures and updates your database, and success and cancel pages that handle the post-checkout UX.

The nuxt.js payment gateway integration pattern covered here scales from a simple one-time purchase to a full subscription management system. Because Dodo acts as your Merchant of Record, you skip the complexity of global tax compliance, VAT registration, and chargeback handling.

Refer to the [Dodo Payments SDKs](https://docs.dodopayments.com/developer-resources/dodo-payments-sdks) page for the full SDK reference, and the [integration guide](https://docs.dodopayments.com/developer-resources/integration-guide) for a complete walkthrough of the API. When you are ready to go live, check your [Dodo Payments pricing](https://dodopayments.com/pricing) and switch your environment variable from `test_mode` to `live_mode`.
---
- [More Integration articles](https://dodopayments.com/blogs/category/integration)
- [All articles](https://dodopayments.com/blogs)