# How to Set Up Payments in Remix

> Complete guide to integrating Dodo Payments in Remix applications. Covers loaders, actions, resource routes for webhooks, and checkout components.
- **Author**: Ayush Agarwal
- **Published**: 2026-03-29
- **Category**: Payments, Developer Tools, How-To
- **URL**: https://dodopayments.com/blogs/set-up-payments-remix

---

Remix's philosophy of web standards and progressive enhancement makes it great for building resilient web apps. When you build with Remix, you are working with the grain of the web, using loaders for data fetching and actions for data mutations. Adding payments to this architecture should feel just as natural. By using Remix's built-in primitives, you can create a checkout flow that is both robust and easy to maintain.

In this guide, we will walk through the process of setting up payments in a Remix application using Dodo Payments. We will cover everything from initial installation to handling webhooks and managing customer subscriptions. By the end, you will have a fully functional payment system that aligns perfectly with the Remix way of doing things.

## Why Remix and Dodo Payments?

Remix is designed to simplify the complex parts of web development by leaning on browser standards. Dodo Payments follows a similar philosophy by simplifying the complex parts of global commerce. When you combine the two, you get a powerful stack that handles the heavy lifting of both frontend architecture and backend compliance.

> Payment infrastructure should disappear into the background. If your engineering team is spending more than a day per month on billing issues, your payment stack is working against you.
>
> \- Rishabh Goel, Co-founder & CEO at Dodo Payments

Dodo Payments acts as a Merchant of Record (MoR), which means we handle the messy parts of selling software globally. This includes automatic tax calculation and collection in over 220+ countries and regions, compliance with local regulations, and managing chargebacks. For a Remix developer, this means you can focus on building your product instead of worrying about VAT OSS or economic nexus in different US states.

The integration is particularly smooth because Dodo provides a dedicated Remix adapter. This adapter is built specifically for Remix's route-based architecture, making it easy to set up checkout triggers in actions and webhook handlers in resource routes. You don't have to write boilerplate code to parse request bodies or verify signatures manually.

## Prerequisites

Before we dive into the code, make sure you have the following:

- A Remix project already set up. If you don't have one, you can create it using `npx create-remix@latest`.
- A Dodo Payments account. You can sign up at [dodopayments.com](https://dodopayments.com).
- Your Dodo Payments API key and Webhook secret from the dashboard.

## Step 1: Installation

The first step is to install the Dodo Payments Remix adapter. This package contains the handlers we will use to manage checkouts, webhooks, and the customer portal.

```bash
npm install @dodopayments/remix
```

This adapter is a lightweight wrapper around the Dodo Payments SDK, optimized for the Remix environment. It handles the conversion between standard Web Fetch API requests and the Dodo API, which is exactly how Remix handles its own loaders and actions.

## Step 2: Environment Variables

Security is paramount when handling payments. You should never hardcode your API keys or secrets in your source code. Instead, use environment variables. Create a `.env` file in your project root if you don't have one already.

```env
DODO_PAYMENTS_API_KEY=your_api_key_here
DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here
DODO_PAYMENTS_RETURN_URL=https://dodopayments.com/checkout/success
DODO_PAYMENTS_ENVIRONMENT=test_mode
```

In a production environment, you would set these variables in your hosting provider's dashboard (like Vercel, Netlify, or Fly.io). Remix automatically loads these variables from the `.env` file during development.

## Step 3: The Remix Payment Architecture

Before we write more code, let's look at how the different parts of our Remix app will interact with Dodo Payments. The flow typically involves a loader to check the user's current status, an action to trigger the checkout, and a resource route to handle asynchronous updates from Dodo.

```mermaid
flowchart TD
    A[User visits Pricing Page] --> B[Loader checks Subscription Status]
    B --> C[Component renders Buy Button]
    C --> D[User clicks Buy]
    D --> E[Action triggers Checkout]
    E --> F[Redirect to Dodo Checkout]
    F --> G[User pays]
    G --> H[Dodo sends Webhook]
    H --> I[Resource Route updates DB]
    I --> J[User redirected back to App]
```

This architecture ensures that your application remains the source of truth for user entitlements while Dodo handles the actual transaction.

## Step 4: Creating the Checkout Route

In Remix, we can create a dedicated route to handle checkout requests. This is often a "resource route" that doesn't render a UI but instead performs a redirect or returns JSON. Create a file at `app/routes/api.checkout.tsx`.

```typescript
import { Checkout } from "@dodopayments/remix";
import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";

const checkoutHandler = Checkout({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
  returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
  environment: process.env.DODO_PAYMENTS_ENVIRONMENT as any,
  type: "dynamic",
});

export const loader = ({ request }: LoaderFunctionArgs) => {
  // You can use the loader for static payment links if needed
  return checkoutHandler(request);
};

export const action = async ({ request }: ActionFunctionArgs) => {
  // The action handles POST requests, perfect for dynamic checkouts
  return checkoutHandler(request);
};
```

The `Checkout` handler from the adapter is versatile. It can handle static links via GET requests in the loader or dynamic checkouts via POST requests in the action. For most SaaS applications, you will use the action to pass specific customer details or product configurations.

## Step 5: Handling Webhooks Securely

Webhooks are essential for keeping your database in sync with payment events. When a subscription renews or a payment fails, Dodo sends a POST request to your webhook URL. We need a resource route to receive and verify these events. Create a file at `app/routes/api.webhook.tsx`.

```typescript
import { Webhooks } from "@dodopayments/remix";
import type { ActionFunctionArgs } from "@remix-run/node";

const webhookHandler = Webhooks({
  webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_SECRET!,
  onPaymentSucceeded: async (payload) => {
    const { customer, total_amount, metadata } = payload.data;
    console.log(`Payment of ${total_amount} succeeded for ${customer.email}`);
    // Update your database here
  },
  onSubscriptionActive: async (payload) => {
    const { customer_id, subscription_id } = payload.data;
    // Grant access to your service
  },
  onSubscriptionCancelled: async (payload) => {
    // Revoke access
  },
});

export const action = ({ request }: ActionFunctionArgs) => {
  return webhookHandler(request);
};
```

The `Webhooks` handler automatically verifies the signature of the incoming request using your `webhookKey`. If the signature is invalid, it returns a 401 Unauthorized response, protecting your endpoint from spoofing attacks. It also provides typed callbacks for different event types, making your logic clean and readable.

## Step 6: Triggering Checkout from the UI

Now that our backend routes are ready, we can add a checkout button to our frontend. We will use Remix's `<Form>` component to submit a POST request to our checkout route.

```tsx
import { Form } from "@remix-run/react";

export default function PricingPage() {
  return (
    <div className="pricing-container">
      <h2>Pro Plan</h2>
      <p>$29/month</p>
      <Form action="/api/checkout" method="post">
        <input type="hidden" name="productId" value="pdt_your_product_id" />
        <input type="hidden" name="quantity" value="1" />
        <button type="submit" className="buy-button">
          Upgrade Now
        </button>
      </Form>
    </div>
  );
}
```

When the user clicks the button, Remix submits the form to `/api/checkout`. Our action in that route then calls the Dodo API and redirects the user to the hosted checkout page. This approach works even if JavaScript is disabled, fulfilling the promise of progressive enhancement.

## Step 7: Checking Subscription Status in Loaders

To protect your premium routes, you should check the user's subscription status in a loader. You can fetch this information from your own database (updated via webhooks) or query the Dodo API directly.

```typescript
import { json, redirect } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { getSession } from "~/sessions.server";
import { db } from "~/db.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const session = await getSession(request.headers.get("Cookie"));
  const userId = session.get("userId");

  if (!userId) {
    return redirect("/login");
  }

  const user = await db.user.findUnique({ where: { id: userId } });

  if (!user?.isSubscribed) {
    return redirect("/pricing");
  }

  return json({ data: "Premium Content" });
};
```

By performing this check in the loader, you ensure that the user never sees the premium content unless they have an active subscription. Remix handles the redirect on the server, providing a seamless experience for the user.

## Step 8: The Customer Portal

Allowing users to manage their own subscriptions is a standard requirement for any SaaS. Dodo Payments provides a hosted customer portal where users can update their payment methods, view invoices, and cancel subscriptions. We can set up a route to redirect users to this portal. Create `app/routes/api.customer-portal.tsx`.

```typescript
import { CustomerPortal } from "@dodopayments/remix";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { getSession } from "~/sessions.server";

const portalHandler = CustomerPortal({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
  environment: process.env.DODO_PAYMENTS_ENVIRONMENT as any,
});

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const session = await getSession(request.headers.get("Cookie"));
  const customerId = session.get("dodoCustomerId");

  if (!customerId) {
    return new Response("Customer ID not found", { status: 400 });
  }

  // Append the customer_id to the request URL for the handler
  const url = new URL(request.url);
  url.searchParams.set("customer_id", customerId);
  const modifiedRequest = new Request(url.toString(), request);

  return portalHandler(modifiedRequest);
};
```

Now you can simply link to `/api/customer-portal` in your app's settings page, and Dodo will handle the rest.

## Advanced Tips for Remix Payments

### Using Error Boundaries

Payment failures are a reality of e-commerce. Remix's error boundaries are perfect for handling these gracefully. If your checkout action fails, the nearest error boundary will catch it, allowing you to show a helpful message to the user without crashing the entire page.

```tsx
export function ErrorBoundary() {
  return (
    <div className="error-container">
      <h3>Something went wrong with your checkout.</h3>
      <p>Please try again or contact support if the issue persists.</p>
    </div>
  );
}
```

### Progressive Enhancement with Payment Links

While dynamic checkouts are powerful, sometimes a simple static link is all you need. You can use Dodo's static payment links as a fallback. If your action fails for some reason, you can provide a direct link to the checkout page in your UI. This ensures that you never miss a sale due to a temporary technical glitch.

### Deploying to the Edge

Remix is designed to run anywhere, including edge environments like Cloudflare Workers or Vercel Edge Functions. The Dodo Payments Remix adapter is built using standard web APIs, meaning it is fully compatible with these environments. You can process payments and handle webhooks with incredibly low latency, no matter where your users are located.

## Conclusion

Setting up payments in Remix doesn't have to be a daunting task. By leveraging the `@dodopayments/remix` adapter and Remix's native features like actions, loaders, and resource routes, you can build a payment system that is secure, scalable, and easy to maintain.

Dodo Payments takes care of the global compliance and tax headaches, while Remix provides the architectural foundation for a great user experience. Together, they allow you to focus on what matters most: building a great product for your customers.

Whether you are building a simple digital product store or a complex SaaS with usage-based billing, this setup provides the flexibility you need to grow. Start by integrating the basic checkout flow and then expand into more advanced features like the customer portal and detailed webhook handling as your needs evolve.

For more information on building with Dodo Payments, check out our [integration guide](https://docs.dodopayments.com/developer-resources/integration-guide) or explore the [API reference](https://docs.dodopayments.com/api-reference/introduction).

## FAQ

### Does Dodo Payments support Remix's new Vite plugin?

Yes, the Dodo Payments Remix adapter is fully compatible with the Remix Vite plugin. Since the adapter uses standard Remix route handlers, it works seamlessly regardless of your build tool. You can continue using your existing Vite configuration without any special adjustments for the payment integration.

### How do I handle local development with webhooks?

To test webhooks locally, you can use a tool like Ngrok or the Dodo CLI to tunnel requests from the internet to your local machine. Once you have a public URL, you can set it as your webhook endpoint in the Dodo dashboard. Make sure to update your `DODO_PAYMENTS_WEBHOOK_SECRET` in your local `.env` file to match the secret provided for that endpoint.

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

Absolutely. The adapter is built using standard Web Fetch APIs, which are the native language of Cloudflare Workers. You won't run into issues with Node.js-specific dependencies. This makes it an excellent choice for developers looking to build high-performance, globally distributed applications on the edge.

### What happens if a user cancels during the checkout process?

If a user cancels, they will be redirected back to your application based on the `returnUrl` you provided. You can also listen for the `onPaymentCancelled` event in your webhook handler if you need to perform any specific cleanup in your database. Generally, it is best to keep the user's state as "pending" until you receive a successful payment event.

### Is it possible to customize the checkout page appearance?

Yes, Dodo Payments allows you to customize the look and feel of your hosted checkout pages through the dashboard. You can add your logo, change brand colors, and customize the messaging to ensure a consistent experience for your users. These changes are applied automatically to all checkout sessions and payment links generated by your Remix app.

## Internal Links

- [How to Accept Online Payments](https://dodopayments.com/blogs/how-to-accept-online-payments)
- [Embedded Payments for SaaS](https://dodopayments.com/blogs/embedded-payments-saas)
- [Payments Architecture for SaaS](https://dodopayments.com/blogs/payments-architecture-saas)
- [Merchant of Record for SaaS](https://dodopayments.com/blogs/merchant-of-record-for-saas)
- [How to Sell Software Online](https://dodopayments.com/blogs/how-to-sell-software-online)
- [Subscription Pricing Models](https://dodopayments.com/blogs/subscription-pricing-models)
- [Best Platform to Sell Digital Products](https://dodopayments.com/blogs/best-platform-sell-digital-products)
- [Vibe Coding with Dodo Payments](https://dodopayments.com/blogs/vibe-coding)
- [Dodo Payments Home](https://dodopayments.com)
- [Dodo Payments Pricing](https://dodopayments.com/pricing)

## Documentation Links

- [Overlay Checkout Guide](https://docs.dodopayments.com/developer-resources/overlay-checkout)
- [Webhook Integration](https://docs.dodopayments.com/developer-resources/webhooks)
- [Dodo Payments SDKs](https://docs.dodopayments.com/developer-resources/dodo-payments-sdks)
- [Integration Guide](https://docs.dodopayments.com/developer-resources/integration-guide)
- [Inline Checkout Guide](https://docs.dodopayments.com/developer-resources/inline-checkout)
---
- [More Payments articles](https://dodopayments.com/blogs/category/payments)
- [All articles](https://dodopayments.com/blogs)