npx skills add ...
npx skills add dodopayments/skills --skill checkout-integration
Guide for starting hosted Checkout Sessions, payment links, and overlay or inline checkout for one-time and recurring products; use subscription-integration for post-checkout lifecycle management.
npx skills add dodopayments/skills --skill checkout-integration
Use client.checkoutSessions.create(...) to build hosted checkout pages or overlay checkout modals. This is the recommended path for all new payment integrations.
Dodo Payments offers three ways to collect payment:
| Method | Best For | Setup |
|---|---|---|
| Checkout Sessions (recommended) | Most integrations; full control | Server-side SDK call |
| Static Payment Links | No-code sharing; reusable URLs | Dashboard or direct URL |
| Overlay/Inline Checkout | Checkout stays on your site | Client-side SDK |
Legacy: Dynamic Payment Links created via POST /payments or POST /subscriptions are deprecated. Use Checkout Sessions instead.
Amounts in smallest currency unit: All prices are in cents (or equivalent). A $10 USD charge is 1000.
Checkout Session: A single-use session that generates a hosted checkout URL. Expires after 24 hours (or 15 minutes if confirm=true).
Return URL: Where the customer lands after payment. Query parameters include status=success and session_id.
Entitlement on webhook: The browser redirect is not the source of truth. Always verify payment via webhook before granting access. See webhook-integration skill for verification.
Response fields:
session_id: Unique checkout session IDcheckout_url: Hosted checkout URL (redirect customer here). Nullable when confirm=trueclient_secret: Present only if confirm=truepublishable_key: Present only if confirm=true. Pair it with client_secret for the inline SDK flowpayment_id: Present only if confirm=truepublishable_key is a per-session value returned for confirm-mode inline checkout. It is not a Stripe-style
publishable API key — Dodo issues no such credential, and every API key is secret and server-side only.
Use the dodopayments-checkout package for overlay or inline checkout that stays on your site.
Create the Checkout Session on the server. The browser sends a public plan slug, while the server derives the customer from the authenticated user and maps the slug to an allowlisted product id.
Browser code calls the server route and receives only the checkout URL. The Dodo Payments API bearer token stays in the server route and is never sent to the browser.
No-code shareable links. No server-side API call needed.
Example:
Supported parameters:
quantity: Item quantityredirect_url: Success redirect URLemail: Prefill customer emailfullName, firstName, lastName: Prefill namecountry, city, state, zipCode, addressLine: Prefill addresspaymentCurrency: Force currencymetadata_*: Custom metadata (e.g., metadata_orderId=123)After payment, the customer is redirected to your return_url with query parameters:
Query parameters:
status: success or failedsession_id: Checkout session IDDo not trust the browser redirect. Use the Express webhook route above: it receives the raw signed body
before express.json(), fulfills one-time purchases on verified payment.succeeded using
event.data.customer.customer_id, and grants subscription access only on verified subscription.active.
Webhook signature verification is covered in the webhook-integration skill.
1. Granting access from the return URL The return URL redirect is not proof of payment. Never grant access or fulfill an order from its query parameters; wait for a verified webhook event.
2. Using deprecated APIs
Do not use client.payments.create() or client.subscriptions.create() for new integrations. Both are deprecated. Use client.checkoutSessions.create().
3. Forgetting the environment flag
The default is live_mode. Always set environment: 'test_mode' during development to avoid charging real cards.
4. Assuming only one discount-code form is valid
Both discount_code (a string) and discount_codes (an array) are valid Checkout Session parameters.
5. Amounts in wrong unit
All amounts are in the smallest currency unit (cents for USD). $10 is 1000, not 10.
6. Reusing checkout URLs Checkout URLs are single-use. Create a new session for each checkout attempt.
7. Ignoring confirm=true behavior
When confirm=true, the session is finalized immediately and the checkout URL expires in 15 minutes instead of 24 hours. Use only when you have all required customer data.
8. Forgetting raw body for webhooks
Webhook signature verification requires the raw request body, not a re-serialized JSON object. Mount the
webhook route with express.raw({ type: 'application/json' }) before express.json().
9. Trusting a client-supplied product id
Do not accept an arbitrary pdt_ id from the browser. Authenticate the user, accept a public plan slug,
map it to an allowlisted product id on the server, and reject quantities that are not positive integers.