Skip to main content
The @dodopayments/express adaptor gives your Express app three route handlers: checkoutHandler returns checkout URLs, CustomerPortal sends a customer to the Customer Portal, and Webhooks verifies webhook requests and calls your event handlers.

Checkout Handler

Create payment links and checkout sessions from your Express app.

Customer Portal

Let customers manage their subscriptions and details.

Webhooks

Verify and process Dodo Payments webhook events.

Installation

1

Install the Package

Run the following command in your project root:
2

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. While you build, use a test mode API key with DODO_PAYMENTS_ENVIRONMENT=test_mode, because a test mode key works only against test mode. DODO_PAYMENTS_RETURN_URL is optional.
Never commit your .env file or secrets to version control.

Route Handler Examples

The examples register routes on an Express app created with express(). The POST checkout handlers and the webhook handler read req.body, so each example registers express.json() before its routes.
Use this handler to integrate Dodo Payments checkout into your Express app. Supports static (GET), dynamic (POST), and session (POST) payment flows. Register each POST flow on its own path, because the first handler registered for a path answers every request to it.

Checkout Route Handler

The adaptor supports all three Dodo Payments checkout flows. Set type in the handler config to choose the flow a route serves. Every flow responds with JSON that contains a checkout_url for the customer to open.
  • Static Payment Links: type: "static", GET. Builds a payment link for one product from query parameters, after checking that the product exists.
  • Dynamic Payment Links: type: "dynamic", POST. Creates a one-time payment or a subscription with a payment link, depending on whether the product is recurring.
  • Checkout Sessions: type: "session", POST. Creates a checkout session from a product cart and customer details. Use this flow for new integrations.
checkoutHandler takes these options: Register the handler for GET when type is static, and for POST when type is dynamic or session. The handler returns 405 for other methods.

Supported Query Parameters

string
required
Product identifier, for example ?productId=pdt_nZuwz45WAs64n3l07zpQR.
integer
default:"1"
Quantity of the product.
string
Customer’s full name. Ignored if firstName or lastName is provided.
string
Customer’s first name.
string
Customer’s last name.
string
Customer’s email address.
string
Customer’s country, as an ISO 3166-1 alpha-2 code.
string
Customer’s street address.
string
Customer’s city.
string
Customer’s state or province.
string
Customer’s postal or ZIP code.
boolean
Set to true to disable the full name field.
boolean
Set to true to disable the first name field.
boolean
Set to true to disable the last name field.
boolean
Set to true to disable the email field.
boolean
Set to true to disable the country field.
boolean
Set to true to disable the address line field.
boolean
Set to true to disable the city field.
boolean
Set to true to disable the state field.
boolean
Set to true to disable the ZIP code field.
string
The payment currency, for example USD.
boolean
default:"true"
Show or hide the currency selector.
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.
boolean
default:"true"
Show or hide the discounts section.
string
Any query parameter that starts with metadata_ is passed to checkout as metadata, for example metadata_orderId=123.
A disable flag takes effect only when it’s true and the matching field has a value, for example email with disableEmail. The handler passes these parameters to a static payment link.
If productId is missing, the handler returns a 400 response. Invalid query parameters, or a product that doesn’t exist in your account, also result in a 400 response.

Response Format

Static checkout returns a JSON response with the checkout URL:
  • Send 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 (with an optional quantity) or product_cart. Subscriptions need product_id.
  • The handler also forwards metadata, allowed_payment_method_types, billing_currency, discount_codes (or the deprecated discount_code), return_url, show_saved_payment_methods, and tax_id. For subscriptions, it forwards addons, on_demand, and trial_period_days too. It ignores other fields.
  • For field details, refer to:
Dynamic Checkout calls the deprecated POST /payments and POST /subscriptions endpoints. Use Checkout Sessions for new integrations.

Response Format

Dynamic checkout returns a JSON response with the payment link as the checkout URL:
Send a checkout session payload as the JSON body. The handler creates a checkout session, which handles the complete payment flow for one-time purchases and subscriptions, and returns its checkout_url. product_cart is required and must contain at least one product.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.Refer to Checkout Sessions Integration Guide for more details and a complete list of supported fields.

Response Format

Checkout sessions return a JSON response with the checkout URL:

Customer Portal Route Handler

The Customer Portal Route Handler creates a Customer Portal session for the customer in customer_id and redirects the request to the portal link. CustomerPortal takes the bearerToken and environment options, the same as checkoutHandler. If Dodo Payments can’t create the session, the handler returns 500.

Query Parameters

string
required
The customer ID for the portal session, for example ?customer_id=cus_123.
boolean
If set to true, sends an email to the customer with the portal link.
Returns 400 if customer_id is missing. The handler doesn’t authenticate the request and opens the portal for any customer_id it receives, so put the route behind your own authentication and pass only the signed-in user’s customer ID.

Webhook Route Handler

The webhook handler verifies each request with your webhook secret, passed as webhookKey, then calls your event handlers.
Register express.json() before the webhook route. The handler verifies the signature against req.body, so it rejects every request unless the body is parsed JSON. Don’t use express.raw() for this route.
  • 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: 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, and returns 200 when they finish. The handler doesn’t catch errors that your event handlers throw.

Supported Webhook Event Handlers

Every handler is optional and async. For the payload of each event, see the Webhook Event Guide.

Prompt for LLM

Last modified on September 26, 2026