> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dodopayments.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Nuxt Adaptor

> Add Dodo Payments checkout, Customer Portal, and webhook server routes to a Nuxt 3 app with the @dodopayments/nuxt module and runtime config.

The `@dodopayments/nuxt` module gives your Nuxt app three server route handlers. `checkoutHandler` returns checkout URLs, `customerPortalHandler` sends a customer to the Customer Portal, and `Webhooks` verifies webhook events and routes them to your code.

<CardGroup cols={2}>
  <Card title="Checkout API Route" icon="cart-shopping" href="#checkout-route-handler">
    Create checkout URLs from a Nuxt server route.
  </Card>

  <Card title="Customer Portal API Route" icon="user" href="#customer-portal-route-handler">
    Let customers manage their subscriptions and details from a Nuxt server route.
  </Card>

  <Card title="Webhooks API Route" icon="bell" href="#webhook-route-handler">
    Receive and verify Dodo Payments webhook events in Nuxt.
  </Card>
</CardGroup>

## Overview

<Info>
  The module registers its handlers as Nuxt server auto-imports, so your server routes call `checkoutHandler`, `customerPortalHandler`, and `Webhooks` without import statements. Each route reads your credentials from `runtimeConfig`. Nuxt exposes only `runtimeConfig.public` to the browser, so the API key and webhook secret stay on the server.
</Info>

## Installation

<Steps>
  <Step title="Install the Nuxt Module">
    Run this command in your project root:

    ```bash theme={null}
    npm install @dodopayments/nuxt
    ```

    The module lists Nuxt 3 (3.13.1 or later) and `zod` 3.25 or later as peer dependencies.
  </Step>

  <Step title="Register the Module in nuxt.config.ts">
    Add `@dodopayments/nuxt` to your `modules` array, and map your credentials into `runtimeConfig`:

    ```typescript nuxt.config.ts expandable theme={null}
    export default defineNuxtConfig({
      modules: ["@dodopayments/nuxt"],
      devtools: { enabled: true },
      compatibilityDate: "2025-02-25",
      runtimeConfig: {
        private: {
          bearerToken: process.env.NUXT_PRIVATE_BEARER_TOKEN,
          webhookKey: process.env.NUXT_PRIVATE_WEBHOOK_KEY,
          environment: process.env.NUXT_PRIVATE_ENVIRONMENT,
          returnUrl: process.env.NUXT_PRIVATE_RETURN_URL
        },
      }
    });
    ```

    Set these environment variables, for example in a `.env` file in your project root:

    | Variable | Value |
    | - | - |
    | `NUXT_PRIVATE_BEARER_TOKEN` | Your API key. Create it under **Developer → API Keys** in the dashboard. |
    | `NUXT_PRIVATE_WEBHOOK_KEY` | Your webhook secret. Add your endpoint under **Developer → Webhooks** and copy its signing secret. |
    | `NUXT_PRIVATE_ENVIRONMENT` | `test_mode` or `live_mode`. If it's unset, the handlers use `live_mode`. |
    | `NUXT_PRIVATE_RETURN_URL` | Optional. The URL customers land on after checkout. |

    A built Nuxt server doesn't read your `.env` file. At runtime, Nuxt overrides a `runtimeConfig` value only from the variable that matches its path, such as `NUXT_PRIVATE_RETURN_URL` for `private.returnUrl`, so set these variables in your hosting environment too.
  </Step>
</Steps>

<Warning>
  Never commit your `.env` file or secrets to version control.
</Warning>

## API Route Handler Examples

<Info>
  The examples create server routes in the `server/routes/api/` directory. Nuxt routes each file by its name and method suffix, so `checkout.get.ts` handles `GET /api/checkout`.
</Info>

<Tabs>
  <Tab title="Checkout API Route">
    <Info>
      Use this handler to add Dodo Payments checkout to your Nuxt app. A GET route serves static checkout. A POST route serves checkout sessions, or dynamic checkout when you set `type: "dynamic"`.
    </Info>

    Create a GET route for static checkout:

    <CodeGroup>
      ```typescript server/routes/api/checkout.get.ts expandable theme={null}
      export default defineEventHandler((event) => {
        const {
          private: { bearerToken, environment, returnUrl },
        } = useRuntimeConfig();

        const handler = checkoutHandler({
          bearerToken: bearerToken,
          environment: environment as "test_mode" | "live_mode",
          returnUrl: returnUrl,
        });

        return handler(event);
      });
      ```
    </CodeGroup>

    `checkout.post.ts` serves one POST flow. Use either the dynamic checkout example or the checkout session example:

    <CodeGroup>
      ```typescript server/routes/api/checkout.post.ts expandable theme={null}
      export default defineEventHandler((event) => {
        const {
          private: { bearerToken, environment, returnUrl },
        } = useRuntimeConfig();

        const handler = checkoutHandler({
          bearerToken: bearerToken,
          environment: environment as "test_mode" | "live_mode",
          returnUrl: returnUrl,
          type: "dynamic"
        });

        return handler(event);
      });
      ```
    </CodeGroup>

    <CodeGroup>
      ```typescript server/routes/api/checkout.post.ts (Checkout Sessions) expandable theme={null}
      export default defineEventHandler((event) => {
        const {
          private: { bearerToken, environment, returnUrl },
        } = useRuntimeConfig();

        const handler = checkoutHandler({
          bearerToken: bearerToken,
          environment: environment as "test_mode" | "live_mode",
          returnUrl: returnUrl,
          type: "session"
        });

        return handler(event);
      });
      ```
    </CodeGroup>

    <Warning>
      If `productId` is missing or invalid, the handler returns a 400 response.
    </Warning>

    To test the routes, send these requests:

    <CodeGroup>
      ```bash Static Checkout cURL Example theme={null}
      curl --request GET \
      --url 'https://example.com/api/checkout?productId=pdt_fqJhl7pxKWiLhwQR042rh'
      ```
    </CodeGroup>

    <CodeGroup>
      ```bash Dynamic Checkout cURL Example expandable theme={null}
      curl --request POST \
      --url https://example.com/api/checkout \
      --header 'Content-Type: application/json' \
      --data '{
      "billing": {
        "city": "Austin",
        "country": "US",
        "state": "TX",
        "street": "123 Main St",
        "zipcode": "78701"
      },
      "customer": {
        "email": "customer@example.com",
        "name": "John Doe"
      },
      "metadata": {},
      "payment_link": true,
      "product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
      "quantity": 1,
      "billing_currency": "USD",
      "discount_codes": ["IKHZ23M9GQ"],
      "return_url": "https://example.com",
      "trial_period_days": 10
      }'
      ```
    </CodeGroup>

    <CodeGroup>
      ```bash Checkout Session cURL Example expandable theme={null}
      curl --request POST \
      --url https://example.com/api/checkout \
      --header 'Content-Type: application/json' \
      --data '{
      "product_cart": [
        {
          "product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
          "quantity": 1
        }
      ],
      "customer": {
        "email": "customer@example.com",
        "name": "John Doe"
      },
      "return_url": "https://example.com/success"
      }'
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Customer Portal API Route">
    <Info>
      Create a GET route that sends customers to their Customer Portal. The route reads `customer_id` from the query string.
    </Info>

    <CodeGroup>
      ```typescript server/routes/api/customer-portal.get.ts expandable theme={null}
      export default defineEventHandler((event) => {
        const {
          private: { bearerToken, environment },
        } = useRuntimeConfig();

        const handler = customerPortalHandler({
          bearerToken,
          environment: environment as "test_mode" | "live_mode",
        });

        return handler(event);
      });
      ```
    </CodeGroup>

    **Query Parameters:**

    * `customer_id` (required): The customer ID for the portal session, for example `?customer_id=cus_123`.
    * `send_email` (optional, boolean): If `true`, Dodo Payments also emails the portal link to the customer.

    <Warning>
      From `@dodopayments/nuxt` 0.2.11, the handler returns HTTP `400` if `customer_id` is missing and HTTP `500` if the portal session can't be created. Earlier versions return HTTP `200` in both cases, with a JSON body such as `{ "status": 400, "body": "Missing customer_id in query parameters" }`. To rely on the HTTP status, upgrade to 0.2.11 or later.
    </Warning>

    <CodeGroup>
      ```bash Customer Portal cURL Example theme={null}
      curl --request GET \
      --url 'https://example.com/api/customer-portal?customer_id=cus_9VuW4K7O3GHwasENg31m&send_email=true'
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Webhook API Route">
    <Info>
      Create a POST route to receive Dodo Payments webhook events and verify their signatures.
    </Info>

    <CodeGroup>
      ```typescript server/routes/api/webhook.post.ts expandable theme={null}
      export default defineEventHandler((event) => {
        const {
          private: { webhookKey },
        } = useRuntimeConfig();

        const handler = Webhooks({
          webhookKey: webhookKey,
          onPayload: async (payload: any) => {
            // Handle webhook payload here
          },
          // ...add other event handlers as needed
        });

        return handler(event);
      });
      ```
    </CodeGroup>
  </Tab>
</Tabs>

## Checkout Route Handler

The checkout handler supports all three ways to take payments with Dodo Payments:

* **Static Payment Links:** Shareable URLs that collect payments without code.
* **Dynamic Payment Links:** Payment links you generate with custom details. They use deprecated endpoints.
* **Checkout Sessions:** Hosted checkout with a product cart, customer details, and customization options. This is the recommended flow.

`checkoutHandler` takes these options:

| Option | Type | Description |
| - | - | - |
| `bearerToken` | `string` | Your Dodo Payments API key. |
| `environment` | `string` | `test_mode` or `live_mode`. Defaults to `live_mode`. |
| `returnUrl` | `string` | Optional. The URL that checkout redirects the customer to after payment. A `return_url` in a POST body takes precedence. |
| `type` | `string` | Optional. On a GET route, leave it unset or set `static`, because other values make static checkout return 400. On a POST route, `dynamic` selects dynamic checkout, and any other value, or none, selects checkout sessions. |

<AccordionGroup>
  <Accordion title="Static Checkout (GET)">
    ### Supported Query Parameters

    <ParamField query="productId" type="string" required>
      Product identifier, for example `?productId=pdt_nZuwz45WAs64n3l07zpQR`.
    </ParamField>

    <ParamField query="quantity" type="integer" default="1">
      Quantity of the product.
    </ParamField>

    <ParamField query="fullName" type="string">
      Customer's full name. Ignored if `firstName` or `lastName` is provided.
    </ParamField>

    <ParamField query="firstName" type="string">
      Customer's first name.
    </ParamField>

    <ParamField query="lastName" type="string">
      Customer's last name.
    </ParamField>

    <ParamField query="email" type="string">
      Customer's email address.
    </ParamField>

    <ParamField query="country" type="string">
      Customer's country, as an ISO 3166-1 alpha-2 code.
    </ParamField>

    <ParamField query="addressLine" type="string">
      Customer's address line.
    </ParamField>

    <ParamField query="city" type="string">
      Customer's city.
    </ParamField>

    <ParamField query="state" type="string">
      Customer's state or province.
    </ParamField>

    <ParamField query="zipCode" type="string">
      Customer's ZIP or postal code.
    </ParamField>

    <ParamField query="disableFullName" type="boolean">
      Set to `true` to disable the full name field.
    </ParamField>

    <ParamField query="disableFirstName" type="boolean">
      Set to `true` to disable the first name field.
    </ParamField>

    <ParamField query="disableLastName" type="boolean">
      Set to `true` to disable the last name field.
    </ParamField>

    <ParamField query="disableEmail" type="boolean">
      Set to `true` to disable the email field.
    </ParamField>

    <ParamField query="disableCountry" type="boolean">
      Set to `true` to disable the country field.
    </ParamField>

    <ParamField query="disableAddressLine" type="boolean">
      Set to `true` to disable the address line field.
    </ParamField>

    <ParamField query="disableCity" type="boolean">
      Set to `true` to disable the city field.
    </ParamField>

    <ParamField query="disableState" type="boolean">
      Set to `true` to disable the state field.
    </ParamField>

    <ParamField query="disableZipCode" type="boolean">
      Set to `true` to disable the ZIP code field.
    </ParamField>

    <ParamField query="paymentCurrency" type="string">
      Payment currency, for example `USD`.
    </ParamField>

    <ParamField query="showCurrencySelector" type="boolean" default="true">
      Show or hide the currency selector.
    </ParamField>

    <ParamField query="paymentAmount" type="number">
      Fixes the amount charged, in major currency units, for example `12.5` for \$12.50. Works with Pay What You Want products only, and is ignored if it's below the product's minimum price.
    </ParamField>

    <ParamField query="showDiscounts" type="boolean" default="true">
      Show or hide the discounts section.
    </ParamField>

    <ParamField query="metadata_*" type="string">
      Any query parameter that starts with `metadata_` is passed as metadata.
    </ParamField>

    The handler adds `returnUrl` from its config to the link as `redirect_url`.

    <Warning>
      If `productId` is missing, the handler returns a 400 response. Invalid query parameters and product IDs that don't exist also return 400.
    </Warning>

    ### Response Format

    Static checkout returns a JSON response with the checkout URL. In test mode, the URL uses `test.checkout.dodopayments.com`.

    ```json theme={null}
    {
      "checkout_url": "https://checkout.dodopayments.com/..."
    }
    ```
  </Accordion>

  <Accordion title="Dynamic Checkout (POST)">
    * Send the parameters as a JSON body in a POST request.
    * Supports both one-time and recurring payments.
    * `billing` and `customer` are required.
    * For every supported body field, see:
      * [Request body for a One Time Payment Product](https://docs.dodopayments.com/api-reference/payments/post-payments)
      * [Request body for a Subscription Product](https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions)

    <Warning>
      Dynamic checkout proxies the deprecated `POST /payments` and `POST /subscriptions` endpoints. It keeps working for existing integrations, but new integrations should use checkout sessions.
    </Warning>

    ### Response Format

    Dynamic checkout returns a JSON response with the checkout URL:

    ```json theme={null}
    {
      "checkout_url": "https://checkout.dodopayments.com/..."
    }
    ```
  </Accordion>

  <Accordion title="Checkout Sessions (POST)">
    Checkout sessions create a hosted checkout for one-time purchases and subscriptions, with full control over customization. `product_cart` is the only required field. If the body has no `return_url`, the handler uses `returnUrl` from its config.

    For more details and every supported field, see the [Checkout Sessions Integration Guide](https://docs.dodopayments.com/developer-resources/checkout-session).

    A session created with `payment_method_id` returns no checkout URL, so the handler responds with 400. To charge a saved payment method, create the session with the SDK instead.

    ### Response Format

    Checkout sessions return a JSON response with the checkout URL:

    ```json theme={null}
    {
      "checkout_url": "https://checkout.dodopayments.com/session/..."
    }
    ```
  </Accordion>
</AccordionGroup>

## Customer Portal Route Handler

The Customer Portal route handler creates a Customer Portal session for the customer you pass and redirects the browser to it.

<Warning>
  The handler doesn't check who is calling it. Anyone who requests it with a customer ID gets that customer's portal. Protect the route with your own authentication, and pass only the signed-in user's customer ID.
</Warning>

### Query Parameters

<ParamField query="customer_id" type="string" required>
  The customer ID for the portal session, for example `?customer_id=cus_123`.
</ParamField>

<ParamField query="send_email" type="boolean">
  If set to `true`, Dodo Payments also emails the portal link to the customer.
</ParamField>

<Warning>
  From `@dodopayments/nuxt` 0.2.11, the handler returns HTTP `400` if `customer_id` is missing and HTTP `500` if the portal session can't be created. Earlier versions return HTTP `200` in both cases, with a JSON body such as `{ "status": 400, "body": "Missing customer_id in query parameters" }`. To rely on the HTTP status, upgrade to 0.2.11 or later.
</Warning>

## Webhook Route Handler

The webhook route handler verifies each request before it runs your code:

* **Method:** Only POST requests are supported. Other methods return 405.
* **Signature Verification:** Verifies the raw request body and the `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers with `webhookKey`, following the [Standard Webhooks](https://standardwebhooks.com/) specification. Returns 401 if verification fails.
* **Payload Validation:** Validates the payload with Zod. Returns 400 for an invalid payload.
* **Error Handling:**
  * 401: Invalid signature
  * 400: Invalid payload
  * 500: Internal error during verification
* **Event Routing:** Calls `onPayload` for every event, then the handler for the event's type, and returns 200.

The adaptor doesn't catch errors thrown in your handlers. They propagate to Nuxt, and the request fails.

### Supported Webhook Event Handlers

Each handler receives the verified payload for its event type:

<CodeGroup>
  ```typescript TypeScript expandable theme={null}
  onPayload?: (payload: WebhookPayload) => Promise<void>;
  onPaymentSucceeded?: (payload: WebhookPayload) => Promise<void>;
  onPaymentFailed?: (payload: WebhookPayload) => Promise<void>;
  onPaymentProcessing?: (payload: WebhookPayload) => Promise<void>;
  onPaymentCancelled?: (payload: WebhookPayload) => Promise<void>;
  onRefundSucceeded?: (payload: WebhookPayload) => Promise<void>;
  onRefundFailed?: (payload: WebhookPayload) => Promise<void>;
  onDisputeOpened?: (payload: WebhookPayload) => Promise<void>;
  onDisputeExpired?: (payload: WebhookPayload) => Promise<void>;
  onDisputeAccepted?: (payload: WebhookPayload) => Promise<void>;
  onDisputeCancelled?: (payload: WebhookPayload) => Promise<void>;
  onDisputeChallenged?: (payload: WebhookPayload) => Promise<void>;
  onDisputeWon?: (payload: WebhookPayload) => Promise<void>;
  onDisputeLost?: (payload: WebhookPayload) => Promise<void>;
  onSubscriptionActive?: (payload: WebhookPayload) => Promise<void>;
  onSubscriptionOnHold?: (payload: WebhookPayload) => Promise<void>;
  onSubscriptionRenewed?: (payload: WebhookPayload) => Promise<void>;
  onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise<void>;
  onSubscriptionCancelled?: (payload: WebhookPayload) => Promise<void>;
  onSubscriptionFailed?: (payload: WebhookPayload) => Promise<void>;
  onSubscriptionExpired?: (payload: WebhookPayload) => Promise<void>;
  onSubscriptionUpdated?: (payload: WebhookPayload) => Promise<void>;
  onSubscriptionPaused?: (payload: WebhookPayload) => Promise<void>;
  onSubscriptionUnpaused?: (payload: WebhookPayload) => Promise<void>;
  onSubscriptionUpdatePaymentMethod?: (payload: WebhookPayload) => Promise<void>;
  onLicenseKeyCreated?: (payload: WebhookPayload) => Promise<void>;
  onAbandonedCheckoutDetected?: (payload: WebhookPayload) => Promise<void>;
  onAbandonedCheckoutRecovered?: (payload: WebhookPayload) => Promise<void>;
  onDunningStarted?: (payload: WebhookPayload) => Promise<void>;
  onDunningRecovered?: (payload: WebhookPayload) => Promise<void>;
  onCreditAdded?: (payload: WebhookPayload) => Promise<void>;
  onCreditDeducted?: (payload: WebhookPayload) => Promise<void>;
  onCreditExpired?: (payload: WebhookPayload) => Promise<void>;
  onCreditRolledOver?: (payload: WebhookPayload) => Promise<void>;
  onCreditRolloverForfeited?: (payload: WebhookPayload) => Promise<void>;
  onCreditOverageCharged?: (payload: WebhookPayload) => Promise<void>;
  onCreditOverageReset?: (payload: WebhookPayload) => Promise<void>;
  onCreditManualAdjustment?: (payload: WebhookPayload) => Promise<void>;
  onCreditBalanceLow?: (payload: WebhookPayload) => Promise<void>;
  onEntitlementGrantCreated?: (payload: WebhookPayload) => Promise<void>;
  onEntitlementGrantDelivered?: (payload: WebhookPayload) => Promise<void>;
  onEntitlementGrantFailed?: (payload: WebhookPayload) => Promise<void>;
  onEntitlementGrantRevoked?: (payload: WebhookPayload) => Promise<void>;
  onPayoutCreated?: (payload: WebhookPayload) => Promise<void>;
  onPayoutOnHold?: (payload: WebhookPayload) => Promise<void>;
  onPayoutInProgress?: (payload: WebhookPayload) => Promise<void>;
  onPayoutFailed?: (payload: WebhookPayload) => Promise<void>;
  onPayoutSuccess?: (payload: WebhookPayload) => Promise<void>;
  ```
</CodeGroup>

For what each event means, see the [Webhook Event Guide](/developer-resources/webhooks/intents/webhook-events-guide).

***

## Prompt for LLM

Copy this prompt into your AI coding assistant to have it add the module to your project. To give your agent the Dodo Payments docs and skills as well, install the [Agent Plugin](/developer-resources/build-with-ai-coding-agents).

```text expandable theme={null}
You are an expert Nuxt developer assistant. Your task is to guide a user through integrating the @dodopayments/nuxt module into their existing Nuxt project.

The @dodopayments/nuxt module provides API route handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, designed for Nuxt 3 server routes. The module auto-imports checkoutHandler, customerPortalHandler, and Webhooks into server routes, so the route files need no import statements.

First, install the necessary package:

npm install @dodopayments/nuxt

Second, add the configuration to nuxt.config.ts

export default defineNuxtConfig({
  modules: ["@dodopayments/nuxt"],
  devtools: { enabled: true },
  compatibilityDate: "2025-02-25",
  runtimeConfig: {
    private: {
      bearerToken: process.env.NUXT_PRIVATE_BEARER_TOKEN,
      webhookKey: process.env.NUXT_PRIVATE_WEBHOOK_KEY,
      environment: process.env.NUXT_PRIVATE_ENVIRONMENT,
      returnUrl: process.env.NUXT_PRIVATE_RETURN_URL
    },
  }
});


Here's how you should structure your response:

    Ask the user which functionalities they want to integrate.

"Which parts of the @dodopayments/nuxt module would you like to integrate into your project? You can choose one or more of the following:

    Checkout API Route (for handling product checkouts)
    Customer Portal API Route (for managing customer subscriptions/details)
    Webhook API Route (for receiving Dodo Payments webhook events)
    All (integrate all three)"

    Based on the user's selection, provide detailed integration steps for each chosen functionality.

If Checkout API Route is selected:

Purpose: This route returns a JSON response with a checkout_url for the Dodo Payments checkout page.
File Creation: Create a new file at server/routes/api/checkout.get.ts in your Nuxt project.

Code Snippet:

// server/routes/api/checkout.get.ts

export default defineEventHandler((event) => {
  const {
    private: { bearerToken, environment, returnUrl },
  } = useRuntimeConfig();

  const handler = checkoutHandler({
    bearerToken: bearerToken,
    environment: environment as "test_mode" | "live_mode",
    returnUrl: returnUrl,
  });

  return handler(event);
});

Configuration & Usage:
- bearerToken: Your Dodo Payments API key. Set via the NUXT_PRIVATE_BEARER_TOKEN environment variable.
- returnUrl: (Optional) The URL to redirect the user to after a successful checkout.
- environment: (Optional) "test_mode" or "live_mode". Defaults to "live_mode".
- type: (Optional) On a POST route (server/routes/api/checkout.post.ts), set "dynamic" for dynamic checkout or "session" for checkout sessions. Any value other than "dynamic" serves checkout sessions.

Static Checkout (GET) Query Parameters:
- productId (required): Product identifier (e.g., ?productId=pdt_nZuwz45WAs64n3l07zpQR)
- quantity (optional): Quantity of the product
- Customer Fields (optional): fullName, firstName, lastName, email, country, addressLine, city, state, zipCode
- Disable Flags (optional, set to true to disable): disableFullName, disableFirstName, disableLastName, disableEmail, disableCountry, disableAddressLine, disableCity, disableState, disableZipCode
- Advanced Controls (optional): paymentCurrency, showCurrencySelector, paymentAmount, showDiscounts
- Metadata (optional): Any query parameter starting with metadata_ (e.g., ?metadata_userId=abc123)

Dynamic Checkout (POST): Parameters are sent as a JSON body. Supports both one-time and recurring payments. For a complete list of supported POST body fields, refer to:
- Docs - One Time Payment Product: https://docs.dodopayments.com/api-reference/payments/post-payments
- Docs - Subscription Product: https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions

Checkout Sessions (POST) - (Recommended) A more customizable checkout experience. Returns JSON with checkout_url: Parameters are sent as a JSON body, and product_cart is required. Supports both one-time and recurring payments. Returns: {"checkout_url": "https://checkout.dodopayments.com/session/..."}. For a complete list of supported fields, refer to:

  Checkout Sessions Integration Guide: https://docs.dodopayments.com/developer-resources/checkout-session

Error Handling: If productId is missing or other query parameters are invalid, the handler will return a 400 response.

If Customer Portal API Route is selected:

Purpose: This route redirects a customer to their Dodo Payments customer portal. It does not authenticate the caller, so protect the route and pass only the signed-in user's customer ID.
File Creation: Create a new file at server/routes/api/customer-portal.get.ts in your Nuxt project.

Code Snippet:

// server/routes/api/customer-portal.get.ts

export default defineEventHandler((event) => {
  const {
    private: { bearerToken, environment },
  } = useRuntimeConfig();

  const handler = customerPortalHandler({
    bearerToken,
    environment: environment as "test_mode" | "live_mode",
  });

  return handler(event);
});

Query Parameters:
- customer_id (required): The customer ID for the portal session (e.g., ?customer_id=cus_123)
- send_email (optional, boolean): If set to true, sends an email to the customer with the portal link.
- Returns 400 if customer_id is missing, and 500 if the portal session cannot be created (@dodopayments/nuxt 0.2.11 or later).

If Webhook API Route is selected:

Purpose: This route processes incoming webhook events from Dodo Payments, allowing your application to react to events like successful payments, refunds, or subscription changes.
File Creation: Create a new file at server/routes/api/webhook.post.ts in your Nuxt project.

Code Snippet:

// server/routes/api/webhook.post.ts

export default defineEventHandler((event) => {
  const {
    private: { webhookKey },
  } = useRuntimeConfig();

  const handler = Webhooks({
    webhookKey: webhookKey,
    onPayload: async (payload) => {
      // handle the payload
    },
    // ... other event handlers for granular control
  });

  return handler(event);
});

Handler Details:
- Method: Only POST requests are supported. Other methods return 405.
- Signature Verification: The handler verifies the webhook signature using webhookKey and returns 401 if verification fails.
- Payload Validation: The payload is validated with Zod. Returns 400 for invalid payloads.

Error Handling:
- 401: Invalid signature
- 400: Invalid payload
- 500: Internal error during verification

Event Routing: Calls onPayload for every event, then the handler for the event's type. Supported event handlers include:
- onPayload?: (payload: WebhookPayload) => Promise<void>
- onPaymentSucceeded?: (payload: WebhookPayload) => Promise<void>
- onPaymentFailed?: (payload: WebhookPayload) => Promise<void>
- onPaymentProcessing?: (payload: WebhookPayload) => Promise<void>
- onPaymentCancelled?: (payload: WebhookPayload) => Promise<void>
- onRefundSucceeded?: (payload: WebhookPayload) => Promise<void>
- onRefundFailed?: (payload: WebhookPayload) => Promise<void>
- onDisputeOpened?: (payload: WebhookPayload) => Promise<void>
- onDisputeExpired?: (payload: WebhookPayload) => Promise<void>
- onDisputeAccepted?: (payload: WebhookPayload) => Promise<void>
- onDisputeCancelled?: (payload: WebhookPayload) => Promise<void>
- onDisputeChallenged?: (payload: WebhookPayload) => Promise<void>
- onDisputeWon?: (payload: WebhookPayload) => Promise<void>
- onDisputeLost?: (payload: WebhookPayload) => Promise<void>
- onSubscriptionActive?: (payload: WebhookPayload) => Promise<void>
- onSubscriptionOnHold?: (payload: WebhookPayload) => Promise<void>
- onSubscriptionRenewed?: (payload: WebhookPayload) => Promise<void>
- onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise<void>
- onSubscriptionCancelled?: (payload: WebhookPayload) => Promise<void>
- onSubscriptionFailed?: (payload: WebhookPayload) => Promise<void>
- onSubscriptionExpired?: (payload: WebhookPayload) => Promise<void>
- onSubscriptionUpdated?: (payload: WebhookPayload) => Promise<void>
- onSubscriptionPaused?: (payload: WebhookPayload) => Promise<void>
- onSubscriptionUnpaused?: (payload: WebhookPayload) => Promise<void>
- onSubscriptionUpdatePaymentMethod?: (payload: WebhookPayload) => Promise<void>
- onLicenseKeyCreated?: (payload: WebhookPayload) => Promise<void>
- onAbandonedCheckoutDetected?: (payload: WebhookPayload) => Promise<void>
- onAbandonedCheckoutRecovered?: (payload: WebhookPayload) => Promise<void>
- onDunningStarted?: (payload: WebhookPayload) => Promise<void>
- onDunningRecovered?: (payload: WebhookPayload) => Promise<void>
- onCreditAdded?: (payload: WebhookPayload) => Promise<void>
- onCreditDeducted?: (payload: WebhookPayload) => Promise<void>
- onCreditExpired?: (payload: WebhookPayload) => Promise<void>
- onCreditRolledOver?: (payload: WebhookPayload) => Promise<void>
- onCreditRolloverForfeited?: (payload: WebhookPayload) => Promise<void>
- onCreditOverageCharged?: (payload: WebhookPayload) => Promise<void>
- onCreditOverageReset?: (payload: WebhookPayload) => Promise<void>
- onCreditManualAdjustment?: (payload: WebhookPayload) => Promise<void>
- onCreditBalanceLow?: (payload: WebhookPayload) => Promise<void>
- onEntitlementGrantCreated?: (payload: WebhookPayload) => Promise<void>
- onEntitlementGrantDelivered?: (payload: WebhookPayload) => Promise<void>
- onEntitlementGrantFailed?: (payload: WebhookPayload) => Promise<void>
- onEntitlementGrantRevoked?: (payload: WebhookPayload) => Promise<void>
- onPayoutCreated?: (payload: WebhookPayload) => Promise<void>
- onPayoutOnHold?: (payload: WebhookPayload) => Promise<void>
- onPayoutInProgress?: (payload: WebhookPayload) => Promise<void>
- onPayoutFailed?: (payload: WebhookPayload) => Promise<void>
- onPayoutSuccess?: (payload: WebhookPayload) => Promise<void>

Environment Variable Setup:
To ensure the module functions correctly, set up the following environment variables in your Nuxt project's deployment environment (e.g., Vercel, Netlify, AWS, etc.). A built Nuxt server does not read the .env file, and Nuxt overrides runtime config values at runtime only from variables whose names match the config path:
- NUXT_PRIVATE_BEARER_TOKEN: Your Dodo Payments API Key (required for Checkout and Customer Portal).
- NUXT_PRIVATE_WEBHOOK_KEY: Your Dodo Payments Webhook Secret (required for Webhook handler).
- NUXT_PRIVATE_ENVIRONMENT: (Optional) "test_mode" or "live_mode". Defaults to "live_mode".
- NUXT_PRIVATE_RETURN_URL: (Optional) The URL to redirect to after a successful checkout (for Checkout handler).

Usage in your code:
bearerToken: useRuntimeConfig().private.bearerToken
webhookKey: useRuntimeConfig().private.webhookKey

Important: Never commit sensitive environment variables directly into your version control. Use environment variables for all sensitive information.

If the user needs assistance setting up environment variables for their specific deployment environment, ask them what platform they are using (e.g., Vercel, Netlify, AWS, etc.), and provide guidance.
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.