> ## 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.

# Convex Component

> Add Dodo Payments checkout, Customer Portal, and webhook handling to your Convex backend with the @dodopayments/convex component.

The `@dodopayments/convex` component adds Dodo Payments to your Convex backend. It provides a `checkout` function that creates checkout sessions, a `customerPortal` function that opens the Customer Portal for the signed-in user, and `createDodoWebhookHandler`, which verifies webhooks in a Convex HTTP action. It requires Convex 1.26 or later.

<CardGroup cols={2}>
  <Card title="Checkout Function" icon="cart-shopping" href="#checkout-function">
    Create checkout sessions from Convex actions.
  </Card>

  <Card title="Customer Portal" icon="user" href="#customer-portal-function">
    Let customers manage their subscriptions and details.
  </Card>

  <Card title="Webhooks" icon="bell" href="#webhook-handler">
    Receive and process Dodo Payments webhook events.
  </Card>
</CardGroup>

## Installation

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

    ```bash theme={null}
    npm install @dodopayments/convex
    ```
  </Step>

  <Step title="Add Component to Convex Config">
    Add the Dodo Payments component to your Convex configuration:

    ```typescript theme={null}
    // convex/convex.config.ts
    import { defineApp } from "convex/server";
    import dodopayments from "@dodopayments/convex/convex.config";

    const app = defineApp();
    app.use(dodopayments);
    export default app;
    ```

    After you edit `convex.config.ts`, run `npx convex dev` once to generate the types.
  </Step>

  <Step title="Set Up Environment Variables">
    Set environment variables in your Convex dashboard under **Settings → Environment Variables**. To open the dashboard, run:

    ```bash theme={null}
    npx convex dashboard
    ```

    Add these environment variables:

    * `DODO_PAYMENTS_API_KEY`: Your Dodo Payments API key, from **Developer → API Keys** in the Dodo Payments dashboard.
    * `DODO_PAYMENTS_ENVIRONMENT`: `test_mode` or `live_mode`.
    * `DODO_PAYMENTS_WEBHOOK_SECRET`: Your webhook secret, from **Developer → Webhooks**. Required for webhook handling. The webhook handler reads this exact variable name.

    <Warning>
      Store secrets as Convex environment variables. Convex backend functions don't read `.env` files. Never commit secrets to version control.
    </Warning>
  </Step>
</Steps>

## Component Setup Examples

<Steps>
  <Step title="Create Internal Query">
    Create an internal query that finds a customer in your database by auth ID. The `identify` function in the next step uses it to get the signed-in user's Dodo Payments customer ID for the customer portal.

    <Warning>
      The component doesn't define a schema. Before you use this query, define a `customers` table with a `by_auth_id` index in `convex/schema.ts`, or change the query to match your existing schema.
    </Warning>

    ```typescript theme={null}
    // convex/customers.ts
    import { internalQuery } from "./_generated/server";
    import { v } from "convex/values";

    // Internal query to fetch customer by auth ID
    export const getByAuthId = internalQuery({
      args: { authId: v.string() },
      handler: async (ctx, { authId }) => {
        return await ctx.db
          .query("customers")
          .withIndex("by_auth_id", (q) => q.eq("authId", authId))
          .first();
      },
    });
    ```
  </Step>

  <Step title="Configure DodoPayments Component">
    Create the client. `identify` maps the signed-in Convex user to a Dodo Payments customer ID. It returns `null` if no user is signed in or no customer matches.

    <CodeGroup>
      ```typescript Convex Component Setup expandable theme={null}
      // convex/dodo.ts
      import { DodoPayments, type DodoPaymentsClientConfig } from "@dodopayments/convex";
      import { components } from "./_generated/api";
      import { internal } from "./_generated/api";

      export const dodo = new DodoPayments(components.dodopayments, {
      // This function maps your Convex user to a Dodo Payments customer
      // Customize it based on your authentication provider and database
      identify: async (ctx) => {
        const identity = await ctx.auth.getUserIdentity();
        if (!identity) {
          return null; // User is not logged in
        }
        
        // Use ctx.runQuery() to lookup customer from your database
        const customer = await ctx.runQuery(internal.customers.getByAuthId, {
          authId: identity.subject,
        });
        
        if (!customer) {
          return null; // Customer not found in database
        }
        
        return {
          dodoCustomerId: customer.dodoCustomerId, // Replace customer.dodoCustomerId with your field storing Dodo Payments customer ID
        };
      },
      apiKey: process.env.DODO_PAYMENTS_API_KEY!,
      environment: process.env.DODO_PAYMENTS_ENVIRONMENT as "test_mode" | "live_mode",
      } as DodoPaymentsClientConfig);

      // Export the API methods for use in your app
      export const { checkout, customerPortal } = dodo.api();
      ```
    </CodeGroup>
  </Step>
</Steps>

Then add the functions you need:

<Tabs>
  <Tab title="Checkout Function Setup">
    <Info>
      Use this function to add Dodo Payments checkout to your Convex app. It creates a checkout session from the fields accepted by the component's checkout payload validator.
    </Info>

    <CodeGroup>
      ```typescript Checkout Action expandable theme={null}
      // convex/payments.ts
      import { action } from "./_generated/server";
      import { v } from "convex/values";
      import { checkout } from "./dodo";

      export const createCheckout = action({
        args: { 
          product_cart: v.array(v.object({
            product_id: v.string(),
            quantity: v.number(),
          })),
          returnUrl: v.optional(v.string()),
        },
        handler: async (ctx, args) => {
          try {
            const session = await checkout(ctx, {
              payload: {
                product_cart: args.product_cart,
                return_url: args.returnUrl,
                billing_currency: "USD",
                feature_flags: {
                  allow_discount_code: true,
                },
              },
            });
            if (!session?.checkout_url) {
              throw new Error("Checkout session did not return a checkout_url");
            }
            return session;
          } catch (error) {
            console.error("Failed to create checkout session", error);
            throw new Error("Unable to create checkout session. Please try again.");
          }
        },
      });
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Customer Portal Setup">
    <Info>
      Use this function to let customers manage their subscriptions and details in the Dodo Payments Customer Portal. The `identify` function finds the customer.
    </Info>

    <CodeGroup>
      ```typescript Customer Portal Action expandable theme={null}
      // convex/payments.ts (add to existing file)
      import { action } from "./_generated/server";
      import { v } from "convex/values";
      import { customerPortal } from "./dodo";

      export const getCustomerPortal = action({
        args: {
          send_email: v.optional(v.boolean()),
        },
        handler: async (ctx, args) => {
          try {
            const portal = await customerPortal(ctx, args);
            if (!portal?.portal_url) {
              throw new Error("Customer portal did not return a portal_url");
            }
            return portal;
          } catch (error) {
            console.error("Failed to generate customer portal link", error);
            throw new Error("Unable to generate customer portal link. Please try again.");
          }
        },
      });
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Webhook Handler Setup">
    <Info>
      Use this handler to receive Dodo Payments webhook events in your Convex app and verify their signatures. Every webhook handler receives the Convex `ActionCtx` as its first parameter, so you can call `ctx.runQuery()` and `ctx.runMutation()` to work with your database.
    </Info>

    <CodeGroup>
      ```typescript Convex HTTP Action expandable theme={null}
      // convex/http.ts
      import { createDodoWebhookHandler } from "@dodopayments/convex";
      import { httpRouter } from "convex/server";
      import { internal } from "./_generated/api";

      const http = httpRouter();

      http.route({
        path: "/dodopayments-webhook",
        method: "POST",
        handler: createDodoWebhookHandler({
          // Handle successful payments
          onPaymentSucceeded: async (ctx, payload) => {
            console.log("🎉 Payment Succeeded!");

            // Use Convex context to persist payment data
            await ctx.runMutation(internal.webhooks.createPayment, {
              paymentId: payload.data.payment_id,
              businessId: payload.business_id,
              customerEmail: payload.data.customer.email,
              amount: payload.data.total_amount,
              currency: payload.data.currency,
              status: payload.data.status,
              webhookPayload: JSON.stringify(payload),
            });
          },

          // Handle subscription activation
          onSubscriptionActive: async (ctx, payload) => {
            console.log("🎉 Subscription Activated!");
            // Use Convex context to persist subscription data
            await ctx.runMutation(internal.webhooks.createSubscription, {
              subscriptionId: payload.data.subscription_id,
              businessId: payload.business_id,
              customerEmail: payload.data.customer.email,
              status: payload.data.status,
              webhookPayload: JSON.stringify(payload),
            });
          },
          // Add other event handlers as needed
        }),
      });

      export default http;
      ```
    </CodeGroup>

    <Warning>
      Define the database mutation that each handler calls. For example, create a `createPayment` mutation to record successful payments, or a `createSubscription` mutation to track subscription state. This example expects them in `convex/webhooks.ts`.
    </Warning>

    In the Dodo Payments dashboard, add the endpoint under **Developer → Webhooks**. Convex serves HTTP actions from your deployment's `.convex.site` domain, so the URL is `https://<your-deployment-name>.convex.site/dodopayments-webhook`.
  </Tab>
</Tabs>

## Checkout Function

The Convex component creates checkout sessions, the recommended checkout flow for all payments. A session holds the product cart, customer details, and checkout options.

### Usage

Call `checkout` from a Convex action, with the checkout session fields in `payload`:

```typescript theme={null}
const result = await checkout(ctx, {
  payload: {
    product_cart: [{ product_id: "pdt_123", quantity: 1 }],
    customer: { email: "user@example.com" },
    return_url: "https://example.com/success"
  }
});
```

`checkout` doesn't call `identify`. To attach an existing customer, pass `customer: { customer_id }` in the payload. For more details and a complete list of supported fields, see [Checkout Sessions](/developer-resources/checkout-session).

A session created with `payment_method_id` returns no checkout URL, so `checkout` throws an error for it.

### Response Format

The checkout function returns an object with the checkout URL:

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

## Customer Portal Function

The customer portal function returns a Customer Portal URL for the signed-in user.

### Usage

```typescript theme={null}
const result = await customerPortal(ctx, {
  send_email: false
});
```

It returns an object with a `portal_url` field.

### Parameters

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

<Info>
  `customerPortal` gets the customer from the `identify` function in your DodoPayments setup, which must return the customer's `dodoCustomerId`. If `identify` returns `null`, `customerPortal` throws a `User is not authenticated.` error.
</Info>

## Webhook Handler

`createDodoWebhookHandler` verifies each request before it runs your code:

* **Method:** Register the route with `method: "POST"`. Requests with other methods don't reach the handler.
* **Signature Verification:** Verifies the Standard Webhooks signature with the `DODO_PAYMENTS_WEBHOOK_SECRET` environment variable. Returns 400 if verification fails.
* **Payload Validation:** Validated with Zod. Returns 400 for invalid payloads.
* **Error Handling:**
  * 400: Invalid signature, invalid payload, or an error thrown by one of your handlers
  * 200: All handlers finished
  * If `DODO_PAYMENTS_WEBHOOK_SECRET` isn't set, the handler throws an error and the request fails.
* **Event Routing:** Calls `onPayload` for every event, then the handler for the event's type.

### Supported Webhook Event Handlers

Each handler receives the Convex `ActionCtx` and the verified payload for its event type:

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

## Frontend Usage

Call the checkout and portal actions from your React components with the `useAction` hook from `convex/react`.

<CodeGroup>
  ```tsx React Checkout Component expandable theme={null}
  import { useAction } from "convex/react";
  import { api } from "../convex/_generated/api";

  export function CheckoutButton() {
    const createCheckout = useAction(api.payments.createCheckout);

    const handleCheckout = async () => {
      try {
        const { checkout_url } = await createCheckout({
          product_cart: [{ product_id: "pdt_123", quantity: 1 }],
          returnUrl: "https://example.com/success"
        });
        if (!checkout_url) {
          throw new Error("Missing checkout_url in response");
        }
        window.location.href = checkout_url;
      } catch (error) {
        console.error("Failed to create checkout", error);
        throw new Error("Unable to create checkout. Please try again.");
      }
    };

    return <button onClick={handleCheckout}>Buy Now</button>;
  }
  ```
</CodeGroup>

<CodeGroup>
  ```tsx Customer Portal Component expandable theme={null}
  import { useAction } from "convex/react";
  import { api } from "../convex/_generated/api";

  export function CustomerPortalButton() {
    const getPortal = useAction(api.payments.getCustomerPortal);

    const handlePortal = async () => {
      try {
        const { portal_url } = await getPortal({ send_email: false });
        if (!portal_url) {
          throw new Error("Missing portal_url in response");
        }
        window.location.href = portal_url;
      } catch (error) {
        console.error("Unable to open customer portal", error);
        alert("We couldn't open the customer portal. Please try again.");
      }
    };

    return <button onClick={handlePortal}>Manage Subscription</button>;
  }
  ```
</CodeGroup>

## Prompt for LLM

Copy this prompt into your AI coding assistant to have it add the component 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 Convex developer assistant. Your task is to guide a user through integrating the @dodopayments/convex component into their existing Convex application.

The @dodopayments/convex adapter provides a Convex component for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, built using the official Convex component architecture pattern.

First, install the necessary package:

npm install @dodopayments/convex

Here's how you should structure your response:

1. Ask the user which functionalities they want to integrate.

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

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

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

If Checkout Function is selected:

Purpose: This function handles session-based checkout flows and returns checkout URLs for programmatic handling.

Integration Steps:

Step 1: Add the component to your Convex configuration.

// convex/convex.config.ts
import { defineApp } from "convex/server";
import dodopayments from "@dodopayments/convex/convex.config";

const app = defineApp();
app.use(dodopayments);
export default app;

Step 2: Guide the user to set up environment variables in the Convex dashboard. Instruct them to open the dashboard by running:

npx convex dashboard

Then add the required environment variables (e.g., DODO_PAYMENTS_API_KEY, DODO_PAYMENTS_ENVIRONMENT, DODO_PAYMENTS_WEBHOOK_SECRET) in **Settings → Environment Variables**. Do not use .env files for backend functions.

Step 3: Create an internal query to fetch customers from your database.
Note: Ensure the user has appropriate schema defined in their convex/schema.ts file or modify the query to match their existing schema.

// convex/customers.ts
import { internalQuery } from "./_generated/server";
import { v } from "convex/values";

// Internal query to fetch customer by auth ID
export const getByAuthId = internalQuery({
  args: { authId: v.string() },
  handler: async (ctx, { authId }) => {
    return await ctx.db
      .query("customers")
      .withIndex("by_auth_id", (q) => q.eq("authId", authId))
      .first();
  },
});

Step 4: Create your payment functions file.

// convex/dodo.ts
import { DodoPayments, type DodoPaymentsClientConfig } from "@dodopayments/convex";
import { components } from "./_generated/api";
import { internal } from "./_generated/api";

export const dodo = new DodoPayments(components.dodopayments, {
  // This function maps your Convex user to a Dodo Payments customer
  // Customize it based on your authentication provider and user database
  identify: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      return null; // User is not logged in
    }
    
    // Use ctx.runQuery() to lookup customer from your database
    const customer = await ctx.runQuery(internal.customers.getByAuthId, {
      authId: identity.subject,
    });
    
    if (!customer) {
      return null; // Customer not found in database
    }
    
    return {
      dodoCustomerId: customer.dodoCustomerId, // Replace customer.dodoCustomerId with your field storing Dodo Payments customer ID
    };
  },
  apiKey: process.env.DODO_PAYMENTS_API_KEY!,
  environment: process.env.DODO_PAYMENTS_ENVIRONMENT as "test_mode" | "live_mode",
} as DodoPaymentsClientConfig);

// Export the API methods for use in your app
export const { checkout, customerPortal } = dodo.api();

Step 5: Create actions that use the checkout function.

// convex/payments.ts
import { action } from "./_generated/server";
import { v } from "convex/values";
import { checkout } from "./dodo";

// Checkout session with full feature support
export const createCheckout = action({
  args: { 
    product_cart: v.array(v.object({
      product_id: v.string(),
      quantity: v.number(),
    })),
    returnUrl: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    return await checkout(ctx, {
      payload: {
        product_cart: args.product_cart,
        return_url: args.returnUrl,
        billing_currency: "USD",
        feature_flags: {
          allow_discount_code: true,
        },
      },
    });
  },
});

Step 6: Use in your frontend.

// Your frontend component
import { useAction } from "convex/react";
import { api } from "../convex/_generated/api";

export function CheckoutButton() {
  const createCheckout = useAction(api.payments.createCheckout);

  const handleCheckout = async () => {
    const { checkout_url } = await createCheckout({
      product_cart: [{ product_id: "pdt_123", quantity: 1 }],
    });
    window.location.href = checkout_url;
  };

  return <button onClick={handleCheckout}>Buy Now</button>;
}

Configuration Details:

- `checkout()`: Checkout session with full feature support using session checkout.
- Returns: `{"checkout_url": "https://checkout.dodopayments.com/..."}`

For complete API documentation, refer to:
- Checkout Sessions: https://docs.dodopayments.com/developer-resources/checkout-session
- One-time Payments: https://docs.dodopayments.com/api-reference/payments/post-payments
- Subscriptions: https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions

If Customer Portal Function is selected:

Purpose: This function allows customers to manage their subscriptions and payment methods. The customer is automatically identified via the `identify` function.

Integration Steps:

Follow Steps 1-4 from the Checkout Function section, then:

Step 5: Create a customer portal action.

// convex/payments.ts (add to existing file)
import { action } from "./_generated/server";
import { v } from "convex/values";
import { customerPortal } from "./dodo";

export const getCustomerPortal = action({
  args: {
    send_email: v.optional(v.boolean()),
  },
  handler: async (ctx, args) => {
    try {
      const portal = await customerPortal(ctx, args);
      if (!portal?.portal_url) {
        throw new Error("Customer portal did not return a portal_url");
      }
      return portal;
    } catch (error) {
      console.error("Failed to generate customer portal link", error);
      throw new Error("Unable to generate customer portal link. Please retry.");
    }
  },
});

Step 6: Use in your frontend.

// Your frontend component
import { useAction } from "convex/react";
import { api } from "../convex/_generated/api";

export function CustomerPortalButton() {
  const getPortal = useAction(api.payments.getCustomerPortal);

  const handlePortal = async () => {
    const { portal_url } = await getPortal({ send_email: false });
    window.location.href = portal_url;
  };

  return <button onClick={handlePortal}>Manage Subscription</button>;
}

Configuration Details:
- Requires authenticated user (via `identify` function).
- Customer identification is handled automatically by the `identify` function.
- `send_email`: Optional boolean to send portal link via email.

If Webhook Handler is selected:

Purpose: This handler processes incoming webhook events from Dodo Payments, allowing your application to react to events like successful payments or subscription changes.

Integration Steps:

Step 1: Add the webhook secret to your environment variables in the Convex dashboard.

Guide the user to open the Convex dashboard by running:

npx convex dashboard

In the dashboard, go to **Settings → Environment Variables** and add:

- `DODO_PAYMENTS_WEBHOOK_SECRET=whsec_...`

Do not use .env files for backend functions; always set secrets in the Convex dashboard. The webhook handler reads this exact variable name.

Step 2: Create a file `convex/http.ts`:

// convex/http.ts
import { createDodoWebhookHandler } from "@dodopayments/convex";
import { httpRouter } from "convex/server";
import { internal } from "./_generated/api";

const http = httpRouter();

http.route({
  path: "/dodopayments-webhook",
  method: "POST",
  handler: createDodoWebhookHandler({
    // Handle successful payments
    onPaymentSucceeded: async (ctx, payload) => {
      console.log("🎉 Payment Succeeded!");

      // Use Convex context to persist payment data
      await ctx.runMutation(internal.webhooks.createPayment, {
        paymentId: payload.data.payment_id,
        businessId: payload.business_id,
        customerEmail: payload.data.customer.email,
        amount: payload.data.total_amount,
        currency: payload.data.currency,
        status: payload.data.status,
        webhookPayload: JSON.stringify(payload),
      });
    },

    // Handle subscription activation
    onSubscriptionActive: async (ctx, payload) => {
      console.log("🎉 Subscription Activated!");
      // Use Convex context to persist subscription data
      await ctx.runMutation(internal.webhooks.createSubscription, {
        subscriptionId: payload.data.subscription_id,
        businessId: payload.business_id,
        customerEmail: payload.data.customer.email,
        status: payload.data.status,
        webhookPayload: JSON.stringify(payload),
      });
    },
    // Add other event handlers as needed
  }),
});

export default http;

Note: Make sure to define the corresponding database mutations in your Convex backend for each webhook event you want to handle. For example, create a `createPayment` mutation to record successful payments or a `createSubscription` mutation to manage subscription state.

Now, you can set the webhook endpoint URL in your Dodo Payments dashboard to `https://<your-deployment-name>.convex.site/dodopayments-webhook`. Convex serves HTTP actions from the deployment's .convex.site domain, not .convex.cloud.

Environment Variable Setup:

Set up the following environment variables in your Convex dashboard if you haven't already (Settings → Environment Variables):

- `DODO_PAYMENTS_API_KEY` - Your Dodo Payments API key
- `DODO_PAYMENTS_ENVIRONMENT` - Set to `test_mode` or `live_mode`
- `DODO_PAYMENTS_WEBHOOK_SECRET` - Your webhook secret (required for webhook handling)

Usage in your component configuration:

apiKey: process.env.DODO_PAYMENTS_API_KEY
environment: process.env.DODO_PAYMENTS_ENVIRONMENT as "test_mode" | "live_mode"

Important: Never commit sensitive environment variables directly into your code. Always use Convex environment variables for all sensitive information.

If the user needs assistance setting up environment variables or deployment, ask them about their specific setup and provide guidance accordingly.

Run `npx convex dev` after setting up the component to generate the necessary types.
```


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