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

# Astro Adaptor

> Add Dodo Payments checkout, Customer Portal, and webhook endpoints to an Astro project with the @dodopayments/astro package.

The `@dodopayments/astro` package gives your Astro project three endpoint handlers. `Checkout` returns checkout URLs, `CustomerPortal` sends a customer to the Customer Portal, and `Webhooks` verifies webhook events and routes them to your code.

<CardGroup cols={2}>
  <Card title="Checkout Handler" icon="cart-shopping" href="#checkout-route-handler">
    Create checkout URLs with static, dynamic, and checkout session flows.
  </Card>

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

  <Card title="Webhooks" icon="bell" href="#webhook-route-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/astro
    ```

    The package lists Astro 4 or 5, and `zod` 3.25 or later, as peer dependencies.
  </Step>

  <Step title="Set Up Environment Variables">
    Create a `.env` file in your project root. Create the API key under **Developer → API Keys**. Add your webhook endpoint under **Developer → Webhooks**, and copy its **Signing secret** into `DODO_PAYMENTS_WEBHOOK_KEY`:

    ```env expandable theme={null}
    DODO_PAYMENTS_API_KEY=your-api-key
    DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
    # test_mode or live_mode
    DODO_PAYMENTS_ENVIRONMENT=test_mode
    DODO_PAYMENTS_RETURN_URL=https://yourdomain.com/checkout/success
    ```

    `DODO_PAYMENTS_RETURN_URL` is where customers land after checkout. If you don't pass an environment, the handlers use `live_mode`. A test mode API key works only with `test_mode`.

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

## Route Handler Examples

<Info>
  The examples are Astro server endpoints in `src/pages/api/`. Endpoints that call Dodo Payments must render on demand, so add a server adapter to your Astro project. In Astro's default `static` output mode, endpoints render at build time, so each example exports `prerender = false` to render the endpoint on each request instead.
</Info>

<Tabs>
  <Tab title="Checkout Handler">
    <Info>
      Use this handler to add Dodo Payments checkout to your app. The `GET` handler serves static checkout. The `POST` handler serves checkout sessions, or dynamic checkout when you set `type: "dynamic"`. An endpoint file can export only one `POST` handler, so the dynamic checkout example assumes you set `type: "dynamic"`.
    </Info>

    <CodeGroup>
      ```typescript Astro Route Handler expandable theme={null}
      // src/pages/api/checkout.ts
      import { Checkout } from "@dodopayments/astro";

      export const prerender = false;

      export const GET = Checkout({
          bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY,
          returnUrl: import.meta.env.DODO_PAYMENTS_RETURN_URL,
          environment: import.meta.env.DODO_PAYMENTS_ENVIRONMENT,
          type: "static", // optional, defaults to 'static'
      });

      export const POST = Checkout({
          bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY,
          returnUrl: import.meta.env.DODO_PAYMENTS_RETURN_URL,
          environment: import.meta.env.DODO_PAYMENTS_ENVIRONMENT,
          type: "session", // for checkout sessions, or "dynamic" for dynamic checkout
      });
      ```
    </CodeGroup>

    <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 Handler">
    <Info>
      Use this handler to send customers to the Dodo Payments Customer Portal, where they manage their subscriptions and details.
    </Info>

    <CodeGroup>
      ```typescript Astro Route Handler expandable theme={null}
      // src/pages/api/customer-portal.ts
      import { CustomerPortal } from "@dodopayments/astro";

      export const prerender = false;

      export const GET = CustomerPortal({
          bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY,
          environment: import.meta.env.DODO_PAYMENTS_ENVIRONMENT,
      });
      ```
    </CodeGroup>

    <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 Handler">
    <Info>
      Use this handler to receive Dodo Payments webhook events in your Astro app and verify their signatures.
    </Info>

    <CodeGroup>
      ```typescript Astro Route Handler expandable theme={null}
      // src/pages/api/webhook.ts
      import { Webhooks } from "@dodopayments/astro";

      export const prerender = false;

      export const POST = Webhooks({
          webhookKey: import.meta.env.DODO_PAYMENTS_WEBHOOK_KEY,
          onPayload: async (payload) => {
              // handle the payload
          },
          // ... other event handlers for granular control
      });
      ```
    </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.

`Checkout` 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` | The checkout flow: `static`, `dynamic`, or `session`. Defaults to `static`. |

The handler serves static checkout for `GET` requests. For `POST` requests, it creates a dynamic payment link when `type` is `dynamic`, and a checkout session otherwise.

<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 street address.
    </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 to checkout as metadata, for example `metadata_orderId=123`.
    </ParamField>

    A disable flag takes effect only when the matching field has a value, for example `email` with `disableEmail=true`. 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, or a product that doesn't exist in your account, 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://test.checkout.dodopayments.com/buy/pdt_fqJhl7pxKWiLhwQR042rh?quantity=1&redirect_url=https%3A%2F%2Fyourdomain.com%2Fcheckout%2Fsuccess"
    }
    ```
  </Accordion>

  <Accordion title="Dynamic Checkout (POST)">
    * Send the parameters as a JSON body in a POST request.
    * Supports both one-time and recurring payments. The handler retrieves the product, then creates a subscription if the product is recurring and a one-time payment otherwise.
    * The body needs `billing` (with `street`, `city`, `state`, `country`, and `zipcode`) and `customer`, plus `product_id` or `product_cart`. Subscriptions need `product_id`.
    * 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 payment link as the checkout URL:

    ```json theme={null}
    {
      "checkout_url": "https://test.checkout.dodopayments.com/cbq"
    }
    ```
  </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, and it needs at least one product. If the body has no `return_url`, the handler uses `returnUrl` from its config.

    Each `checkout_url` works once and expires after 24 hours, or after 15 minutes when you pass `confirm: true`. A session created with `payment_method_id` returns no `checkout_url`, so the handler responds with 400.

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

    ### Response Format

    Checkout sessions return a JSON response with the checkout URL:

    ```json theme={null}
    {
      "checkout_url": "https://test.checkout.dodopayments.com/session/cks_Gi6KGJ2zFJo9rq9Ukifwa"
    }
    ```
  </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. `CustomerPortal` takes the same `bearerToken` and `environment` options as `Checkout`.

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

The handler returns 400 if `customer_id` is missing, and 500 if the portal session can't be created.

## Webhook Route Handler

The webhook route handler verifies each request with your webhook secret, passed as `webhookKey`, before it runs your code:

* **Method:** Only POST requests are supported. Other methods return 405.
* **Signature Verification:** Verifies the `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers with `webhookKey`, following the Standard Webhooks 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 Astro, and the request fails.

### Supported Webhook Event Handlers

Every handler is optional and async, and 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 adaptor 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 Astro developer assistant. Your task is to guide a user through integrating the @dodopayments/astro adapter into their existing Astro project.

The @dodopayments/astro adapter provides route handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, designed for Astro server endpoints in src/pages/api/.

First, install the necessary packages. Use the package manager appropriate for your project (npm, yarn, or bun) based on the presence of lock files (e.g., package-lock.json for npm, yarn.lock for yarn, bun.lockb for bun):

npm install @dodopayments/astro

The endpoints must render on demand. Make sure the project has an Astro server adapter, and export prerender = false from each endpoint file unless the project uses output: 'server'.

Here's how you should structure your response:

    Ask the user which functionalities they want to integrate.

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

    Checkout Route Handler (for handling product checkouts)

    Customer Portal Route Handler (for managing customer subscriptions/details)

    Webhook Route Handler (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 Route Handler is selected:

Purpose: This handler manages different types of checkout flows. All checkout types (static, dynamic, and sessions) return JSON responses with checkout URLs for programmatic handling.
File Creation: Create a new file at src/pages/api/checkout.ts in your Astro project.

Code Snippet:

// src/pages/api/checkout.ts
import { Checkout } from "@dodopayments/astro";

export const prerender = false;

export const GET = Checkout({
  bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY,
  returnUrl: import.meta.env.DODO_PAYMENTS_RETURN_URL,
  environment: import.meta.env.DODO_PAYMENTS_ENVIRONMENT,
  type: "static", // optional, defaults to 'static'
});

export const POST = Checkout({
  bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY,
  returnUrl: import.meta.env.DODO_PAYMENTS_RETURN_URL,
  environment: import.meta.env.DODO_PAYMENTS_ENVIRONMENT,
  type: "session", // for checkout sessions, or "dynamic" for dynamic checkout
});

A file can export only one POST handler. To serve both dynamic checkout and checkout sessions, create a second endpoint file.

Configuration & Usage:

    bearerToken: Your Dodo Payments API key. It's recommended to set this via the DODO_PAYMENTS_API_KEY environment variable.

    returnUrl: (Optional) The URL to redirect the user to after a successful checkout. A return_url in the POST body takes precedence.

    environment: (Optional) Set to "test_mode" for testing, or omit/set to "live_mode" for production.

    type: (Optional) Set to "static" for GET/static checkout, "dynamic" for POST/dynamic checkout, or "session" for POST/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)

    Returns: {"checkout_url": "https://test.checkout.dodopayments.com/buy/pdt_nZuwz45WAs64n3l07zpQR?quantity=1"}

Dynamic Checkout (POST) - Returns JSON with checkout_url: Parameters are sent as a JSON body. It requires billing and customer, plus product_id or product_cart. Supports both one-time and recurring payments. Returns: {"checkout_url": "https://test.checkout.dodopayments.com/cbq"}. 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://test.checkout.dodopayments.com/session/cks_Gi6KGJ2zFJo9rq9Ukifwa"}. 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 Route Handler is selected:

Purpose: This handler 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 src/pages/api/customer-portal.ts in your Astro project.

Code Snippet:

// src/pages/api/customer-portal.ts
import { CustomerPortal } from "@dodopayments/astro";

export const prerender = false;

export const GET = CustomerPortal({
  bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY,
  environment: import.meta.env.DODO_PAYMENTS_ENVIRONMENT,
});

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.

If Webhook Route Handler is selected:

Purpose: This handler 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 src/pages/api/webhook.ts in your Astro project.

Code Snippet:

// src/pages/api/webhook.ts
import { Webhooks } from "@dodopayments/astro";

export const prerender = false;

export const POST = Webhooks({
  webhookKey: import.meta.env.DODO_PAYMENTS_WEBHOOK_KEY,
  onPayload: async (payload) => {
    // handle the payload
  },
  // ... other event handlers for granular control
});

Handler Details:

    Method: Only POST requests are supported. Other methods return 405.

    Signature Verification: The handler verifies the webhook signature using the 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 event handler that matches the payload type.

Supported Webhook Event Handlers:

    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 adapter functions correctly, you will need to manually set up the following environment variables in your Astro project's deployment environment (e.g., Vercel, Netlify, AWS, etc.):

    DODO_PAYMENTS_API_KEY: Your Dodo Payments API Key (required for Checkout and Customer Portal).

    DODO_PAYMENTS_RETURN_URL: (Optional) The URL to redirect to after a successful checkout (for Checkout handler).

    DODO_PAYMENTS_WEBHOOK_KEY: Your Dodo Payments Webhook Secret (required for Webhook handler).

    DODO_PAYMENTS_ENVIRONMENT: test_mode or live_mode. The handlers default to live_mode.

Example .env file:

DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
DODO_PAYMENTS_ENVIRONMENT=test_mode
DODO_PAYMENTS_RETURN_URL=your-return-url

Usage in your code:

bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY
webhookKey: import.meta.env.DODO_PAYMENTS_WEBHOOK_KEY

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. You can also add comments to their PR or chat depending on the context

```


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