# Create Addon
Source: https://docs.dodopayments.com/api-reference/addons/create-addon
post /addons
Create a new addon product that can be attached to your main subscription products
# Get Addon
Source: https://docs.dodopayments.com/api-reference/addons/get-addon
get /addons/{id}
Retrieve detailed information about a specific addon using its unique identifier
# List Addons
Source: https://docs.dodopayments.com/api-reference/addons/list-addons
get /addons
Retrieve a comprehensive list of all available addons
# Update Addon
Source: https://docs.dodopayments.com/api-reference/addons/update-addon
patch /addons/{id}
Modify the details of an existing addon using its unique identifier
# Update Addon Images
Source: https://docs.dodopayments.com/api-reference/addons/update-addon-images
put /addons/{id}/images
Update images for an existing addon
# Create Brand
Source: https://docs.dodopayments.com/api-reference/brands/create-brand
post /brands
Create a new brand.
# Get Brand
Source: https://docs.dodopayments.com/api-reference/brands/get-brand
get /brands/{id}
Retrieve a specific brand by ID.
# List Brands
Source: https://docs.dodopayments.com/api-reference/brands/list-brands
get /brands
List all brands.
# Update Brand
Source: https://docs.dodopayments.com/api-reference/brands/update-brand
patch /brands/{id}
Update an existing brand's details.
# Update Brand Images
Source: https://docs.dodopayments.com/api-reference/brands/update-brand-images
put /brands/{id}/images
Update the images associated with a brand.
# Create Checkout Session
Source: https://docs.dodopayments.com/api-reference/checkout-sessions/create
post /checkouts
Unified endpoint for creating checkout sessions for all types of billing requirements.
# Get Checkout Session
Source: https://docs.dodopayments.com/api-reference/checkout-sessions/get-checkouts
get /checkouts/{id}
Retrieve details of a specific checkout session by its ID.
# Create Customer
Source: https://docs.dodopayments.com/api-reference/customers/create-customer
post /customers
Create a new customer in your Dodo Payments account.
# Create Customer Portal Session
Source: https://docs.dodopayments.com/api-reference/customers/create-customer-portal-session
post /customers/{customer_id}/customer-portal/session
Create a session of the Customer Portal for a specific customer.
# Get Customer Wallets
Source: https://docs.dodopayments.com/api-reference/customers/get-customer-wallets
get /customers/{customer_id}/wallets
Retrieve wallets associated with a specific customer.
# List Customer Wallet Ledger Entries
Source: https://docs.dodopayments.com/api-reference/customers/get-customer-wallets-ledger-entries
get /customers/{customer_id}/wallets/ledger-entries
List wallet ledger entries for a specific customer. Supports pagination and currency filter.
# List Customers
Source: https://docs.dodopayments.com/api-reference/customers/get-customers
get /customers
Get a list of all customers associated with your account.
# Get Customer Detail
Source: https://docs.dodopayments.com/api-reference/customers/get-customers-1
get /customers/{customer_id}
Get detailed information about a specific customer.
# Patch Customer
Source: https://docs.dodopayments.com/api-reference/customers/patch-customer
patch /customers/{customer_id}
Update a customer's information.
# Create Customer Wallet Ledger Entry
Source: https://docs.dodopayments.com/api-reference/customers/post-customer-wallets-ledger-entries
post /customers/{customer_id}/wallets/ledger-entries
Create a credit or debit ledger entry in a customer's wallet.
# Create Discount
Source: https://docs.dodopayments.com/api-reference/discounts/create-discount
post /discounts
Create a discount for your account.
# Delete Discount
Source: https://docs.dodopayments.com/api-reference/discounts/delete-discount
delete /discounts/{discount_id}
Delete a discount from your account.
# List Discounts
Source: https://docs.dodopayments.com/api-reference/discounts/list-discounts
get /discounts
Retrieve a list of all discounts associated with your account.
# Update Discount
Source: https://docs.dodopayments.com/api-reference/discounts/update-discount
patch /discounts/{discount_id}
Update a discount in your account.
# Validate Discount
Source: https://docs.dodopayments.com/api-reference/discounts/validate-discount
get /discounts/{discount_id}
Validate a discount code to check if it is valid and can be applied to the current cart.
# List Disputes
Source: https://docs.dodopayments.com/api-reference/disputes/get-disputes
get /disputes
Get a list of all disputes associated with your account.
# Get Dispute Detail
Source: https://docs.dodopayments.com/api-reference/disputes/get-disputes-1
get /disputes/{dispute_id}
Get detailed information about a specific dispute.
# Error Codes
Source: https://docs.dodopayments.com/api-reference/error-codes
Learn more about error codes and how to resolve them in Dodo Payments API.
## Overview
Dodo Payments API uses standard HTTP status codes and custom error codes to indicate the success or failure of API requests. When an error occurs, the API returns an appropriate HTTP status code and a JSON response containing detailed information about the error.
Each error response includes:
* An HTTP status code indicating the general category of the error
* A specific error code that identifies the exact nature of the error
* A human-readable error message explaining what went wrong
* Additional details about the error when applicable
Understanding these error codes and their meanings is crucial for:
* Debugging integration issues
* Implementing proper error handling in your application
* Providing meaningful feedback to end users
* Maintaining a robust payment processing system
The following sections provide detailed information about standard HTTP status codes and specific error codes you may encounter when using the Dodo Payments API.
## Standard API Error Codes
| Error code | HTTP Status | Description |
| ---------- | --------------------- | -------------------------------------------------------------------- |
| **400** | Bad Request | The request was malformed or contained invalid parameters |
| **401** | Unauthorized | Authentication failed or API key is invalid |
| **403** | Forbidden | The API key doesn't have permission to access the requested resource |
| **404** | Not Found | The requested resource doesn't exist |
| **405** | Method Not Allowed | The HTTP method is not supported for this endpoint |
| **409** | Conflict | The request conflicts with the current state of the resource |
| **422** | Unprocessable Entity | The request was well-formed but contained semantic errors |
| **429** | Too Many Requests | Rate limit exceeded |
| **500** | Internal Server Error | An unexpected error occurred on our servers |
| **502** | Bad Gateway | The server received an invalid response from an upstream server |
| **503** | Service Unavailable | The service is temporarily unavailable |
| **504** | Gateway Timeout | The server timed out while waiting for an upstream response |
## Error Response Format
When an error occurs, the API returns a JSON response with the following structure:
```json theme={null}
{
"code": "UNSUPPORTED_COUNTRY",
"message": "Country AI currently not supported"
}
```
## Error Codes Reference
* `ACTIVATION_LIMIT_LESS_THAN_CURRENT_AMOUNT`
* **Trigger:** License-key activations: new limit \< existing instance count
* **Message:** New activation limit cannot be less than current instances count
* `ADDONS_IN_USAGE_BASED_BILLING_NOT_SUPPORTED`
* **Trigger:** Attempt to add addons to usage-based billing subscriptions
* **Message:** Addons in Subscriptions are not supported for Usage Based Billing
* `ADDONS_NOT_ALLOWED_FOR_ON_DEMAND`
* **Trigger:** Attempt to add addons to on-demand subscriptions
* **Message:** Addons are not allowed for on demand subscriptions
* `BRAND_MISMATCH`
* **Trigger:** Cart items belong to different brands
* **Message:** All items in the product cart should belong to the same brand
* `BRAND_NOT_ENABLED`
* **Trigger:** Brand is disabled or not active
* **Message:** Brand provided is not enabled
* `BRAND_SUBMISSION_NOT_ENABLED`
* **Trigger:** Brand verification resubmission feature not enabled
* **Message:** Brand verificatin resubmission is not enabled
* `CHARGE_NOT_ALLOWED_FOR_SCHEDULED_CANCELLATION`
* **Trigger:** Attempt to charge subscription scheduled for cancellation
* **Message:** Subscription scheduled for cancellation
* `CHECKOUT_SESSION_CONSUMED`
* **Trigger:** Checkout session has already generated a payment
* **Message:** Checkout session has already been consumed
* `DISCOUNT_CODE_ALREADY_EXISTS`
* **Trigger:** Duplicate discount code creation
* **Message:** Discount Code already exists
* `DISCOUNT_CODE_EXPIRED`
* **Trigger:** Discount code past its `expires_at` date
* **Message:** Discount code expired
* `DISCOUNT_CODE_USAGE_LIMIT_EXCEEDED`
* **Trigger:** Discount reused after `usage_limit` reached
* **Message:** Usage limit cannot be less than times\_used / Discount code hit usage limit
* `DISCOUNT_NOT_AVAILABLE_FOR_ON_DEMAND`
* **Trigger:** Code applied to on-demand subscription
* **Message:** Discount coupon not available for on demand subscriptions
* `DISCOUNT_NOT_AVAILABLE_FOR_PRODUCT`
* **Trigger:** Code applied to unrelated product(s)
* **Message:** Discount coupon not available for this product
* `DUPLICATE_LINE_ITEMS_IN_REQUEST`
* **Trigger:** Same `item_id` appears twice in `items[]`
* **Message:** Duplicate item\_ids specified in items array
* `DUPLICATE_METER_IDS_IN_REQUEST`
* **Trigger:** Same meter ID appears multiple times in request
* **Message:** Duplicate Meter Ids are not allowed
* `EXCHANGE_RATE_NOT_FOUND`
* **Trigger:** No FX rate for `from → to` currency pair
* **Message:** Exchange rate not found to convert from Currency to Currency
* `EXISTING_REFUND_REQUEST_PROCESSING`
* **Trigger:** Previous refund request still being processed
* **Message:** A refund request with status "Pending" is still being processed
* `INACTIVE_LICENSE_KEY`
* **Trigger:** Key status ≠ `ACTIVE`
* **Message:** License key is not active
* `INACTIVE_SUBSCRIPTION_PLAN_CHANGE_NOT_SUPPORTED`
* **Trigger:** Plan change on inactive subscription
* **Message:** Changing plans is not supported for inactive subscriptions
* `INSUFFICIENT_WALLET_FUNDS`
* **Trigger:** Wallet balance \< debit amount
* **Message:** Insufficient funds in wallet
* `INTEGER_CONVERSION_FAILURE`
* **Trigger:** Any integer ↔ string/decimal conversion that fails server-side
* **Message:** Integer Conversion Failure
* `INTERNAL_SERVER_ERROR`
* **Trigger:** Uncaught exceptions; you should log details server-side
* **Message:** *No public message (generic 500)*
* `INVALID_DISCOUNT_CODE`
* **Trigger:** Code does not exist / not applicable
* **Message:** Invalid Discount Code / Discount Code cannot be applied to any product in the cart
* `INVALID_PERCENTAGE`
* **Trigger:** Percent amount > 100% (or 10,000 basis points)
* **Message:** Percentage amount cannot be more than 10000 / Discount code amount cannot be more than 100%
* `INVALID_QUANTITY`
* **Trigger:** Invalid quantity specified for usage-based pricing
* **Message:** Only 1 quantity allowed in usage based price products
* `INVALID_QUERY_PARAMS`
* **Trigger:** Mutually exclusive / malformed query parameters
* **Message:** Query params should only contain either time\_frame or (start, end)
* `INVALID_REQUEST_BODY`
* **Trigger:** Malformed JSON or schema violation
* **Message:** Your request body is invalid. Please check your request headers and object.
* `INVALID_REQUEST_PARAMETERS`
* **Trigger:** Semantics wrong (e.g. date in past)
* **Message:** Cannot change next\_billing\_date to past time
* `INVALID_SUGGESTED_PRICE`
* **Trigger:** PWYW price \< minimum allowed price
* **Message:** Suggested Price cannot be lower than minimum price. In case of pay what you want, price is considered as minimum accepted amount
* `INVALID_TAX_ID`
* **Trigger:** VAT/GST/TIN failed validation
* **Message:** Tax Id is invalid
* `LICENSE_KEY_LIMIT_REACHED`
* **Trigger:** Activations = limit
* **Message:** License key activation limit reached
* `LICENSE_KEY_NOT_FOUND`
* **Trigger:** Instance ID or key ID invalid
* **Message:** License key instance not found or does not belong to this license key
* `LINE_ITEM_FULLY_REFUNDED`
* **Trigger:** Attempt to refund already fully refunded line item
* **Message:** Line item {id} has been fully refunded cannot be refunded further.
* `LINE_ITEM_NOT_FOUND`
* **Trigger:** Item ID not part of the referenced payment
* **Message:** Line item {id} not found in payment
* `LINE_ITEM_PRORATED`
* **Trigger:** Refund or update attempted on a prorated line
* **Message:** Line item {id} cannot be refunded because its prorated
* `LINE_ITEM_REFUND_AMOUNT_TOO_HIGH`
* **Trigger:** Refund amount > paid amount (tax incl.)
* **Message:** Line item {id} requested refund amount including tax is {amount} which is above the paid amount {amount}
* `LINE_ITEM_REFUND_AMOUNT_TOO_LOW`
* **Trigger:** Refund amount below minimum threshold
* **Message:** Line item {id} requested refund amount is {amount} which is too low
* `MAXIMUM_KEYS_REACHED`
* **Trigger:** Metadata / custom-fields exceeded 50 pairs
* **Message:** Exceeds 50 key-value pairs
* `MERCHANT_NOT_LIVE`
* **Trigger:** Business still in test/sandbox mode
* **Message:** Merchant is not live
* `METER_IS_DELETED`
* **Trigger:** Attempt to use deleted meter
* **Message:** The Meter is already been deleted
* `MISSING_ADDON_IDS`
* **Trigger:** `addon_id` list empty or unknown IDs
* **Message:** One or more product IDs do not exist: {id}
* `MISSING_METER_IDS`
* **Trigger:** Meter ID list empty or contains invalid IDs
* **Message:** One or more meter IDs do not exist: {id}
* `MISSING_PRODUCT_INFORMATION`
* **Trigger:** Product exists but mandatory info missing
* **Message:** Product {id} exists but other mandatory information is missing or invalid
* `NEGATIVE_BALANCE_ADJUSTMENT`
* **Trigger:** Attempt to make wallet balance negative
* **Message:** Wallet balance is not allowed to be made negative
* `NO_ELIGIBLE_PAYMENT_METHODS`
* **Trigger:** After filtering, nothing left
* **Message:** No eligible payment methods found
* `NO_EXPIRY_ON_SUBSCRIPTION_LICENSE_KEYS`
* **Trigger:** Attempt to set expiry on sub-based key
* **Message:** Cannot set expiry date for subscription-based license key
* `NOT_FOUND`
* **Trigger:** Generic 404 for any missing resource
* **Message:** Item not found *(or more specific)*
* `ON_DEMAND_PLAN_CHANGE_NOT_SUPPORTED`
* **Trigger:** Plan swap not allowed for on-demand
* **Message:** Changing plans is not supported for on demand subscriptions
* `ON_DEMAND_SUBSCRIPTIONS_NOT_ENABLED`
* **Trigger:** Business has feature flag off
* **Message:** On demand subscriptions not enabled for this business
* `ON_DEMAND_USAGE_BASED_BILLING_NOT_SUPPORTED`
* **Trigger:** Attempt to use on-demand with usage-based billing
* **Message:** On Demand Subscriptions are not supported for Usage Based Billing
* `PAY_AS_YOU_WANT_AMOUNT_REQUIRED`
* **Trigger:** Price missing for PWYW product
* **Message:** Amount is mandatory for pay as you want product
* `PAYMENT_ALREADY_REFUNDED`
* **Trigger:** Duplicate refund
* **Message:** This payment has already been refunded
* `PAYMENT_HAS_BEEN_REFUNDED`
* **Trigger:** Payment has been fully refunded
* **Message:** The Payment ID has been fully refunded.
* `PAYMENT_NOT_SUCCEEDED`
* **Trigger:** Attempt to refund/process unsuccessful payment
* **Message:** The provided payment has not succeeded
* `PLAN_CHANGE_NOT_ALLOWED_FOR_SCHEDULED_CANCELLATION`
* **Trigger:** Plan change attempted on subscription scheduled for cancellation
* **Message:** Subscription scheduled for cancellation
* `PREVIOUS_PAYMENT_PENDING`
* **Trigger:** Attempt to create charge while previous is in non-terminal state
* **Message:** Cannot create new charge as previous payment is not successful yet
* `PRODUCT_CART_EMPTY`
* **Trigger:** Empty product cart submitted
* **Message:** product\_cart is empty
* `PRODUCT_IS_DELETED`
* **Trigger:** Product soft-deleted
* **Message:** *No messages*
* `REFUND_AMOUNT_EXCEEDS_PAID_AMOUNT`
* **Trigger:** Aggregate refund amount > paid amount
* **Message:** The calculated refund amount is larger than the paid amount
* `REFUND_WINDOW_EXPIRED`
* **Trigger:** Outside allowable refund window
* **Message:** Refunds cannot be initiated {days} days after payment creation. Contact [support@dodopayments.com](mailto:support@dodopayments.com).
* `REQUEST_AMOUNT_BELOW_MINIMUM`
* **Trigger:** Amount \< product minimum
* **Message:** Amount cannot be less than minimum amount specified for the product
* `SUBSCRIPTION_EXPIRED`
* **Trigger:** Billing past `ends_at`
* **Message:** Subscription expired cannot create new charges
* `SUBSCRIPTION_INACTIVE`
* **Trigger:** Status ≠ `ACTIVE`
* **Message:** Subscription is not active
* `SUBSCRIPTION_NOT_ON_DEMAND`
* **Trigger:** Expected on-demand but got fixed interval
* **Message:** Subscription is already not on demand
* `SUBSCRIPTION_PAYMENT_RETRY_LIMIT_EXCEEDED`
* **Trigger:** Subscription payment retries exceeded maximum attempts
* **Message:** Maximum retry limit of 10 attempts exceeded for this subscription
* `TOO_MANY_REQUESTS`
* **Trigger:** 429 rate-limit
* **Message:** *No messages*
* `TOTAL_PAYMENT_AMOUNT_BELOW_MINIMUM_AMOUNT`
* **Trigger:** Combined cart total \< gateway minimum
* **Message:** Minimum amount of {display_str} is required to process payment
* `UNABLE_TO_EDIT_PRIMARY_BRAND`
* **Trigger:** Attempt to update primary brand via regular API
* **Message:** Primary brand cannot be updated via this API endpoint.
* `UNAUTHORIZED`
* **Trigger:** No API key or invalid token / scope
* **Message:** You are not authorised to perform this action
* `UNSUPPORTED_ACTION`
* **Trigger:** Unsupported action for resource type
* **Message:** Changing plans for usage based subscriptions is not supported
* `UNSUPPORTED_BILLING_CURRENCY`
* **Trigger:** Subscriptions restricted to USD
* **Message:** Non USD billing currency is not supported for subscriptions
* `UNSUPPORTED_COUNTRY`
* **Trigger:** Geo not yet supported
* **Message:** Country {country_name} currently not supported
* `UNSUPPORTED_CURRENCY`
* **Trigger:** Product or addon currency invalid
* **Message:** Currency is not currently supported / Only USD and INR products supported currently / Only USD and INR supported for addon price / Can only request USD or INR for billing\_currency / Currency Not Supported / Unexpected currency for Indian card subscripitons
* `UNSUPPORTED_DISCOUNT_TYPE`
* **Trigger:** Flat-amount discounts, etc., not yet live
* **Message:** Only percentage discount codes are supported for now
* `UNSUPPORTED_PAYMENT_CURRENCY`
* **Trigger:** Payment currency blocked for connector
* **Message:** INR transaction is not supported for this transaction
* `UNSUPPORTED_TAX_CATEGORY`
* **Trigger:** Tax category string not in enum
* **Message:** Category {category} currently not supported
* `UNSUCCESSFUL_PAYMENT_ID`
* **Trigger:** Payment ID references unsuccessful payment
* **Message:** The Payment ID has an unsuccessful status.
* `ZERO_AMOUNT_PAYMENT_REFUND_NOT_ALLOWED`
* **Trigger:** Attempt to refund zero-amount payment
* **Message:** Cannot refund a payment with zero currency amount
## Best Practices
1. Always handle errors gracefully in your application
2. Implement proper error logging
3. Use appropriate error messages for end users
4. Implement retry logic for transient errors
5. Contact support for unresolved issues
## Support
For additional help with error codes or integration issues, please contact our support team at [support@dodopayments.com](mailto:support@dodopayments.com).
# Introduction
Source: https://docs.dodopayments.com/api-reference/introduction
The Dodo Payments API provides comprehensive endpoints for payment processing, subscription management, and digital product delivery. Our RESTful API follows industry standards with detailed responses for all operations.
Accelerate your integration by using official SDKs for TypeScript, Python, Go, PHP, Java, Kotlin, C#, Ruby, and React Native. These libraries simplify API requests, authentication, and error handling, letting you focus on building great payment experiences.
## Environment URLs
* **Test Mode**: [`https://test.dodopayments.com`](https://test.dodopayments.com)
* **Live Mode**: [`https://live.dodopayments.com`](https://live.dodopayments.com)
Learn more about [Test Mode vs Live Mode](/miscellaneous/test-mode-vs-live-mode).
## API Key Management and Authentication
Navigate to **Developer → API Keys** in your dashboard to manage your keys.
Select **Add API Key**, provide a descriptive name, and generate your key.
Copy the generated key immediately and ensure it is stored securely.
Use your API keys to authenticate all requests. Apply the following authorization format:
```bash theme={null}
Authorization: Bearer YOUR_API_KEY
```
Never expose your secret API keys in client-side code or public repositories.
## Response Format
```json Success Response theme={null}
{
"id": "pay_1234567890",
"status": "completed",
"amount": 2999,
"currency": "USD",
"created_at": "2024-01-15T10:30:00Z"
}
```
```json Error Response theme={null}
{
"code": "INVALID_REQUEST",
"message": "The request contains invalid parameters"
}
```
## Rate Limits
* **Standard**: 1000 requests per minute per business
* **Burst**: Up to 100 requests per second
Monitor `X-RateLimit-Remaining` header to track usage.
## Error Handling
To effectively manage errors, consult the *Error Codes* and *Transaction Failures* sections for detailed guidance.
Delve into comprehensive error details and their resolutions.
Gain insights into common transaction issues and their solutions.
## Webhooks
Receive real-time notifications about payment events. See our [Webhook Guide](/developer-resources/webhooks) for setup instructions.
Set up webhooks for real-time notifications and event handling.
# Activate License
Source: https://docs.dodopayments.com/api-reference/licenses/activate-license
post /licenses/activate
This endpoint allows you to activate a license for the user.
**No API Key Required**: This is a public endpoint that does not require authentication. You can call it directly from client applications, desktop software, or CLIs to activate license keys without exposing your API credentials.
# Deactivate License
Source: https://docs.dodopayments.com/api-reference/licenses/deactivate-license
post /licenses/deactivate
This endpoint allows you to deactivate a license for the user.
**No API Key Required**: This is a public endpoint that does not require authentication. You can call it directly from client applications, desktop software, or CLIs to deactivate license keys without exposing your API credentials.
# Get License Key
Source: https://docs.dodopayments.com/api-reference/licenses/get-license-key
get /license_keys/{id}
Retrieve the details of a specific license key by its ID.
# Get License Key Instance
Source: https://docs.dodopayments.com/api-reference/licenses/get-license-key-instance
get /license_key_instances/{id}
Retrieve details of a specific license key instance by its ID.
# Get License Key Instances
Source: https://docs.dodopayments.com/api-reference/licenses/get-license-key-instances
get /license_key_instances
Retrieve a list of instances for a specific license key.
# Get License Keys
Source: https://docs.dodopayments.com/api-reference/licenses/get-license-keys
get /license_keys
Retrieve a list of license keys associated with your account.
# Update License Key
Source: https://docs.dodopayments.com/api-reference/licenses/update-license-key
patch /license_keys/{id}
This endpoint allows you to update a license key by its ID.
# Update License Key Instance
Source: https://docs.dodopayments.com/api-reference/licenses/update-license-key-instance
patch /license_key_instances/{id}
This endpoint allows you to update a specific license key instance by its ID.
# Validate License
Source: https://docs.dodopayments.com/api-reference/licenses/validate-license
post /licenses/validate
This endpoint allows you to validate a license for the user.
**No API Key Required**: This is a public endpoint that does not require authentication. You can call it directly from client applications, desktop software, or CLIs to validate license keys without exposing your API credentials.
# Metadata Guide
Source: https://docs.dodopayments.com/api-reference/metadata
Learn how to use metadata to store additional information about your Dodo Payments objects
## Introduction
Metadata allows you to store additional, structured information about your objects in Dodo Payments. You can attach metadata to most Dodo Payments objects, including payments, subscriptions, and more.
## Overview
* Metadata keys can be up to 40 characters long
* Metadata values can be up to 500 characters long
* You can have up to 50 metadata key-value pairs per object
* Keys should only contain alphanumeric characters, dashes, and underscores
* Metadata is not searchable using our API but is returned in API responses and webhooks
## Use Cases
Metadata is useful for:
* Storing external IDs or references
* Adding internal annotations
* Linking Dodo Payments objects to your system
* Categorizing transactions
* Adding custom attributes for reporting
## Adding Metadata
You can add metadata when creating or updating objects through the API. For products, you also have the option to add metadata directly from the dashboard UI.
### Via API
```javascript theme={null}
// Adding metadata when creating a customer
const customer = await client.customers.create({
name: 'John Doe',
email: 'john@example.com',
metadata: {
account_tier: 'premium',
signup_source: 'website',
referral_code: 'REF123'
}
});
// Adding metadata when creating a payment
const payment = await client.payments.create({
billing: { city: 'city', country: 'AF', state: 'state', street: 'street', zipcode: 0 },
customer: { customer_id: 'customer_id' },
product_cart: [{ product_id: 'product_id', quantity: 0 }],
metadata:{order_id: 'ORD-123', customer_notes: 'Customer notes'}
});
// Adding metadata when creating a product
const product = await client.products.create({
name: 'Premium Software License',
price: 9900,
currency: 'USD',
metadata: {
category: 'software',
license_type: 'premium',
support_tier: 'priority'
}
});
```
### Via Dashboard UI (Products Only)
For products, you can also add metadata directly from the Dodo Payments dashboard when creating or editing a product. The metadata section allows you to easily add custom key-value pairs without writing code.
Using the dashboard UI for product metadata is particularly useful for non-technical team members who need to manage product information and categories.
## Retrieving Metadata
Metadata is included in API responses when retrieving objects:
```javascript theme={null}
// Retrieving customer metadata
const customer = await client.customers.retrieve('cus_123');
console.log(customer.metadata.account_tier); // 'premium'
// Retrieving payment metadata
const payment = await client.payments.retrieve('pay_123');
console.log(payment.metadata.order_id); // '6735'
```
## Searching and Filtering
While metadata is not directly searchable via our API, you can:
1. Store important identifiers in metadata
2. Retrieve objects using their primary IDs
3. Filter the results in your application code
```javascript theme={null}
// Example: Find a customer using metadata
const customers = await client.customers.list({
limit: 100
});
const matchingCustomer = customers.data.find(
customer => customer.metadata?.account_tier === 'premium'
);
// Example: Find a payment using your order ID
const payments = await client.payments.list({
limit: 100
});
const matchingPayment = payments.data.find(
payment => payment.metadata.order_id === '6735'
);
```
## Best Practices
### Do:
* Use consistent naming conventions for metadata keys
* Document your metadata schema internally
* Keep values short and meaningful
* Use metadata for static data only
* Consider using prefixes for different systems (e.g., `crm_id`, `inventory_sku`)
### Don't:
* Store sensitive data in metadata
* Use metadata for frequently changing values
* Rely on metadata for critical business logic
* Store duplicate information that's available elsewhere in the object
* Use special characters in metadata keys
## Supported Objects
Metadata is supported on the following objects:
| Object Type | Support |
| ------------- | ------- |
| Customers | ✓ |
| Payments | ✓ |
| Subscriptions | ✓ |
| Products | ✓ |
## Webhooks and Metadata
Metadata is included in webhook events, making it easy to handle notifications with your custom data:
```javascript theme={null}
// Example webhook handler
app.post('/webhook', (req, res) => {
const event = req.body;
if (event.type === 'payment.succeeded') {
const orderId = event.data.object.metadata.order_id;
// Process order using your internal order ID
}
if (event.type === 'customer.created') {
const accountTier = event.data.object.metadata.account_tier;
// Handle customer creation with custom metadata
}
});
```
# Archive Meter
Source: https://docs.dodopayments.com/api-reference/meters/archive-meter
delete /meters/{id}
Archive a meter by its ID.
# Create Meter
Source: https://docs.dodopayments.com/api-reference/meters/create-meter
post /meters
Create a new meter.
# List Meters
Source: https://docs.dodopayments.com/api-reference/meters/get-meters
get /meters
Get a list of all meters.
# Get Meter
Source: https://docs.dodopayments.com/api-reference/meters/retrieve-meter
get /meters/{id}
Retrieve a meter by its ID.
# Unarchive Meter
Source: https://docs.dodopayments.com/api-reference/meters/unarchive-meter
post /meters/{id}/unarchive
Unarchive a meter by its ID.
# Charge Subscription
Source: https://docs.dodopayments.com/api-reference/misc/charge-subscriptions
post /subscriptions/{subscription_id}/charge
Charge a customer for an on-demand subscription.
# Supported Countries
Source: https://docs.dodopayments.com/api-reference/misc/supported-countries
get /checkout/supported_countries
Retrieve a list of countries that are supported by the Dodo Payments for your customers.
You will receive a list of [ISO Alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country codes representing the countries we **currently support for payments**. You can view the full list of supported countries [here](/miscellaneous/list-of-countries-we-accept-payments-from). This list is updated regularly as we expand our coverage. Note: The available options include ISO Alpha-2 codes for **all countries**, not just the ones we currently support. Please refer to the supported countries list to confirm availability.
# Get Invoice
Source: https://docs.dodopayments.com/api-reference/payments/get-invoice
get /invoices/payments/{payment_id}
Get an invoice by Payment ID.
# List Payments
Source: https://docs.dodopayments.com/api-reference/payments/get-payments
get /payments
Get a list of all payments associated with your account.
# Get Payment Detail
Source: https://docs.dodopayments.com/api-reference/payments/get-payments-1
get /payments/{payment_id}
Get detailed information about a specific payment.
# Create One Time Payment
Source: https://docs.dodopayments.com/api-reference/payments/post-payments
post /payments
Create a one-time payment for a customer.
# Retrieve Line Items
Source: https://docs.dodopayments.com/api-reference/payments/retrieve-line-items
get /payments/{payment_id}/line-items
Retrieve line items for a specific payment.
# List Payouts
Source: https://docs.dodopayments.com/api-reference/payouts/get-payouts
get /payouts
Get a list of all payouts associated with your account.
# Archive Products
Source: https://docs.dodopayments.com/api-reference/products/archive-product
delete /products/{id}
Archive a product associated with your account.
# List Products
Source: https://docs.dodopayments.com/api-reference/products/get-products
get /products
Get a list of all products associated with your account.
# Get Product Detail
Source: https://docs.dodopayments.com/api-reference/products/get-products-1
get /products/{id}
Get detailed information about a specific product.
# Update Product
Source: https://docs.dodopayments.com/api-reference/products/patch-products
patch /products/{id}
Update a product's details.
# Create Product
Source: https://docs.dodopayments.com/api-reference/products/post-products
post /products
Create a new product.
# Update Product Images
Source: https://docs.dodopayments.com/api-reference/products/put-products-images
put /products/{id}/images
Update a product's images.
# Unarchive Products
Source: https://docs.dodopayments.com/api-reference/products/unarchive-product
post /products/{id}/unarchive
Unarchive a product associated with your account.
# Update Files
Source: https://docs.dodopayments.com/api-reference/products/update-files
put /products/{id}/files
Update the files associated with a product.
# Get Refund Receipt
Source: https://docs.dodopayments.com/api-reference/refunds/get-refund-receipt
get /invoices/refunds/{refund_id}
Get a refund receipt for a refund.
# List Refunds
Source: https://docs.dodopayments.com/api-reference/refunds/get-refunds
get /refunds
Get a list of all refunds associated with your account.
# Get Refund Detail
Source: https://docs.dodopayments.com/api-reference/refunds/get-refunds-1
get /refunds/{refund_id}
Get detailed information about a specific refund.
# Create Refund
Source: https://docs.dodopayments.com/api-reference/refunds/post-refunds
post /refunds
Create a refund for a payment.
# Change Plan
Source: https://docs.dodopayments.com/api-reference/subscriptions/change-plan
post /subscriptions/{subscription_id}/change-plan
Modify an existing subscription's plan, enabling both upgrades and downgrades to different pricing tiers.
Note: This will use the existing payment information of the customer to upgrade/downgrade the plan.
# Create Charge (On-Demand)
Source: https://docs.dodopayments.com/api-reference/subscriptions/create-charge
post /subscriptions/{subscription_id}/charge
Create a charge for a subscription. This is useful for on-demand billing.
# Get Subscription Detail
Source: https://docs.dodopayments.com/api-reference/subscriptions/get-subscriptions
get /subscriptions/{subscription_id}
Get detailed information about a specific subscription.
# List subscriptions
Source: https://docs.dodopayments.com/api-reference/subscriptions/get-subscriptions-1
get /subscriptions
Get a list of all subscriptions associated with your account.
# Get Subscription Usage History
Source: https://docs.dodopayments.com/api-reference/subscriptions/get-usage-history
get /subscriptions/{subscription_id}/usage-history
Retrieve the complete usage history for a subscription with usage-based billing.
# Update Subscription
Source: https://docs.dodopayments.com/api-reference/subscriptions/patch-subscriptions
patch /subscriptions/{subscription_id}
Update a subscription's details.
# Create Subscription
Source: https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions
post /subscriptions
Create a subscription for a customer.
# Transaction Failures
Source: https://docs.dodopayments.com/api-reference/transaction-failures
Learn about different transaction failure reasons and how to handle them in Dodo Payments API.
## Overview
Dodo Payments API provides detailed transaction failure reasons to help you understand why a payment attempt was unsuccessful. These failure reasons are standardized across different payment methods and providers, making it easier to implement consistent error handling in your application.
Each transaction failure response includes:
* A specific failure reason code
* A human-readable error message
* Additional context about the failure when available
* Whether the error is user-correctable or requires system intervention
Understanding these failure reasons is crucial for:
* Providing clear feedback to your customers
* Implementing appropriate retry logic
* Handling different failure scenarios gracefully
* Maintaining a reliable payment processing system
## Transaction Failure Reasons
The following table lists all possible transaction failure reasons, their meanings, and whether they are user-correctable errors.
| Failure Code | User Error | Description |
| ---------------------------------- | ---------- | --------------------------------------------------------------- |
| `AUTHENTICATION_FAILURE` | Yes | The authentication process failed during the transaction |
| `AUTHENTICATION_REQUIRED` | Yes | Additional authentication is needed to complete the transaction |
| `AUTHENTICATION_TIMEOUT` | Yes | The authentication process timed out |
| `CARD_DECLINED` | No | The card was declined by the issuing bank |
| `CARD_NOT_ACTIVATED` | Yes | The card has not been activated by the cardholder |
| `CARD_VELOCITY_EXCEEDED` | Yes | Too many transactions attempted in a short period |
| `CUSTOMER_CANCELLED` | Yes | The customer cancelled the transaction |
| `DO_NOT_HONOR` | No | The issuing bank declined the transaction |
| `EXPIRED_CARD` | Yes | The card has expired |
| `FRAUDULENT` | Yes | The transaction was flagged as potentially fraudulent |
| `GENERIC_DECLINE` | No | The transaction was declined for an unspecified reason |
| `INCORRECT_CVC` | Yes | The provided CVC code was incorrect |
| `INCORRECT_NUMBER` | Yes | The card number was entered incorrectly |
| `INSUFFICIENT_FUNDS` | Yes | The account has insufficient funds to complete the transaction |
| `INVALID_ACCOUNT` | Yes | The account details provided are invalid |
| `INVALID_AMOUNT` | Yes | The transaction amount is invalid |
| `INVALID_CARD_NUMBER` | Yes | The card number format is invalid |
| `INVALID_CARD_OWNER` | Yes | The card owner information is invalid |
| `INVALID_CVC` | Yes | The CVC format is invalid |
| `INVALID_EXPIRY_YEAR` | Yes | The card expiry year is invalid |
| `INVALID_PIN` | Yes | The provided PIN is incorrect |
| `INVALID_REQUEST` | Yes | The transaction request contains invalid data |
| `INVALID_UPI_ID` | Yes | The UPI ID provided is invalid |
| `LIMIT_EXCEEDED` | Yes | The transaction exceeds the card or account limit |
| `LIVE_MODE_TEST_CARD` | Yes | A test card was used in live mode |
| `LOST_CARD` | Yes | The card has been reported as lost |
| `MANDATE_INVALID` | Yes | The payment mandate is invalid |
| `MANDATE_REQUIRED` | Yes | A mandate is required for this transaction |
| `MANDATE_REQUIRED_SYSTEM` | No | System requires a mandate for this transaction type |
| `NETWORK_ERROR` | No | A network error occurred during the transaction |
| `NETWORK_TIMEOUT` | No | The network request timed out |
| `ORDER_CREATION_FAILED` | No | Failed to create the order for the transaction |
| `PAYMENT_METHOD_PROVIDER_DECLINED` | Yes | The payment method provider declined the transaction |
| `PAYMENT_METHOD_UNSUPPORTED` | Yes | The payment method is not supported for this transaction |
| `PICKUP_CARD` | Yes | The card has been reported as stolen and should be picked up |
| `PROCESSING_ERROR` | No | An error occurred while processing the transaction |
| `PROVIDER_UNSUPPORTED` | No | The payment provider does not support this transaction type |
| `REENTER_TRANSACTION` | Yes | The transaction needs to be re-entered |
| `REVOCATION_OF_AUTHORIZATION` | Yes | The authorization for the transaction was revoked |
| `STOLEN_CARD` | Yes | The card has been reported as stolen |
| `TRANSACTION_NOT_ALLOWED` | Yes | The transaction is not allowed for this card or account |
| `TRANSACTION_NOT_APPROVED` | Yes | The transaction was not approved |
| `TRY_AGAIN_LATER` | No | The transaction should be retried later |
| `UNKNOWN_ERROR` | No | An unknown error occurred |
**User Error** indicates whether the payment decline can be resolved by the customer. When `true`, the customer can take action to fix the issue (e.g., entering correct card details). When `false`, the decline is due to system-level issues or bank restrictions that cannot be resolved by the customer.
## Support
For additional help with transaction failures or integration issues, please contact our support team at [support@dodopayments.com](mailto:support@dodopayments.com).
# Get Event
Source: https://docs.dodopayments.com/api-reference/usage-events/get-event
get /events/{event_id}
Get an event by its ID.
# Ingest Events
Source: https://docs.dodopayments.com/api-reference/usage-events/ingest-events
post /events/ingest
Ingest events for usage-based billing.
# List Events
Source: https://docs.dodopayments.com/api-reference/usage-events/list-events
get /events
Get a list of all events.
# Create Webhook
Source: https://docs.dodopayments.com/api-reference/webhooks/create-webhook
post /webhooks
Create a new webhook for a business.
# Delete Webhook
Source: https://docs.dodopayments.com/api-reference/webhooks/delete-webhook
delete /webhooks/{webhook_id}
Delete a specific webhook.
# Get Webhook Details
Source: https://docs.dodopayments.com/api-reference/webhooks/get-details
get /webhooks/{webhook_id}
Get detailed information about a specific webhook.
# Get Webhook Headers
Source: https://docs.dodopayments.com/api-reference/webhooks/get-webhook-headers
get /webhooks/{webhook_id}/headers
Get the headers for a specific webhook.
# Get Webhook Signing Key
Source: https://docs.dodopayments.com/api-reference/webhooks/get-webhook-signing-key
get /webhooks/{webhook_id}/secret
Get the signing key for a specific webhook.
# List Webhooks
Source: https://docs.dodopayments.com/api-reference/webhooks/list-webhooks
get /webhooks
List all webhooks for a business.
# Update Webhook
Source: https://docs.dodopayments.com/api-reference/webhooks/update-webhook
patch /webhooks/{webhook_id}
Update a specific webhook.
# Update Webhook Headers
Source: https://docs.dodopayments.com/api-reference/webhooks/update-webhook-headers
patch /webhooks/{webhook_id}/headers
Update the headers for a specific webhook.
# Introduction
Source: https://docs.dodopayments.com/changelog/introduction
Welcome to the Dodo Payments Changelog! Stay updated on all the latest features and improvements we've rolled out to enhance your payment processing experience. At Dodo Payments, we strive to provide a seamless, transparent, and robust platform for global merchants.
# Version Logs
| Version | Date | Description | Link |
| --------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
| `v1.56.0` | `27-10-2025` | Streamlined onboarding, C# SDK launch, webhook verification, Two-Factor Authentication, overlay checkout sessions, and usage-based subscription plan changes | [Link](/changelog/v1.56.0) |
| `v1.53.2` | `13-9-2025` | Customer credits, enhanced refund details, higher meter limits, license activation context, and \$0 downgrades | [Link](/changelog/v1.53.2) |
| `v1.52.4` | `3-9-2025` | Usage-Based Billing, public Refund Receipt API, 100% subscription discounts, and checkout session ID in payments | [Link](/changelog/v1.52.4) |
| `v1.51.0` | `23-8-2025` | Powerful Checkout Sessions API, Metadata in Payments Export, and Subscription Next Billing Date Update | [Link](/changelog/v1.51.0) |
| `v1.47.0` | `11-8-2025` | Dashboard UI overhaul, discount codes for subscriptions, expanded Adaptive Currency, webhook management API, and product metadata support. | [Link](/changelog/v1.47.0) |
| `v1.44.0` | `3-8-2025` | Adaptive Currency, Turkish Language Support, and New Proration Mode | [Link](/changelog/v1.44.0) |
| `v1.43.0` | `24-7-2025` | Comprehensive Webhooks Upgrade: endpoint management, security, delivery logs, debugging, and monitoring—now fully self-serve in the dashboard. | [Link](/changelog/v1.43.0) |
| `v1.38.0` | `9-7-2025` | Analytics Chart Sharing, Multiple Partial Refunds, Cumulative Revenue Analytics, Context7 Integration, Next.js Adapter | [Link](/changelog/v1.38.0) |
| `v1.37.0` | `2-7-2025` | Enhanced Analytics Dashboard with comprehensive revenue and retention insights, Reports 2.0 with detailed business reporting capabilities, and improved subscription management features for better business intelligence and operational efficiency. | [Link](/changelog/v1.37.0) |
| `v1.34.4` | `26-6-2025` | Enhanced Dashboard Analytics with comprehensive new metrics across three key dashboard sections: Revenue Analytics, Customer Analytics, and Payment Success Rate Analytics. | [Link](/changelog/v1.34.4) |
| `v1.34.0` | `18-6-2025` | Added Digital Product Delivery, React Native SDK beta release, new proration modes, adaptive currency for subscriptions, and enhanced verification form management. | [Link](/changelog/v1.34.0) |
| `v1.32.0` | `9-6-2025` | Added support for Affiliates with Affonso, multiple webhook support, enhanced fraud prevention, subscription schedule cancellation, and static payment links. | [Link](/changelog/v1.32.0) |
| `v1.30.0` | `2-6-2025` | Added support for \$0 payments and 100% discount codes for one-time products, enabled payout threshold for USD wallet, and standardized API error codes and messages. | [Link](/changelog/v1.30.0) |
| `v1.27.0` | `26-5-2025` | Introduced Partial Refunds and Multi-Brand Support, enabling merchants to manage multiple brands and websites under a single verified business account. | [Link](/changelog/v1.27.0) |
| `v1.25.0` | `20-5-2025` | Introduced Overlay Checkout Beta, a modern TypeScript library for embedded payments, and a new subscription.plan\_changed webhook event for better subscription lifecycle management. | [Link](/changelog/v1.25.0) |
| `v1.22.0` | `9-5-2025` | Added unified search to the dashboard, added support for upgrading and downgrading subscriptions with addons, and added comprehensive card information to payment responses. | [Link](/changelog/v1.22.0) |
| `v1.21.0` | `3-5-2025` | Added support for addons to subscriptions, made dispute handling more robust, and added customer billing details to payment responses. | [Link](/changelog/v1.21.0) |
| `v1.18.3` | `26-4-2025` | Introduced Subscription Plan Change API, enhanced onboarding guidance, notification preferences center, and improved adaptive currency information. | [Link](/changelog/v1.18.3) |
| `v1.16.1` | `18-4-2025` | Introduced modern verification forms, new Java and Kotlin SDKs, Afterpay/Clearpay integration, and Google Sign-In. | [Link](/changelog/v1.16.1) |
| `v1.14.0` | `12-4-2025` | Introduced new European payment methods, subscription billing address updates, and various improvements. | [Link](/changelog/v1.14.0) |
| `v1.13.0` | `9-4-2025` | Introduced Adaptive Currency Support, MCP Server for AI integration, mobile apps, Custom GPT, and Dodo Dash game. | [Link](/changelog/v1.13.0) |
| `v1.11.0` | `28-3-2025` | Introduced address autofill for checkout page, notifications center, payment link query builder, saved payment methods, and billing currency control. | [Link](/changelog/v1.11.0) |
| `v1.10.1` | `21-3-2025` | Introduced Checkout 2.0, configurable payment methods, dynamic tax calculation, and on-demand subscriptions. | [Link](/changelog/v1.10.1) |
| `v1.7.0` | `14-3-2025` | Introduced a dedicated subscriptions page, enhanced customer details, detailed payout information, an official Ruby SDK, enhanced API filters, and various minor improvements. | [Link](/changelog/v1.7.0) |
| `v1.5.0` | `7-3-2025` | Introduced a custom date filter for analytics, a new tax category, and various improvements. | [Link](/changelog/v1.5.0) |
| `v1.4.0` | `6-3-2025` | Introduced the Storefront Feature and Amazon Pay Integration. | [Link](/changelog/v1.4.0) |
| `v1.3.2` | `28-2-2025` | Introduced the Teams Feature, enhanced success/failure screens, and minor improvements. | [Link](/changelog/v1.3.2) |
| `v1.0.0` | `23-2-2025` | Public release of Dodo Payments with Customer Portal and Customer Portal Session API. | [Link](/changelog/v1.0.0) |
| `v0.24.0` | `15-2-2025` | Introduced discount coupons, support for over 25 local payment methods, customizable statement descriptors, and a complete UI makeover of the login page. | [Link](/changelog/v0.24.0) |
| `v0.22.0` | `6-2-2025` | Introduced subscription's first payment, UPI QR payment method, tax ID on checkout, dashboard restructure, self-serve onboarding guides, multi-language support, invoice PDF makeover, and a centralized help widget. | [Link](/changelog/v0.22.0) |
| `v0.20.1` | `31-1-2025` | Introduced Pay What You Want (PWYW) pricing and expands global payment coverage. | [Link](/changelog/v0.20.1) |
| `v0.19.0` | `24-1-2025` | Added support for UPI and Indian Rupay cards, released PHP SDK, and introduced product archiving. | [Link](/changelog/v0.19.0) |
| `v0.18.0` | `20-1-2025` | Added AI chat support, enhanced APIs, inclusive tax pricing, automated country selection, and more. | [Link](/changelog/v0.18.0) |
| `v0.16.1` | `11-1-2025` | Added Account Summary feature, enhanced API documentation, and improved postal code validation. | [Link](/changelog/v0.16.1) |
| `v0.15.1` | `3-1-2025` | Introducing Invoice API, subscription webhooks, comprehensive guides, Dodo Wall of Love, and social sharing features. Plus, enhanced subscription trial periods and customer identification. | [Link](/changelog/v0.15.1) |
| `v0.14.0` | `26-12-2024` | Introduced the License Key Management feature, enhancing security and control for merchants distributing digital products and services. | [Link](/changelog/v0.14.0) |
| `v0.13.2` | `21-12-2024` | Updated Atlas codebase, SDK updates, and minor improvements. | [Link](/changelog/v0.13.2) |
| `v0.12.0` | `17-12-2024` | Added official SDKs, metadata field, and expanded customer APIs with minor improvements and bug fixes. | [Link](/changelog/v0.12.0) |
| `v0.11.0` | `13-12-2024` | Introduced automated emails, webhook event logs, a license key generation guide, and various minor improvements and bug fixes. | [Link](/changelog/v0.11.0) |
| `v0.10.0` | `8-12-2024` | Open-sourced Atlas demo, feature-request section, webhook request logs API, subscription list API, and various improvements and bug fixes. | [Link](/changelog/v0.10.0) |
| `v0.1.0` | `25-11-2024` | This is the initial release of the Dodo Payments documentation, marking the Beta launch of our product. Explore the features and improvements designed to enhance your payment processing experience. | [Link](/changelog/v0.1.0) |
# v0.1.0 (25 Nov 2024)
Source: https://docs.dodopayments.com/changelog/v0.1.0
This is the initial release of the Dodo Payments documentation, marking the Beta launch of our product. Explore the features and improvements designed to enhance your payment processing experience.
## Smooth Onboarding
* Comprehensive onboarding tracker to monitor merchant progress.
* Support for document resubmission and live-mode activation upon verification.
## Product Catalogue Management
* Easy creation, editing, and deletion of one-time and subscription products.
## Payment Link
* Generate payment links for one-time and subscription products.
* Includes configurable quantities and redirection options for post-payment customer journeys.
## Customizable Checkout Page
* Branded checkout experiences with localized payment options available.
* Real-time tax and total amount calculation displayed on checkout.
## Automated Invoice Generation
* Automatic invoice generation for payments, refunds, and payouts.
## Refund Management
* Seamless partial and full refund management from the dashboard.
* Introduced dispute tracking for effective resolutions.
## Transparent Payouts
* Flexible payout mechanisms with transparent policies and fees.
## Analytics and Reporting
* Advanced analytics dashboard to showcase total revenue, transaction breakdown, customer insights, and top-performing product types.
## API and Webhook Generation
* Easy-to-generate API keys to integrate Dodo Payments into your website or application.
* Webhook setup for real-time notifications about transactions, refunds, and disputes.
# v0.10.0 (8 Dec 2024)
Source: https://docs.dodopayments.com/changelog/v0.10.0
Open-sourced Atlas demo, feature-request section, webhook request logs API, subscription list API, and various improvements and bug fixes.
## New Features
* **Open Sourced Atlas**: A demo application written in Next.js to showcase the integration of Dodo Payments. Explore the [GitHub Repository](https://github.com/dodopayments/dodo-checkout-demo) and documentation [here](/developer-resources/integration-guide).
* **Feature-Request Section**: Introduced a feature-request section on the Dodo Payments dashboard to prioritize the product roadmap based on user feedback.
* **Webhook Request Logs API**: Added APIs for listing and detailing webhook request logs.
* **Static Payment Links**: Added more query parameters to the static payment links to provide more flexibility in the checkout process. For more details, click [here](/developer-resources/integration-guide#1-static-payment-links).
* **Subscription List API**: Added an API to fetch all subscriptions. Refer to the [API Reference](/api-reference/subscriptions/get-subscriptions-1) for details.
## Minor Improvements and Bug Fixes
* Enhanced the Product and Subscription API responses with additional parameters.
* Implemented rate limiting for API requests, currently set to 20 requests per second.
* Improved the documentation to be more user-friendly and easier to navigate.
* Various minor bug fixes and improvements.
# v0.11.0 (13 Dec 2024)
Source: https://docs.dodopayments.com/changelog/v0.11.0
Introduced automated emails, webhook event logs, a license key generation guide, and various minor improvements and bug fixes.
## New Features
* **Automated Emails**: Added automated emails for payments, subscriptions, refunds, and disputes to keep merchants and customers informed.
* **Webhook Event Log**: Added webhook event log on Dodo Payments Dashboard to track webhook events and troubleshoot issues. Navigate to `Settings > Webhooks > Webhook Logs` to access the logs.
* **License Key Generation Guide**: Added a new guide to implement license key generation for digital products. Refer to the [guide](/features/license-keys) for more information.
## Minor Improvements and Bug Fixes
* Various minor fixes and improvements.
# v0.12.0 (17 Dec 2024)
Source: https://docs.dodopayments.com/changelog/v0.12.0
Added official SDKs, metadata field, and expanded customer APIs with minor improvements and bug fixes.
## New Features
* **Official SDKs**: Introduced official Dodo Payments SDKs for **Node.js**, **Python**, and **Go**. Refer to the [SDKs](/developer-resources/dodo-payments-sdks) page for more information.
* **Metadata Field**: Added a `Metadata` field for payments and subscriptions to store additional information. Refer to the [Metadata](/api-reference/metadata) page for more details.
* **Customer APIs**: Expanded customer APIs to include the following endpoints. You can now pass `customer_id` in payments and subscriptions to prevent customer duplication:
* **Create Customer**: [/api-reference/customers/create-customer](/api-reference/customers/create-customer)
* **Patch Customer**: [/api-reference/customers/patch-customer](/api-reference/customers/patch-customer)
## Minor Improvements and Bug Fixes
* Various minor fixes and improvements.
# v0.13.2 (21 Dec 2024)
Source: https://docs.dodopayments.com/changelog/v0.13.2
Updated Atlas codebase, SDK updates, and minor improvements.
## New Features
* **Atlas**: The [GitHub Repository](https://github.com/dodopayments/dodo-checkout-demo) codebase has been updated to use the [Dodo Payments Node SDK](https://github.com/dodopayments/dodopayments-node).
* **SDK Update**: Bumped our SDK versions to `0.13.2` with fixes and updates.
## Minor Improvements and Bug Fixes
* **Dashboard Toggle**: Improved the Test Mode and Live Mode toggle on the dashboard to enhance UX.
* Various minor fixes and improvements.
# v0.14.0 (26 Dec 2024)
Source: https://docs.dodopayments.com/changelog/v0.14.0
Introduced the License Key Management feature, enhancing security and control for merchants distributing digital products and services.
### New Feature: License Key Management
We are excited to introduce the **License Key Management** feature, designed to help merchants secure their digital products and services by generating, managing, and tracking unique license keys. This feature ensures only authorized users access your products and provides merchants with full control over their license key usage and distribution. Ideal for SaaS businesses and digital product vendors, the tool also includes advanced tracking and revocation capabilities for enhanced security.
For more details, please visit the [License Keys Documentation](/features/license-keys).
# v0.15.1 (4 Jan 2025)
Source: https://docs.dodopayments.com/changelog/v0.15.1
Introducing Invoice API, subscription webhooks, comprehensive guides, Dodo Wall of Love, and social sharing features. Plus, enhanced subscription trial periods and customer identification.
## New Features 🚀
### API Enhancements
* Launched Invoice API, enabling merchants and customers to download invoices using `payment_id`. Visit the [Invoice API](/api-reference/payments/get-invoice) documentation for more details.
* Implemented new `subscription.renewed` webhook event for subscription renewal notifications.
### Platform Updates
* Introduced Dodo Payments [Wall of Love](https://dodopayments.com/wall-of-love), showcasing customer testimonials.
* Added social sharing functionality for successful transactions.
* Released Policy Generator tool for automatic generation of privacy policy, terms of service, and refund policy documents. [Try Policy Generator](https://dodopayments.com/tools/policy-generator).
### Documentation Improvements
* Published comprehensive guides:
* [Subscription Integration Guide](/developer-resources/subscription-integration-guide)
* [Webhook Events Guide](/developer-resources/webhooks/intents/webhook-events-guide)
## Improvements and Bug Fixes 🔧
### API Updates
* Added `trial_period_days` parameter to [Create Subscription API](/api-reference/subscriptions/post-subscriptions) to override product-level trial periods.
* Enhanced customer identification logic: now using consistent `customer_id` across payments and subscriptions for the same email.
* Implemented various performance optimizations and fixed minor bugs.
# v0.16.1 (11 Jan 2025)
Source: https://docs.dodopayments.com/changelog/v0.16.1
Added Account Summary feature, enhanced API documentation, and improved postal code validation.
## New Features 🚀
* **Account Summary Feature**: Provides detailed visibility into transaction and payout ledgers.
* **Enhanced API Documentation**: Includes comprehensive parameter descriptions and usage examples.
## Improvements and Bug Fixes 🔧
* **Postal Code Validation**: Updated `Zipcode` field validation to support alphanumeric postal codes.
* **System Optimizations**: Implemented various bug fixes and optimizations to improve overall performance.
# v0.18.0 (20 Jan 2025)
Source: https://docs.dodopayments.com/changelog/v0.18.0
Added AI chat support, enhanced APIs, inclusive tax pricing, automated country selection, and more.
## New Features 🚀
* **AI Chat Support**: Now you can chat with our AI assistant to get help with your technical queries or check the documentation more effectively.
* **Enhanced APIs**: Added APIs to filter payments, refunds, subscriptions, and disputes based on `customer_id`, time period, and status, providing more flexibility in data retrieval.
* **Inclusive Tax Pricing**: Added support to make the product price inclusive of tax while creating/updating the product. The tax amount will be calculated automatically based on the tax rate.
* **Automated Country Selection**: Implemented IP-based country auto-selection in static payment links. The country will be auto-selected based on the user's IP address when they open the static payment links.
* **Query Parameter Removal**: Query parameters are now removed as soon as the static payment link loads, preventing end customers from changing URL parameters and tampering with the static payment link.
## Improvements and Bug Fixes 🔧
* **Minor Enhancements and Fixes**: Various minor enhancements and fixes to improve overall performance and user experience.
# v0.19.0 (24 Jan 2025)
Source: https://docs.dodopayments.com/changelog/v0.19.0
Added support for UPI and Indian Rupay cards, released PHP SDK, and introduced product archiving.
## New Features 🚀
* **UPI and Indian Rupay Card Support**: Added support for UPI and Indian Rupay cards for processing payments in India, currently limited to one-time purchases only. You can now accept payments from customers using UPI and Rupay cards. Additionally, enabled the creation of products with INR pricing on the Dodo Payments Dashboard.
* **PHP SDK Release**: Launched a new PHP SDK for Dodo Payments, simplifying the integration of Dodo Payments into PHP applications.
Explore the PHP SDK for Dodo Payments on GitHub.
* **Product Archiving**: Introduced the ability to archive and unarchive products on the Dodo Payments Dashboard, helping manage clutter and maintain a clean interface.
## Improvements and Bug Fixes 🔧
* **Minor Enhancements and Fixes**: Various minor enhancements and fixes to improve overall performance and user experience.
# v0.20.1 (31 Jan 2025)
Source: https://docs.dodopayments.com/changelog/v0.20.1
Introduced Pay What You Want (PWYW) pricing and expands global payment coverage.
## New Features 🚀
* **Pay What You Want (PWYW) Pricing**: Introduced support for PWYW pricing, allowing users to choose any amount they wish to pay. This feature can be found while creating the product. For more details, please refer to the [documentation](/features/pricing#pay-what-you-want-pricing).
* **Expanded Global Coverage**: Dodo Payments now supports seamless payments from over 150 countries, enhancing global accessibility and convenience for users.
## Improvements and Bug Fixes 🔧
* **Minor Enhancements and Fixes**: Various minor enhancements and fixes to improve overall performance and user experience.
# v0.22.0 (6 Feb 2025)
Source: https://docs.dodopayments.com/changelog/v0.22.0
Introduced subscription's first payment, UPI QR payment method, tax ID on checkout, dashboard restructure, self-serve onboarding guides, multi-language support, invoice PDF makeover, and a centralized help widget.
## New Features 🚀
* **Subscription's First Payment**: The most requested feature is here! Now, there will be a single transaction for the first payment, combining the mandate and the actual subscription amount.
* **UPI QR Payment Method**: Added UPI QR as a payment method for the Indian market, allowing customers to pay using UPI QR codes.
* **Tax ID on Checkout**: Customers can now add their Tax ID on the checkout page for invoicing and tax purposes.
* **Dashboard Restructure**: The entire Dodo Payments Dashboard has been restructured to be more intuitive and user-friendly.
* **Self-Serve Onboarding Guides**: New users can now get started with Dodo Payments using self-serve onboarding guides.
* **Multi-Language Support**: Added support for 14+ languages on the checkout page, allowing customers to pay in their preferred language. Supported languages include:
* English (en)
* العربية (ar) // Arabic
* Català (ca) // Catalan
* 中文 (cn) // Chinese
* Deutsch (de) // German
* Español (es) // Spanish
* Français (fr) // French
* עברית (he) // Hebrew
* Italiano (it) // Italian
* 日本語 (ja) // Japanese
* Nederlands (nl) // Dutch
* Polski (pl) // Polish
* Português (pt) // Portuguese
* Svenska (sv) // Swedish
* **Invoice PDF Makeover**: The invoice PDFs have been given a new, clearer, and more effective design.
* **Centralized Help Widget**: Added a centralized Help widget to streamline and improve support.
## Improvements and Bug Fixes 🔧
* **Rate Limiting**: Added rate limiting in the invoice API to prevent abuse and ensure system stability.
* **Metadata in Static Payment Links**: Added support for metadata in static payment links. For more details, refer to the [documentation](/developer-resources/integration-guide#1-static-payment-links).
* **Minor Enhancements and Fixes**: Various minor enhancements and fixes to improve overall performance and user experience.
# v0.24.0 (15 Feb 2025)
Source: https://docs.dodopayments.com/changelog/v0.24.0
Introduced discount coupons, support for over 25 local payment methods, customizable statement descriptors, and a complete UI makeover of the login page.
## New Features 🚀
* Introduced the functionality to create and add Discount Coupons to your products and services. You can create coupons with percentage discounts, set expiry dates, and more. Go to `Dashboard > Sales > Discounts` to create and manage discount coupons.
* Dodo Payments now supports over 25 local payment methods, including Apple Pay, Google Pay, Klarna, Affirm, Cash App, UPI, and all major credit/debit cards. This helps boost conversions and reduce checkout drop-offs.
* Added functionality to customize the statement descriptor that appears on your customers' bank statements when they make a payment. You can find the option to customize the statement descriptor under `Dashboard > Business > Business Profile`.
* Complete UI makeover of the Dodo Payments login page.
## Improvements and Bug Fixes 🔧
* Security enhancements, performance improvements, and minor fixes.
# v1.0.0 (23 Feb 2025)
Source: https://docs.dodopayments.com/changelog/v1.0.0
Public release of Dodo Payments with Customer Portal and Customer Portal Session API.
This marks the first changelog since Dodo Payments exited Beta and is now publicly available. 🎉
## New Features 🚀
* **Customer Portal**: Introduced the `Customer Portal` feature, allowing customers to manage their subscriptions, invoices, and payment methods. You can create static or dynamic Customer Portal links for your customers via the Dodo Payments Dashboard or through APIs. On the dashboard, navigate to `Sales > Customers > Share Invite`.
* **Customer Portal Session API**: Added the `Customer Portal Session` API to create a session of the Customer Portal for a specific customer. Note that the session is valid for only 24 hours. Check the [API Reference](/api-reference/customers/create-customer-portal-session) for more details.
* **Others**: Minor improvements and bug fixes.
# v1.10.1 (21 March 2025)
Source: https://docs.dodopayments.com/changelog/v1.10.1
Introduced Checkout 2.0, configurable payment methods, dynamic tax calculation, and on-demand subscriptions.
## New Features 🚀
* **Checkout 2.0**: Introducing a completely redesigned checkout experience featuring:
* Streamlined, modern interface for improved user experience
* New Query Collector System that stores parameters in session storage with unique IDs (e.g., `sess_1a2b3c4d`)
* Persistent data availability throughout the entire checkout flow
* More enhancements planned for future releases!
* **Configurable Payment Methods**: New `allowed_payment_methods` configuration enables merchants to customize available payment options during checkout. See the [Allowed Payment Methods](/features/payment-methods#configuring-payment-methods) documentation for implementation details.
* **Dynamic Tax Calculation**: Real-time calculation of tax rates based on customer billing addresses for more accurate pricing on the static payment links.
* **On-Demand Subscriptions**: New API endpoints for flexible subscription billing (available for select merchants). Learn more in the [On-Demand Subscriptions](/features/subscription#enabling-on-demand-subscriptions) guide.
* **Additional Updates**:
* Performance optimizations
* Bug fixes
* General system improvements
# v1.11.0 (28 March 2025)
Source: https://docs.dodopayments.com/changelog/v1.11.0
Introduced address autofill for checkout page, notifications center, payment link query builder, saved payment methods, and billing currency control.
## New Features 🚀
* **Address Autofill**: Streamlined checkout process with smart address field auto-completion based on user input.
* **Notifications Center**: Stay informed with a new dedicated notifications tab for important system events and updates.
* **Payment Link Query Builder**: Create and customize payment links more efficiently with our new visual query builder.
* **Saved Payment Methods**: Enable display of previously used payment methods during checkout by setting `show_saved_payment_methods: true` in payment link configuration.
* **Billing Currency Control**: Enforce specific billing currencies for payment links by setting the `billing_currency` parameter.
* **Improvements**: Various improvements and bug fixes.
# v1.13.0 (9th April 2025)
Source: https://docs.dodopayments.com/changelog/v1.13.0
New features including Adaptive Currency Support, MCP Server for AI integration, mobile apps, Custom GPT, and Dodo Dash game
## New Features 🚀
* **[Adaptive Currency Support](/features/adaptive-currency)** 💱
* Automatically display prices in customers' local currencies based on their location.
* Merchants receive payments in their preferred currency, while customers pay in their local currency.
* Transparent conversion fee structure ranging from 2% to 4%, depending on the order value.
* Support for over 25 new currencies has been added:
* **Middle East**: AED (UAE Dirham), SAR (Saudi Riyal)
* **Asia Pacific**: AUD (Australian Dollar), CNY (Chinese Yuan), HKD (Hong Kong Dollar), IDR (Indonesian Rupiah), KRW (South Korean Won), SGD (Singapore Dollar), TWD (New Taiwan Dollar)
* **Europe**: ALL (Albanian Lek), CHF (Swiss Franc), CZK (Czech Koruna), DKK (Danish Krone), EUR (Euro), GBP (British Pound), HUF (Hungarian Forint), NOK (Norwegian Krone), PLN (Polish Zloty), RON (Romanian Leu), RSD (Serbian Dinar), SEK (Swedish Krona), TRY (Turkish Lira)
* **Americas**: BRL (Brazilian Real), CAD (Canadian Dollar), MXN (Mexican Peso)
* **[Dodo Payments MCP Server](/developer-resources/mcp-server)** 🤖
* Introducing the new Model Context Protocol (MCP) Server for seamless AI model integration.
* Provides a standardized interface for interacting with the DodoPayments API.
* Simple installation via `npx` or through MCP client integration.
* Offers a comprehensive tool suite for managing payments, subscriptions, and more.
* Includes advanced features such as tool filtering and enhanced security controls.
* [Mobile App](/features/mobile-app): Now available for iOS and Android, bringing the complete dashboard experience to your mobile device. Key features include:
* Real-time notifications for transactions, payouts, and subscription renewals
* Full dashboard access with revenue tracking and performance graphs
* Complete subscription and product management capabilities
* Business operations including test/live mode switching and verification management
* Developer tools with API key management and webhook monitoring
Download now on the [App Store](https://apps.apple.com/in/app/dodo-payments/id6743651329) or [Google Play](https://play.google.com/store/apps/details?id=com.dodopayments.app).
* **Custom GPT for Dodo Payments**: Available on the ChatGPT Marketplace, trained on our documentation to assist with technical questions or issues. Access it here, [https://dodo.pe/custom-gpt](https://dodo.pe/custom-gpt)
* **Dodo Dash**: Experience the thrill of our endless runner game, Dodo Dash! Guide a daring dodo bird as it sprints, jumps, and dodges obstacles to survive. Give it a try here, [https://dash.dodopayments.com](https://dash.dodopayments.com).
* Various other bug fixes and improvements have been implemented.
# v1.14.0 (12th April 2025)
Source: https://docs.dodopayments.com/changelog/v1.14.0
Introduced new European payment methods, subscription billing address updates, and various improvements.
## New Features 🚀
* **New European Payment Methods**: We've expanded our payment options with four new European payment methods:
* **iDEAL** - Netherlands
* **Bancontact** - Belgium
* **Przelewy24 (P24)** - Poland
* **EPS** - Austria
These payment methods are currently limited to:
* Euro (EUR) currency only
* One-time payments only (not available for subscriptions)
For detailed information about these payment methods, visit our [Payment Methods](/features/payment-methods) documentation.
* **Subscription Billing Address Updates**: Added the ability to update billing addresses for subscriptions through:
* Customer portal interface
* Patch subscription API endpoint
# v1.16.1 (18th April 2025)
Source: https://docs.dodopayments.com/changelog/v1.16.1
Introduced modern verification forms, new Java and Kotlin SDKs, Afterpay/Clearpay integration, and Google Sign-In.
## New Features 🚀
* **Modern Verification Forms**: Redesigned the verification forms with a modern, user-friendly interface featuring clearer instructions, improved visual hierarchy, and intuitive form fields for a smoother verification process.
* **New Java and Kotlin SDKs**: Released native SDKs for Java and Kotlin to support Android development and enterprise Java applications. The SDKs include comprehensive documentation, sample code, and full support for Dodo Payments features including payments, subscriptions, and refunds. Available on GitHub: [Java SDK](https://github.com/dodopayments/dodopayments-java) and [Kotlin SDK](https://github.com/dodopayments/dodopayments-kotlin).
* **Afterpay/Clearpay Integration**: Added support for Afterpay/Clearpay as a Buy Now, Pay Later (BNPL) payment method, enabling customers to split payments into 4 interest-free installments.
* **Google Sign-In**: Implemented Google Sign-In as a new authentication option, allowing users to log in using their Google accounts. This OAuth 2.0-compliant integration supports both personal and workspace Google accounts for enhanced security and convenience.
# v1.18.3 (26th April 2025)
Source: https://docs.dodopayments.com/changelog/v1.18.3
Introduced Subscription Plan Change API, enhanced onboarding guidance, notification preferences center, and improved adaptive currency information.
## New Features 🚀
* **Subscription Plan Change API**: Launched a new API that enables seamless upgrades or downgrades of subscriptions between different product plans. For implementation details, see the [API reference](/api-reference/subscriptions/change-plan).
* **Enhanced Onboarding Guidance**: Improved sidebar nudges to provide clearer direction on next steps in your Dodo Payments onboarding journey.
* **Notification Preferences Center**: Added a dedicated section for managing your notification settings. Access it by clicking the bell icon in the top right corner and then selecting the settings icon.
* **Enhanced Adaptive Currency Information**: Added new fields to the payment response including `settlement_amount`, `settlement_currency`, and `settlement_tax` to provide greater transparency on settlement details.
* **General Improvements**: Various minor enhancements and bug fixes to improve overall platform performance.
# v1.21.0 (3rd May 2025)
Source: https://docs.dodopayments.com/changelog/v1.21.0
Added support for addons to subscriptions, made dispute handling more robust, and added customer billing details to payment responses.
## New Features 🚀
* **Subscription Addons**: Introduced support for addons to subscription products. In the Products section, you'll find a new Addons tab where you can create addons such as "Seats". Once created, you can edit your subscription products to include these addons. This feature is available both on the dashboard and via API.
* [Create Addon](/api-reference/addons/create-addon)
* [List Addons](/api-reference/addons/list-addons)
* [Get Addon](/api-reference/addons/get-addon)
* [Update Addon](/api-reference/addons/update-addon)
* [Update Addon Images](/api-reference/addons/update-addon-images)
With addons, you unlock new billing options such as seat-based billing or variable billing per customer. Get creative with addons to design flexible billing models for your business.
* **Enhanced Dispute Management**: Improved dispute handling to be more robust and easier to manage through your Dodo Payments dashboard.
* **Expanded Response Data**: Added customer billing details to payment responses, subscription responses, and webhooks.
* **General Improvements**: Various minor enhancements and bug fixes to improve overall platform performance.
# v1.22.0 (9th May 2025)
Source: https://docs.dodopayments.com/changelog/v1.22.0
Added unified search to the dashboard, added support for upgrading and downgrading subscriptions with addons, and added comprehensive card information to payment responses.
## New Features 🚀
* **Unified Dashboard Search**: We've implemented a powerful search functionality across the entire dashboard. You can now quickly find:
* **Identifiers**: Payment, Refund, Dispute, Customer, Discount, Product, and Addon IDs
* **Names**: Products, Addons, and Customers
* **Contact Information**: Customer Email addresses
* **Metadata**: Payment and Subscription metadata
* **Navigation**: All pages within the Dodo Payments dashboard
* **Addons Plan Changes**: Added support for upgrading and downgrading subscriptions that include addons.
* **Expanded Payment Card Details**: Payment responses now include comprehensive card information:
* `card_issuing_country`: Country where the card was issued
* `card_last_four`: Last 4 digits of the card number
* `card_brand`: Card brand (Visa, Mastercard, etc.)
* `card_type`: Card type (credit, debit, prepaid)
* **Platform Enhancements**: Various performance optimizations, bug fixes, and minor improvements across the platform.
# v1.25.0 (20th May 2025)
Source: https://docs.dodopayments.com/changelog/v1.25.0
Introduced Overlay Checkout Beta, a modern TypeScript library for embedded payments, and a new subscription.plan_changed webhook event for better subscription lifecycle management.
## New Features 🚀
* **Overlay Checkout Beta**: We're excited to announce the beta release of our new overlay checkout experience. This modern TypeScript library provides a seamless way to integrate our payment overlay into your web application with real-time event handling and customizable themes. Key features include:
* **Real-time Event Handling**: Listen to checkout events like payment creation, success, and failure
* **TypeScript Support**: Fully typed APIs for better developer experience
* **Customizable Themes**: Support for light and dark themes
* **Browser Compatibility**: Works across all modern browsers including Chrome, Firefox, Safari, Edge, and IE11+
* **PCI Compliance**: Maintains security standards while providing an embedded experience
Try it out:
* Live Demo: [https://atlas.dodopayments.com](https://atlas.dodopayments.com#overlay-checkout)
* Example Code: [https://github.com/dodopayments/dodo-checkout-demo](https://github.com/dodopayments/dodo-checkout-demo)
* Integration Guide: [Overlay Checkout Guide](/developer-resources/overlay-checkout)
* **New Subscription Event**: Added support for the `subscription.plan_changed` webhook event that fires whenever a subscription is upgraded, downgraded, or modified. This event provides:
* Complete subscription details after the change
* Previous and new plan information
* Prorated billing adjustments
* Updated next billing date
This event complements our existing subscription events (`subscription.active`, `subscription.on_hold`, etc.) to provide comprehensive visibility into the subscription lifecycle.
* **Platform Enhancements**: Various performance optimizations, bug fixes, and minor improvements across the platform.
# v1.27.0 (26th May 2025)
Source: https://docs.dodopayments.com/changelog/v1.27.0
Introduced Partial Refunds and Multi-Brand Support, enabling merchants to manage multiple brands and websites under a single verified business account.
## New Features 🚀
* **Partial Refunds**: We're excited to introduce our new Partial Refunds feature, now available through both our API and Dashboard. This powerful capability allows merchants to process precise refunds for specific components of a payment, whether it's a seat add-on or a portion of a subscription. The feature includes built-in validation to prevent errors and automatically generates refund receipts for your records.
* **Multi-Brand Support**: We're launching Multi-Brand Support, enabling you to manage multiple brands and websites under a single verified business account. This feature is ideal for launching new product lines, creating regional microsites, or separating different offerings while maintaining a streamlined operation.
Key features include:
* **Rapid Verification**: Most brands are approved within 24 working hours through our streamlined website review process
* **Branded Checkout Experience**: Customers see the specific brand's logo, name, and descriptor during checkout
* **Seamless Brand Creation**: Create additional brands through Business Settings ▸ Brands ▸ Add Brand
* **Detailed Transaction Analytics**: Each transaction clearly indicates its associated brand for better business insights
* **Simplified Payouts**: All revenue consolidates into your existing account without additional verification requirements
To create additional brands, navigate to Business `Settings ▸ Brands ▸ Add Brand` and complete the required details.
* **Platform Enhancements**: Various performance optimizations, bug fixes, and minor improvements across the platform.
# v1.3.2 (28 Feb 2025)
Source: https://docs.dodopayments.com/changelog/v1.3.2
Introduced the Teams Feature, enhanced success/failure screens, and minor improvements.
## New Features 🚀
* **Teams Feature**: We have introduced the highly requested Teams Feature. Now, you can invite `Editors` and `Viewers` to your Business and collaborate with them seamlessly. To try it out, navigate to the `Business > Teams` section in the Dashboard.
* **Enhanced Success/Failure Screens**: The default success and failure screens have been updated for cases where no `redirect_url` is provided. These screens now offer greater visibility to your customers and include an option to download the invoice.
* **Others**: Minor improvements and bug fixes.
# v1.30.0 (2nd June 2025)
Source: https://docs.dodopayments.com/changelog/v1.30.0
Added support for $0 payments and 100% discount codes for one-time products, enabling free product offerings, payout threshold for USD wallet, and standardized API error codes and messages.
## New Features 🚀
* **Zero-Dollar Payments**: Added support for \$0 payments and 100% discount codes for one-time products, enabling free product offerings and promotional campaigns.
* **Custom Payout Threshold**: Added the ability to set minimum payout amounts for USD wallets, helping merchants optimize payout processing and reduce fees for small transactions. Access this feature at `Payouts ▸ USD Wallet ▸ Edit Payout Threshold`.
* **API Standardization**: Implemented standardized API error codes and messages as part of our ongoing API improvement initiative. This enhancement makes the API more consistent and developer-friendly. For detailed information, visit our [Error Codes Documentation](/api-reference/error-codes).
* **Transaction Decline Standardization**: Introduced unified transaction decline codes and messages, providing clearer insights into failure reasons and distinguishing between user errors and system issues. Learn more in our [Transaction Failures Documentation](/api-reference/transaction-failures).
# v1.32.0 (9th June 2025)
Source: https://docs.dodopayments.com/changelog/v1.32.0
Added support for Affiliates with Affonso, multiple webhook support, enhanced fraud prevention, subscription schedule cancellation, and static payment links.
## New Features 🚀
* **Affiliate Program Integration**: Launched integration with Affonso, enabling you to create and manage affiliate programs directly through Dodo Payments. Track referrals, handle commissions, and grow your revenue with trusted affiliate partnerships. Learn more in our [Affiliates](/features/affiliates) documentation.
* **Multiple Webhook Support**: Enhanced webhook capabilities to support multiple endpoints, allowing you to send event notifications to different systems simultaneously.
* **Enhanced Fraud Prevention**: Added a new business setting to force 3D Secure authentication for all transactions, helping reduce fraud and chargebacks.
* **Subscription Schedule Cancellation**: Introduced the ability to schedule subscription cancellations to take effect at the end of the billing period, available through our API.
* **Static Payment Links**: Added two more query params for static payment links:
* `paymentCurrency`: Specifies the payment currency. Default is set as per the billing country.
* `showCurrencySelector`: Shows the currency selector. Default is true.
# v1.34.0 (18th June 2025)
Source: https://docs.dodopayments.com/changelog/v1.34.0
Added Digital Product Delivery, React Native SDK beta release, new proration modes, adaptive currency for subscriptions, and enhanced verification form management.
## New Features 🚀
* **Digital Product Delivery**: Streamlined digital product delivery system with automated fulfillment, ensuring customers receive their digital goods immediately after successful payment. Learn more in our [Digital Product Delivery](/features/digital-product-delivery) documentation.
* **React Native SDK (Beta)**: Released our React Native SDK in beta, enabling secure payment processing in native Android and iOS apps with customizable UI components and screens. Learn more in our [React Native SDK](/developer-resources/react-native-integration) documentation.
* **New Proration Mode**: Introduced `full_immediately` proration mode, allowing immediate full charges when subscription changes occur, providing more flexible billing options for your business needs.
* **Adaptive Currency for Subscriptions**: Enhanced subscription management with adaptive currency support, automatically adjusting pricing based on customer location and local currency preferences.
* **Instantaneous On-Demand Processing**: Improved processing speed for on-demand charges making it almost instant, reducing transaction times and providing faster payment confirmations.
* **Verification Form Management**: Added comprehensive verification form data viewing capabilities in the Dodo Payments Dashboard verification section, allowing you to review and manage all submitted verification documents in one centralized location.
* **Subscription Cancellation Enhancement**: Implemented subscription cancellation at the end of billing period functionality, now available in both the dashboard and customer portal for improved user experience and billing transparency.
* **General Improvements**: Various minor fixes and performance improvements to enhance overall platform stability and user experience.
# v1.34.4 (26th June 2025)
Source: https://docs.dodopayments.com/changelog/v1.34.4
Enhanced Dashboard Analytics with comprehensive new metrics across three key dashboard sections: Revenue Analytics, Customer Analytics, and Payment Success Rate Analytics.
## New Features 🚀
* **Enhanced Dashboard Analytics**: Completely redesigned the home page analytics with comprehensive new metrics across three key dashboard sections:
* **Revenue Analytics**
* Total Revenue overview
* Daily Revenue tracking
* Payment count metrics
* Refund count tracking
* Payout Received status
* Top 5 revenue-generating countries
* Revenue growth rate analysis
* **Customer Analytics**
* Average Revenue Per User (ARPU)
* Active Customers count
* Customer segmentation (new vs existing)
* Top 6 customers for current month
* **Payment Success Rate Analytics**
* Payment Success Rate metrics
* Refund Success Rate tracking
* Failure Reasons breakdown
* Payments status distribution
* Monthly Success Rate trends
# v1.37.0 (2nd July 2025)
Source: https://docs.dodopayments.com/changelog/v1.37.0
Enhanced Analytics Dashboard with comprehensive revenue and retention insights, Reports 2.0 with detailed business reporting capabilities, and improved subscription management features for better business intelligence and operational efficiency.
## New Features 🚀
### Enhanced Analytics Dashboard
We've expanded the analytics dashboard with comprehensive revenue and retention insights to help you better understand your business performance.
#### Revenue Breakdown Analytics
Track your recurring revenue metrics with detailed breakdowns:
* **Projected ARR**: Annual Recurring Revenue projections with trend analysis
* **MRR Breakdown and Growth Rate**: Monthly Recurring Revenue analysis with month-over-month growth metrics
* **New MRR Chart**: Visual representation of new customer revenue acquisition
* **Expansion MRR Chart**: Tracking revenue growth from existing customer upgrades and expansions
* **Churn MRR Chart**: Monitoring revenue lost from customer cancellations and downgrades
* **Net New MRR Chart**: Net revenue impact combining new, expansion, and churn MRR
#### Retention Analytics
Monitor customer retention and churn patterns:
* **Customer Churn Rate**: Percentage of customers who cancel their subscriptions
* **Revenue Churn Rate**: Percentage of revenue lost from cancellations and downgrades
* **Churn Rate Trends**: Historical tracking of both customer and revenue churn patterns over time
* **User Retention Matrix**: Cohort analysis showing customer retention rates across different time periods
### Reports 2.0
We've completely revamped the reports section with more comprehensive and detailed reporting capabilities. Navigate to **Business > Reports** to access the new reporting suite.
Available reports include:
* **Payment Report**: Detailed record of all processed payments with transaction status tracking
* **Refund Report**: Comprehensive log of all refund activities with detailed refund information
* **Payout Report**: Summary of payouts made to your account
* **Product-wise Sales Report**: Sales performance insights with revenue and business growth metrics
* **Payment Failure Report**: Summary of failed payment attempts with failure reasons
* **Customer Report**: Payment summary organized by customer
* **Disputes Report**: Payment disputes summary with resolution status
* **Account Summary Report**: Complete account overview and summary
### Enhanced Payout Reporting
We've added detailed payout reports that provide clear visibility into fees and transaction details for each payout you receive from Dodo Payments. Access these enhanced reports in **Business > Payouts**.
### Dodo Payments Remote MCP Server
You can now use the hosted Remote MCP Server at [https://mcp.dodopayments.com](https://mcp.dodopayments.com) for instant MCP integration—no local setup required.
This is the fastest way to get started with Dodo Payments' MCP integration. No server installation or maintenance needed.
Visit [https://mcp.dodopayments.com](https://mcp.dodopayments.com) in your browser.
On the site, copy the MCP JSON configuration provided for your client.
Paste the configuration into your application's MCP settings (for example, Cursor or Claude).
For Cursor, your configuration should look like this:
```json theme={null}
{
"mcpServers": {
"dodopayments_api": {
"command": "npx",
"args": ["-y", "mcp-remote@latest", "https://mcp.dodopayments.com/sse"]
}
}
}
```
After adding the JSON configuration, a pop-up will prompt you to:
* Enter your Dodo Payments API key
* Select the environment (test or live)
* Select your MCP Client under Configuration Options
* Click the Login and Approve button
Once setup is complete, you'll see a success screen.
You're now ready to use the Dodo Payments MCP server!
### Additional Improvements
* Various minor improvements and bug fixes for enhanced stability and performance
# v1.38.0 (9th July 2025)
Source: https://docs.dodopayments.com/changelog/v1.38.0
Analytics Chart Sharing, Multiple Partial Refunds, Cumulative Revenue Analytics, Context7 Integration, Next.js Adapter
## New Features 🚀
### Analytics Chart Sharing
Share your analytics charts as images with customizable branding options. Click the share button on any chart to generate an image with multiple color variants that match your brand identity.
### Multiple Partial Refunds
Process multiple partial refunds for a single payment through both the API and Dodo Payments Dashboard, providing greater flexibility in handling customer refund requests.
### Cumulative Revenue Analytics
Track your business growth with new cumulative revenue charts in the analytics dashboard, offering comprehensive insights into your revenue trends over time.
### Next.js Adapter
Easily integrate Dodo Payments into your Next.js application with our official adapter. Set up payment processing in just a few lines of code. You can also use our ready-made LLM prompt with AI coding agents (such as Cursor, Windsurf, Lovable, and others) to automate the integration process.
[View the @dodopayments/nextjs package on npm](https://www.npmjs.com/package/@dodopayments/nextjs)
### Context7 Integration
Access Dodo Payments documentation through Context7 MCP, enabling AI coding agents to leverage comprehensive payment processing context. For detailed information, visit the [Context7 documentation](https://context7.com/llmstxt/dodopayments-llms-full.txt).
# v1.4.0 (6 Mar 2025)
Source: https://docs.dodopayments.com/changelog/v1.4.0
Introduced the Storefront Feature and Amazon Pay Integration.
## New Features 🚀
* **Storefront Feature**: Create a simple, branded, and mobile-friendly online store to showcase both One-Time Payment products and Subscription products. This feature eliminates the complexity of building an entire website, enabling you to quickly offer your products or services online without additional web development efforts.
* **Amazon Pay Integration**: Added Amazon Pay as a payment method for One-Time Payments.
* **Improvements and Fixes**: Minor improvements and bug fixes.
# v1.43.0 (24th July 2025)
Source: https://docs.dodopayments.com/changelog/v1.43.0
Comprehensive Webhooks Upgrade: endpoint management, security, delivery logs, debugging, and monitoring—now fully self-serve in the dashboard.
## New Features 🚀
### Major Webhooks Upgrade
Gain complete control, visibility, and security with our fully reimagined webhooks system. Explore the new capabilities below.
* Create, edit, pause/resume, and delete webhook endpoints (URLs)
* Select event types for each endpoint
* Configure custom headers per endpoint
* Set rate limits for each endpoint
* View and rotate signing secrets (HMAC keys) per endpoint
* Regenerate secrets and view last rotation timestamps
* Copy code snippets or documentation for signature verification
* View message logs for each endpoint (status, timestamps)
* Inspect individual message attempts with request/response bodies and headers
* Replay (re-send) failed or past messages
* Filter logs by event type, status code, or time range
* Send test events from the portal using mock payloads
* Inspect payload schemas and view expected fields
* Track delivery stats: success/failure counts, latency, and recent error trends
* Monitor endpoint health status (e.g., failing, healthy)
You can now manage, debug, and secure your webhooks with greater flexibility—all from the dashboard.
# v1.44.0 (3rd August 2025)
Source: https://docs.dodopayments.com/changelog/v1.44.0
Adaptive Currency, Turkish Language Support, and New Proration Mode
## New Features 🚀
* The Adaptive Currency feature now supports additional currencies, further enhancing localized payment experiences across more regions. Newly supported currencies include:
* MVR (Maldivian Rufiyaa)
* MUR (Mauritian Rupee)
* ZAR (South African Rand)
* PHP (Philippine Peso)
* FJD (Fijian Dollar)
* QAR (Qatari Riyal)
* MYR (Malaysian Ringgit)
For comprehensive details on Adaptive Currency functionality and its benefits, please visit the [Adaptive Currency documentation](/features/adaptive-currency).
* Turkish language support has been added to the Dodo Payments Checkout Page.
* A new proration mode, `difference_immediately`, has been introduced. This mode charges the difference immediately, irrespective of the remaining time in the billing cycle when changing plans via the API. [Documentation](/developer-resources/subscription-integration-guide).
# v1.47.0 (11th August 2025)
Source: https://docs.dodopayments.com/changelog/v1.47.0
Dashboard UI overhaul, discount codes for subscriptions, expanded Adaptive Currency, webhook management API, and product metadata support.
## New Features 🚀
* **Major Dashboard Enhancements**
* **Sidebar Navigation:** Now collapsible, allowing you to minimize distractions and focus on your workflow.
* **Dashboard Search:** Completely redesigned for faster, more relevant results with full keyboard navigation support.
* **Tables:** Gain more control with the ability to hide or rearrange columns, pin columns left or right, and adjust rows per page for a personalized view.
* **Transaction Details Page:** Improved layout displays more payment information, including a detailed payment breakdown for full transparency.
* **UI & Style Updates:** Refined colors, typography, and spacing deliver a cleaner, more consistent user experience.
* **Discount Codes for Subscriptions**\
You can now apply discount codes to subscriptions, specifying the number of billing cycles the discount should apply. This gives you greater flexibility and control over your pricing strategies.
* **Adaptive Currency Expansion**\
The Adaptive Currency feature now supports 50+ additional currencies, enabling even more localized payment experiences. See the [Adaptive Currency documentation](/features/adaptive-currency) for the complete list of supported currencies.
* **Webhook Management API**\
Introduced webhook CRUD APIs, allowing you to programmatically create, update, and manage your webhooks.
* **Product Metadata Support**\
You can now add metadata to your products, making it easy to store and retrieve additional information for each product.
# v1.5.0 (7 March 2025)
Source: https://docs.dodopayments.com/changelog/v1.5.0
Introduced a custom date filter for analytics, a new tax category, and various improvements.
## Minor Features 🚀
* Added a custom date filter on the home page analytics to view data for a specific date range.
* Introduced `EdTech` as a tax category for products.
* Various minor improvements and bug fixes.
# v1.51.0 (23rd August 2025)
Source: https://docs.dodopayments.com/changelog/v1.51.0
Powerful Checkout Sessions API, Metadata in Payments Export, and Subscription Next Billing Date Update
## New Features 🚀
### Checkout Sessions API
Introducing our new **Checkout Sessions API** - a powerful, hosted checkout solution that provides a complete payment flow for both one-time purchases and subscriptions. This new API replaces the previous Dynamic Links approach with enhanced flexibility and improved user experience.
**Key Benefits:**
* **Simplified Integration**: No need to provide complete billing addresses upfront - we handle missing details automatically
* **Enhanced Customization**: Full control over payment methods, currencies, themes, and feature flags
* **Better UX**: Pre-fill customer information to reduce checkout friction and improve conversion rates
* **Flexible Configuration**: Support for multi-product carts, subscription trials, discount codes, and more
**What's Changed:**
* **Before**: Required complete billing address information when creating payment links
* **Now**: Pass whatever customer information you have available - we'll collect the rest during checkout
* **Migration**: Simple API endpoint change with no additional handling required
Complete guide with code examples in Node.js, Python, and REST API
Full API documentation with live testing
### Minor Improvements
* Payments export reports now include metadata for enhanced data analysis.
* You can now update the next billing date for subscriptions directly via the API.
# v1.52.4 (3rd September 2025)
Source: https://docs.dodopayments.com/changelog/v1.52.4
Usage-Based Billing, public Refund Receipt API, 100% subscription discounts, and checkout session ID in payments
## New Features 🚀
### Usage-Based Billing
Bill customers precisely based on actual consumption. Define meters, ingest events, and convert usage into charges automatically.
* **Flexible aggregation**: count, sum (over property), unique, max, last
* **Event filtering**: AND/OR logic with rich comparators to include only relevant events
* **Product linkage**: attach meters to products with price-per-unit and optional free thresholds
* **Analytics**: track trends, event streams, and per-customer consumption
Learn the concepts, components, and the end-to-end data flow.
Step-by-step setup for meters, products, and event ingestion.
Usage-Based Billing is currently in beta.
### 100% Discounts for Subscriptions
Apply a 100% discount for any number of billing cycles to support complimentary periods, sponsorships, or promotional campaigns. Invoices reflect the full discount and proration follows your plan settings.
### Minor Improvements
* Refund Receipt API is now publicly accessible. See the docs: [/api-reference/refunds/get-refund-receipt](/api-reference/refunds/get-refund-receipt).
* Added `checkout session ID` to the Get Payment API response and payment webhooks to help correlate payments with their originating checkout session.
# v1.53.2 (13th September 2025)
Source: https://docs.dodopayments.com/changelog/v1.53.2
Customer credits, enhanced refund details, higher meter limits, license activation context, and $0 downgrades
## New Features 🚀
### Customer Credits
Credits are now **automatically applied to recurring subscription invoices**, making it easy to:
* Reward loyal customers
* Issue service credits
* Handle manual adjustments without disrupting billing cycles
Use customer credits for:
* Promotional incentives
* Compensation for service interruptions
* Pre-paid account balances
You can **add or remove credits from a customer's wallet using the API**, enabling automated workflows for customer support, retention, or loyalty programs.
**Example use cases:**
* Credit a customer's wallet after a successful referral
* Debit credits as part of a custom upgrade path
Explore the new and updated customer wallet APIs:
* [Get Customer Wallets](/api-reference/customers/get-customer-wallets)
* [List Customer Wallet Ledger Entries](/api-reference/customers/get-customer-wallets-ledger-entries)
* [Create Customer Wallet Ledger Entry for Adding and Removing Credits](/api-reference/customers/post-customer-wallets-ledger-entries)
Credit application during Checkout will be available in the next release, allowing customers to redeem their wallet balance directly at the point of purchase.
## Improvements & Updates ✨
* **Customer details in Get Refund API**\
The refund retrieval endpoint now returns associated customer information, streamlining reconciliation and support workflows.\
Learn more: [Get Refund API](/api-reference/refunds/get-refunds-1)
* **Increased meter limit to 50**\
You can now create up to 50 meters per account, enabling more granular usage tracking across your products and features.\
Learn more: [Get Meters API](/api-reference/meters/get-meters)
* **Product and customer context in Activate License Key API**\
License activation responses now include detailed product and customer context, improving entitlement checks and downstream integrations.\
Learn more: [Activate License Key API](/api-reference/licenses/activate-license)
* **Subscriptions can be downgraded to \$0**\
You can now downgrade subscriptions to free or \$0 plans, making it easier to support trials, community tiers, and sponsorships.\
Manage subscriptions: [Update Subscription API](/api-reference/subscriptions/patch-subscriptions)
# v1.56.0 (27 October 2025)
Source: https://docs.dodopayments.com/changelog/v1.56.0
Streamlined onboarding, C# SDK launch, webhook verification, Two-Factor Authentication, overlay checkout sessions, and usage-based subscription plan changes
## New Features 🚀
1. **New Onboarding Experience**\
Onboarding is now streamlined so you can get started faster and begin accepting payments without unnecessary delays.
* Faster account activation
* Simplified verification process
2. **Two-Factor Authentication (2FA)**\
Enhanced account security with two-factor authentication support. Protect your merchant account and team members with an additional layer of security beyond passwords.
* Secure your account against unauthorized access
* Support for authenticator apps
* Required for sensitive operations
3. **Session Links in Overlay Checkout**\
Checkout sessions now integrate seamlessly with overlay checkout, providing a smoother embedded payment experience. Create checkout sessions and display them in an overlay without redirecting customers away from your application.
4. **C# SDK**\
The official Dodo Payments C# SDK is now available, bringing native .NET support for integrating payment functionality into your C# and .NET applications.
* Full type safety with C# interfaces
* Async/await support for all API operations
* Available on NuGet for easy installation
Learn more: [Dodo Payments SDKs](/developer-resources/dodo-payments-sdks)
5. **Webhook Verification in All SDKs**\
All SDKs now include built-in webhook verification functions to securely validate incoming webhook events. Powered by the industry-standard [webhooks library](https://github.com/standard-webhooks), these functions make it simple to verify and process webhook payloads.
Two methods are available:
* `unwrap()`: Verifies webhook signatures using your webhook secret key for production use
* `unsafe_unwrap()`: Parses webhook payloads without verification for local testing and development
To use webhook verification, provide your `DODO_PAYMENTS_WEBHOOK_KEY` when initializing the Dodo Payments client. Both functions automatically parse the webhook payload and extract the event type, making webhook handling more secure and convenient.
***
## Improvements & Updates ✨
* **Update customer name in subscription billing details**\
Customer names can now be modified directly in subscription billing details, making it easier to correct information or update records.
* **Get Checkout Session API**\
Retrieve checkout session details programmatically to check status, validate sessions, or integrate with your analytics workflows.
* **Changing plan support for usage-based subscriptions**\
Subscription plan changes are now fully supported for usage-based billing models, enabling seamless upgrades and downgrades while preserving usage tracking.
* **Tax ID in subscription responses**\
Tax identification numbers are now included in subscription API responses, improving compliance reporting and invoice generation.
* **Force 3DS flags in APIs**\
Enhanced 3D Secure control with force 3DS flags now available in checkout session, payment, and subscription APIs for stronger authentication requirements.
* **Filter balance ledger by currency**\
Dashboard now supports filtering balance ledger entries by currency, simplifying reconciliation for multi-currency operations.
* **Reduced payout threshold to \$50**\
Merchants can now set payout thresholds as low as \$50, providing more flexibility for smaller businesses and faster access to funds.
* **Minor improvements and bug fixes**\
General enhancements and stability improvements across the platform.
# v1.7.0 (14 March 2025)
Source: https://docs.dodopayments.com/changelog/v1.7.0
Introduced a dedicated subscriptions page, enhanced customer details, detailed payout information, an official Ruby SDK, enhanced API filters, and various minor improvements.
## New Features 🚀
* **Dedicated Subscriptions Page**: Manage customer subscriptions easily with the new dedicated page. Navigate to `Sales > Subscriptions` on the dashboard.
* **Enhanced Customer Details**: View subscriptions, payments, and refunds for each customer in one place. Go to `Sales > Customers > arrow icon` on the dashboard.
* **Detailed Payout Information**: Get more insights on payout amounts, cycles, and dates. Visit `Business > Payouts` on the dashboard.
* **Official Ruby SDK**: Integrate Dodo Payments into your Ruby applications effortlessly. Check out the [Ruby SDK](https://github.com/dodopayments/dodopayments-ruby) for more details.
* **Enhanced API Filters**: Added filters like `customer_id`, `status`, etc., to relevant list APIs. Refer to the API documentation for more information.
* **Minor Improvements and Bug Fixes**: Various other minor improvements and bug fixes have been implemented.
# Community projects
Source: https://docs.dodopayments.com/community/overview
Discover community-built SDKs, plugins, and boilerplates for Dodo Payments.
This is a curated list of open-source projects created by the community. These projects are not affiliated with or endorsed by Dodo Payments unless explicitly stated.
* **What you'll find**: SDKs, webhook handlers, plugins, and starters across languages
* **How to use**: Evaluate licensing, maintenance status, and security for your use case
* **How to contribute**: Submit your project via our template on the "Submit your project" page
Use at your own risk. Review code, licenses, and security posture before production use.
Browse by type and language with links to code and docs.
Add your project with a short PR using our template.
# Projects directory
Source: https://docs.dodopayments.com/community/projects
Community-maintained libraries, plugins, and boilerplates for Dodo Payments.
Browse community projects by category. Each card links directly to the repository.
Universal webhook verification and handlers for multiple frameworks.
Includes signature verification and typed event handling for Express, Hono, Elysia, and more.
`TypeScript` `Webhooks` `Node`
Rust SDK for interacting with Dodo Payments APIs.
Provides typed models and helpers to call core endpoints from Rust services.
`Rust` `SDK`
Laravel SDK for Dodo Payments.
Easily integrate Dodo Payments APIs into your Laravel applications with ready-made service classes and helpers.
`PHP` `Laravel` `SDK`
Easy-to-use SDK for interacting with Dodo Payments APIs.
It comes with predefined structures for requests and responses and uses a builder pattern to simplify constructing and sending payment-related API calls.
`Rust` `SDK`
Handle Dodo Payments for multiple applications using single microservice application:
Production-ready payment microservice built with FastAPI that handles payments for multiple applications using Dodo Payments.
Features include `credit management`, `subscription support`, `webhook processing with idempotency`, `complete audit trails`, `MixPanel Analytics`, `Backend-to-backend architecture with API key authentication`.
`PHP` `WHMCS` `Plugin`
Starter boilerplate for integrating Dodo Payments quickly.
Includes example checkout flow, API wiring, and environment configuration.
`Template`
Next.js SaaS starter with Supabase authentication and Dodo Payments integration.
You can go from an empty folder to a working app in an afternoon.
`TypeScript` `Next.js` `Boilerplate`
Want your project listed? See Submit your project.
# Submit your project
Source: https://docs.dodopayments.com/community/submit
How to add your open-source project to the Dodo Payments community directory.
* The project is open-source with a permissive license.
* It’s relevant to Dodo Payments (SDK, plugin, integration, template).
* Has a README with install, usage, and maintenance status.
Edit `community/projects.mdx` and add a new `` under the most relevant tab.
```mdx theme={null}
One-sentence description of your project.
By @github_username
`LANGUAGE` `FRAMEWORK` `CATEGORY`
```
If you prefer, open an issue using the "Community Project Submission" template with:
* Repo URL, description, category (SDK/Plugin/Boilerplate)
* Primary language/framework
* Install/usage snippet link
* Maintenance status and last update
We review for relevance, clarity, and safety. Approved entries are merged and published.
We may remove entries that are unmaintained, insecure, misleading, or violate policy.
# Astro Adaptor
Source: https://docs.dodopayments.com/developer-resources/astro-adaptor
Learn how to integrate Dodo Payments with your Astro App Router project using our Astro Adaptor. Covers checkout, customer portal, webhooks, and secure environment setup.
Integrate Dodo Payments checkout into your Astro app.
Allow customers to manage subscriptions and details.
Receive and process Dodo Payments webhook events.
## Installation
Run the following command in your project root:
```bash theme={null}
npm install @dodopayments/astro
```
Create a .env file in your project root:
```env expandable theme={null}
DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode"
DODO_PAYMENTS_RETURN_URL=https://yourdomain.com/checkout/success
```
Never commit your .env file or secrets to version control.
## Route Handler Examples
All examples assume you are using the Astro App Router.
Use this handler to integrate Dodo Payments checkout into your Astro app. Supports static (GET), dynamic (POST), and session (POST) payment flows.
```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: "dynamic", // for dynamic checkout
});
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
});
```
```curl Static Checkout Curl example expandable theme={null}
curl --request GET \
--url 'https://example.com/api/checkout?productId=pdt_fqJhl7pxKWiLhwQR042rh' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test
```
```curl Dynamic Checkout Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"billing": {
"city": "Texas",
"country": "US",
"state": "Texas",
"street": "56, hhh",
"zipcode": "560000"
},
"customer": {
"email": "test@example.com",
"name": "test"
},
"metadata": {},
"payment_link": true,
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1,
"billing_currency": "USD",
"discount_code": "IKHZ23M9GQ",
"return_url": "https://example.com",
"trial_period_days": 10
}'
```
```curl Checkout Session Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"product_cart": [
{
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1
}
],
"customer": {
"email": "test@example.com",
"name": "test"
},
"return_url": "https://example.com/success"
}'
```
Use this handler to allow customers to manage their subscriptions and details via the Dodo Payments customer portal.
```typescript Astro Route Handler expandable theme={null}
// src/pages/api/customer-portal.ts
import { CustomerPortal } from "@dodopayments/astro";
export const GET = CustomerPortal({
bearerToken: import.meta.env.DODO_PAYMENTS_API_KEY,
environment: import.meta.env.DODO_PAYMENTS_ENVIRONMENT,
});
```
```curl Customer Portal Curl example expandable theme={null}
curl --request GET \
--url 'https://example.com/api/customer-portal?customer_id=cus_9VuW4K7O3GHwasENg31m&send_email=true' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test
```
Use this handler to receive and process Dodo Payments webhook events securely in your Astro app.
```typescript expandable theme={null}
// src/pages/api/webhook.ts
import { Webhooks } from "@dodopayments/astro";
export const POST = Webhooks({
webhookKey: import.meta.env.DODO_PAYMENTS_WEBHOOK_KEY,
onPayload: async (payload) => {
// handle the payload
},
// ... other event handlers for granular control
});
```
## Checkout Route Handler
Dodo Payments supports three types of payment flows for integrating payments into your website, this adaptor supports all types of payment flows.
* **Static Payment Links:** Instantly shareable URLs for quick, no-code payment collection.
* **Dynamic Payment Links:** Programmatically generate payment links with custom details using the API or SDKs.
* **Checkout Sessions:** Create secure, customizable checkout experiences with pre-configured product carts and customer details.
### Supported Query Parameters
Product identifier (e.g., ?productId=pdt\_nZuwz45WAs64n3l07zpQR).
Quantity of the product.
Customer's full name.
Customer's first name.
Customer's last name.
Customer's email address.
Customer's country.
Customer's address line.
Customer's city.
Customer's state/province.
Customer's zip/postal code.
Disable full name field.
Disable first name field.
Disable last name field.
Disable email field.
Disable country field.
Disable address line field.
Disable city field.
Disable state field.
Disable zip code field.
Specify the payment currency (e.g., USD).
Show currency selector.
Specify the payment amount (e.g., 1000 for \$10.00).
Show discount fields.
Any query parameter starting with metadata\_ will be passed as metadata.
If productId is missing, the handler returns a 400 response. Invalid query parameters also result in a 400 response.
### Response Format
Static checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/..."
}
```
* Send parameters as a JSON body in a POST request.
* Supports both one-time and recurring payments.
* For a complete list of supported POST body fields, refer to:
* [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)
### Response Format
Dynamic checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/..."
}
```
Checkout sessions provide a more secure, hosted checkout experience that handles the complete payment flow for both one-time purchases and subscriptions with full customization control.
Refer to [Checkout Sessions Integration Guide](https://docs.dodopayments.com/developer-resources/checkout-session) for more details and a complete list of supported fields.
### Response Format
Checkout sessions return a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/session/..."
}
```
## Customer Portal Route Handler
The Customer Portal Route Handler enables you to seamlessly integrate the Dodo Payments customer portal into your Astro application.
### Query Parameters
The customer ID for the portal session (e.g., ?customer\_id=cus\_123).
If set to true, sends an email to the customer with the portal link.
Returns 400 if customer\_id is missing.
## Webhook Route Handler
* **Method:** Only POST requests are supported. Other methods return 405.
* **Signature Verification:** Verifies the webhook signature using webhookKey. 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 the appropriate event handler based on the payload type.
### Supported Webhook Event Handlers
```typescript Typescript expandable theme={null}
onPayload?: (payload: WebhookPayload) => Promise;
onPaymentSucceeded?: (payload: WebhookPayload) => Promise;
onPaymentFailed?: (payload: WebhookPayload) => Promise;
onPaymentProcessing?: (payload: WebhookPayload) => Promise;
onPaymentCancelled?: (payload: WebhookPayload) => Promise;
onRefundSucceeded?: (payload: WebhookPayload) => Promise;
onRefundFailed?: (payload: WebhookPayload) => Promise;
onDisputeOpened?: (payload: WebhookPayload) => Promise;
onDisputeExpired?: (payload: WebhookPayload) => Promise;
onDisputeAccepted?: (payload: WebhookPayload) => Promise;
onDisputeCancelled?: (payload: WebhookPayload) => Promise;
onDisputeChallenged?: (payload: WebhookPayload) => Promise;
onDisputeWon?: (payload: WebhookPayload) => Promise;
onDisputeLost?: (payload: WebhookPayload) => Promise;
onSubscriptionActive?: (payload: WebhookPayload) => Promise;
onSubscriptionOnHold?: (payload: WebhookPayload) => Promise;
onSubscriptionRenewed?: (payload: WebhookPayload) => Promise;
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise;
onSubscriptionCancelled?: (payload: WebhookPayload) => Promise;
onSubscriptionFailed?: (payload: WebhookPayload) => Promise;
onSubscriptionExpired?: (payload: WebhookPayload) => Promise;
onLicenseKeyCreated?: (payload: WebhookPayload) => Promise;
```
***
## Prompt for LLM
```
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 the Astro App Router.
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
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 redirects users to the Dodo Payments checkout page.
File Creation: Create a new file at app/checkout/route.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: "dynamic", // for dynamic checkout
});
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
});
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.
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://checkout.dodopayments.com/..."}
Dynamic Checkout (POST) - Returns JSON with checkout_url: Parameters are sent as a JSON body. Supports both one-time and recurring payments. Returns: {"checkout_url": "https://checkout.dodopayments.com/..."}. 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. 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 Route Handler is selected:
Purpose: This handler redirects authenticated users to their Dodo Payments customer portal.
File Creation: Create a new file at app/customer-portal/route.ts in your Astro project.
Code Snippet:
// src/pages/api/customer-portal.ts
import { CustomerPortal } from "@dodopayments/astro";
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 app/api/webhook/dodo-payments/route.ts in your Astro project.
Code Snippet:
// src/pages/api/webhook.ts
import { Webhooks } from "@dodopayments/astro";
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 the appropriate event handler based on the payload type.
Supported Webhook Event Handlers:
onPayload?: (payload: WebhookPayload) => Promise
onPaymentSucceeded?: (payload: WebhookPayload) => Promise
onPaymentFailed?: (payload: WebhookPayload) => Promise
onPaymentProcessing?: (payload: WebhookPayload) => Promise
onPaymentCancelled?: (payload: WebhookPayload) => Promise
onRefundSucceeded?: (payload: WebhookPayload) => Promise
onRefundFailed?: (payload: WebhookPayload) => Promise
onDisputeOpened?: (payload: WebhookPayload) => Promise
onDisputeExpired?: (payload: WebhookPayload) => Promise
onDisputeAccepted?: (payload: WebhookPayload) => Promise
onDisputeCancelled?: (payload: WebhookPayload) => Promise
onDisputeChallenged?: (payload: WebhookPayload) => Promise
onDisputeWon?: (payload: WebhookPayload) => Promise
onDisputeLost?: (payload: WebhookPayload) => Promise
onSubscriptionActive?: (payload: WebhookPayload) => Promise
onSubscriptionOnHold?: (payload: WebhookPayload) => Promise
onSubscriptionRenewed?: (payload: WebhookPayload) => Promise
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise
onSubscriptionCancelled?: (payload: WebhookPayload) => Promise
onSubscriptionFailed?: (payload: WebhookPayload) => Promise
onSubscriptionExpired?: (payload: WebhookPayload) => Promise
onLicenseKeyCreated?: (payload: WebhookPayload) => Promise
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).
RETURN_URL: (Optional) The URL to redirect to after a successful checkout (for Checkout handler).
DODO_PAYMENTS_WEBHOOK_SECRET: Your Dodo Payments Webhook Secret (required for Webhook handler).
Example .env file:
DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_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
```
# Astro Minimal Boilerplate
Source: https://docs.dodopayments.com/developer-resources/astro-boilerplate
Get started quickly with our minimal Astro boilerplate for integrating Dodo Payments into your Astro application
## Overview
The Astro minimal boilerplate provides a ready-to-use starting point for integrating Dodo Payments with your Astro application. This template includes authentication, webhook handling, and basic payment setup to help you start accepting payments quickly.
This boilerplate uses Astro with TypeScript and includes example implementations for checkout sessions and webhook verification.
## Quick Start
Clone the boilerplate repository and navigate to the project directory:
```bash theme={null}
git clone https://github.com/dodopayments/dodo-astro-minimal-boilerplate.git
cd dodo-astro-minimal-boilerplate
```
Obtain your credentials from the Dodo Payments dashboard in Test Mode:
* **API Key**: Navigate to **Developer → API Keys** in your [dashboard](https://app.dodopayments.com)
* **Webhook Signing Key**: Navigate to **Developer → Webhooks** in your [dashboard](https://app.dodopayments.com)
Start with Test Mode to safely test your integration without processing real payments.
Create a `.env` file in the root directory:
```bash theme={null}
touch .env
```
Add your Dodo Payments credentials:
```bash .env theme={null}
DODO_API_KEY_TEST=your_test_api_key_here
DODO_PAYMENTS_WEBHOOK_KEY=your_webhook_signing_key_here
```
Never commit your `.env` file to version control. It's already included in `.gitignore`.
When testing webhooks locally, expose your localhost using tools like [ngrok](https://ngrok.com):
```bash theme={null}
ngrok http 4321
```
Add the ngrok URL to the `allowed_hosts` list in `astro.config.mjs` for webhook delivery:
```javascript astro.config.mjs theme={null}
export default defineConfig({
server: {
host: true,
allowedHosts: ['your-ngrok-url.ngrok.io']
}
});
```
This step is required for webhooks to be delivered to your local development environment.
Install the required npm packages:
```bash theme={null}
npm install
```
Start the Astro development server:
```bash theme={null}
npm run dev
```
Your application will be available at `http://localhost:4321`.
You're ready to start building! The boilerplate includes example routes and components to help you understand the integration.
## What's Included
The boilerplate comes with pre-configured essentials:
Example implementation for creating and handling checkout sessions
Secure webhook endpoint with signature verification
Full TypeScript configuration with proper type definitions
Pre-configured styling with Tailwind CSS
## Project Structure
```
dodo-astro-minimal-boilerplate/
├── src/
│ ├── pages/
│ │ ├── api/
│ │ │ └── webhooks.ts # Webhook handler
│ │ └── index.astro # Home page with examples
│ └── lib/
│ └── dodo.ts # Dodo Payments client setup
├── .env.example # Environment variables template
├── astro.config.mjs # Astro configuration
└── package.json
```
## Next Steps
Once you have the boilerplate running:
Add products in your [Dodo Payments dashboard](https://app.dodopayments.com) to enable checkout functionality.
Use [ngrok](https://ngrok.com) to expose your local webhook endpoint and update the `allowed_hosts` in `astro.config.mjs`:
```bash theme={null}
ngrok http 4321
```
Add the ngrok URL to your webhook settings in the dashboard.
Adapt the example code to match your application's needs and design.
When ready, switch to Live Mode API keys and deploy your application.
## Additional Resources
Learn about our comprehensive Astro adapter for advanced integrations
Detailed guide for implementing one-time payments
Complete reference for all webhook event types
Explore the full Dodo Payments API documentation
## Support
Need help with the boilerplate?
* Join our [Discord community](https://discord.gg/bYqAp4ayYh) for questions and discussions
* Check the [GitHub repository](https://github.com/dodopayments/dodo-astro-minimal-boilerplate) for issues and updates
* Contact our [support team](mailto:support@dodopayments.com) for assistance
# Better-Auth Adapter
Source: https://docs.dodopayments.com/developer-resources/better-auth-adaptor
This guide shows you how to integrate Dodo Payments into your authentication flow using the Better-Auth adaptor.
# Overview
The Better Auth Adapter for Dodo Payments provides:
* Automatic customer creation on sign-up
* Checkout Sessions (preferred) with product slug mapping
* Self-service customer portal
* Real-time webhook event processing with signature verification
* Full TypeScript support
You need a Dodo Payments account and API keys to use this integration.
# Prerequisites
* Node.js 20+
* Access to your Dodo Payments Dashboard
* Existing project using [better-auth](https://www.npmjs.com/package/better-auth)
# Installation
Run the following command in your project root:
```bash theme={null}
npm install @dodopayments/better-auth dodopayments better-auth zod
```
All required packages are now installed.
# Setup
Add these to your .env file:
```env expandable theme={null}
DODO_PAYMENTS_API_KEY=your_api_key_here
DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=your_better_auth_secret_here
```
Never commit API keys or secrets to version control.
Create or update src/lib/auth.ts:
```typescript expandable theme={null}
import { betterAuth } from "better-auth";
import {
dodopayments,
checkout,
portal,
webhooks,
} from "@dodopayments/better-auth";
import DodoPayments from "dodopayments";
export const dodoPayments = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
environment: "test_mode", // or "live_mode" for production
});
export const { auth, endpoints, client } = BetterAuth({
plugins: [
dodopayments({
client: dodoPayments,
createCustomerOnSignUp: true,
use: [
checkout({
products: [
{
productId: "pdt_xxxxxxxxxxxxxxxxxxxxx",
slug: "premium-plan",
},
],
successUrl: "/dashboard/success",
authenticatedUsersOnly: true,
}),
portal(),
webhooks({
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_SECRET!,
onPayload: async (payload) => {
console.log("Received webhook:", payload.event_type);
},
}),
],
}),
],
});
```
Set environment to live\_mode for production.
Create or update src/lib/auth-client.ts:
```typescript expandable theme={null}
import { createAuthClient } from "better-auth/react";
import { dodopaymentsClient } from "@dodopayments/better-auth";
export const authClient = createAuthClient({
baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000",
plugins: [dodopaymentsClient()],
});
```
# Usage Examples
Prefer authClient.dodopayments.checkoutSession for new
integrations. The legacy checkout method is deprecated and kept
only for backward compatibility.
## Creating a Checkout Session (Preferred)
```typescript Using a slug theme={null}
const { data: session, error } = await authClient.dodopayments.checkoutSession({
// Option A: use a configured slug
slug: "premium-plan",
// Your internal id for reference
referenceId: "order_123",
});
if (session) {
window.location.href = session.url;
}
```
```typescript Using product cart theme={null}
const { data: session, error } = await authClient.dodopayments.checkoutSession({
// Option B: pass a product cart/product id directly
product_cart: [
{
product_id: "pdt_xxxxxxxxxxxxxxxxxxxxx",
quantity: 1,
}
],
// Your internal id for reference
referenceId: "order_123",
});
if (session) {
window.location.href = session.url;
}
```
Unlike the legacy checkout method, checkoutSession
does not require billing information upfront as it will be taken from the user
at the checkout page. You can still override it by passing the
billing key in the argument.
Similar to the legacy checkout method,
checkoutSession takes customer's email and name from their
better-auth session, however, you can override it by passing the{" "}
customer object with email and name.
You can pass the same options in the argument as the request body for [Create
Checkout
Session](https://docs.dodopayments.com/api-reference/checkout-sessions/create)
endpoint.
The return URL is taken from the successUrl configured in the
server plugin. You do not need to include return\_url in the
client payload.
## Legacy Checkout (Deprecated)
The authClient.dodopayments.checkout method is deprecated. Use
checkoutSession instead for new implementations.
```typescript expandable theme={null}
const { data: checkout, error } = await authClient.dodopayments.checkout({
slug: "premium-plan",
customer: {
email: "customer@example.com",
name: "John Doe",
},
billing: {
city: "San Francisco",
country: "US",
state: "CA",
street: "123 Market St",
zipcode: "94103",
},
referenceId: "order_123",
});
if (checkout) {
window.location.href = checkout.url;
}
```
## Accessing the Customer Portal
```typescript expandable theme={null}
const { data: customerPortal, error } =
await authClient.dodopayments.customer.portal();
if (customerPortal && customerPortal.redirect) {
window.location.href = customerPortal.url;
}
```
## Listing Customer Data
```typescript expandable theme={null}
// Get subscriptions
const { data: subscriptions, error } =
await authClient.dodopayments.customer.subscriptions.list({
query: {
limit: 10,
page: 1,
status: "active",
},
});
// Get payment history
const { data: payments, error } =
await authClient.dodopayments.customer.payments.list({
query: {
limit: 10,
page: 1,
status: "succeeded",
},
});
```
# Webhooks
The webhooks plugin processes real-time payment events from Dodo Payments with
secure signature verification. The default endpoint is{" "}
/api/auth/dodopayments/webhooks.
Generate a webhook secret for your endpoint URL (e.g., https\://\/api/auth/dodopayments/webhooks) in the Dodo Payments Dashboard and set it in your .env file:
```env theme={null}
DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here
```
Example handler:
```typescript expandable theme={null}
webhooks({
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_SECRET!,
onPayload: async (payload) => {
console.log("Received webhook:", payload.event_type);
},
});
```
### Supported Webhook Event Handlers
```typescript Typescript expandable theme={null}
onPayload?: (payload: WebhookPayload) => Promise;
onPaymentSucceeded?: (payload: WebhookPayload) => Promise;
onPaymentFailed?: (payload: WebhookPayload) => Promise;
onPaymentProcessing?: (payload: WebhookPayload) => Promise;
onPaymentCancelled?: (payload: WebhookPayload) => Promise;
onRefundSucceeded?: (payload: WebhookPayload) => Promise;
onRefundFailed?: (payload: WebhookPayload) => Promise;
onDisputeOpened?: (payload: WebhookPayload) => Promise;
onDisputeExpired?: (payload: WebhookPayload) => Promise;
onDisputeAccepted?: (payload: WebhookPayload) => Promise;
onDisputeCancelled?: (payload: WebhookPayload) => Promise;
onDisputeChallenged?: (payload: WebhookPayload) => Promise;
onDisputeWon?: (payload: WebhookPayload) => Promise;
onDisputeLost?: (payload: WebhookPayload) => Promise;
onSubscriptionActive?: (payload: WebhookPayload) => Promise;
onSubscriptionOnHold?: (payload: WebhookPayload) => Promise;
onSubscriptionRenewed?: (payload: WebhookPayload) => Promise;
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise;
onSubscriptionCancelled?: (payload: WebhookPayload) => Promise;
onSubscriptionFailed?: (payload: WebhookPayload) => Promise;
onSubscriptionExpired?: (payload: WebhookPayload) => Promise;
onLicenseKeyCreated?: (payload: WebhookPayload) => Promise;
```
# Configuration Reference
* client (required): DodoPayments client instance -{" "}
createCustomerOnSignUp (optional): Auto-create customers on user
signup - use (required): Array of plugins to enable (checkout,
portal, webhooks)
* products: Array of products or async function returning products -{" "}
successUrl: URL to redirect after successful payment -{" "}
authenticatedUsersOnly: Require user authentication (default: false)
# Troubleshooting & Tips
* Invalid API key: Double-check your{" "}
DODO\_PAYMENTS\_API\_KEY in .env. -{" "}
Webhook signature mismatch: Ensure the webhook secret matches the one
set in the Dodo Payments Dashboard. - Customer not created: Confirm{" "}
createCustomerOnSignUp is set to true.
* Use environment variables for all secrets and keys. - Test in{" "}
test\_mode before switching to live\_mode. - Log
webhook events for debugging and auditing.
# Prompt for LLM
```
You are a skilled developer helping to integrate the @dodopayments/better-auth adapter into a typescript web application with better-auth. This adapter enables seamless payment processing through Dodo Payments with automatic customer management, checkout flows, and webhook handling.
STAGE 1: BASIC SETUP
This stage covers the foundational setup needed before implementing any plugins. Complete this stage first.
STEP 1: Installation
Install the required dependencies:
npm install @dodopayments/better-auth dodopayments better-auth zod
STEP 2: Environment Variables Setup
You will need to complete these external setup tasks. I will provide you with a TODO list for the actions you need to take outside of the code:
TODO LIST FOR USER:
1. Generate Dodo Payments API Key:
- Go to your Dodo Payments Dashboard > Developer > API Keys
- Create a new API key (or use existing)
- Copy the API key value
- Set environment variable: DODO_PAYMENTS_API_KEY=your_api_key_here
2. Generate Better Auth Secret:
- Generate a random secret key (32+ characters)
- Set environment variable: BETTER_AUTH_SECRET=your_better_auth_secret_here
3. Set Application URL:
- For development: BETTER_AUTH_URL=http://localhost:3000
- For production: BETTER_AUTH_URL=https://your-domain.com
4. Webhook Secret (only if implementing webhooks plugin):
- This will be provided after you specify your domain name in Stage 2
- Set environment variable: DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here
Add these environment variables to your .env file:
DODO_PAYMENTS_API_KEY=your_api_key_here
DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=your_better_auth_secret_here
STEP 3: Server Configuration
Create or update your better-auth setup file (src/lib/auth.ts):
import { BetterAuth } from "better-auth";
import { dodopayments } from "@dodopayments/better-auth";
import DodoPayments from "dodopayments";
// Create DodoPayments client
export const dodoPayments = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
environment: "test_mode", // Change to "live_mode" for production
});
// Configure better-auth with dodopayments adapter
export const { auth, endpoints, client } = BetterAuth({
plugins: [
dodopayments({
client: dodoPayments,
createCustomerOnSignUp: true, // Auto-create customers on signup
use: [], // We'll add plugins here in Stage 2
}),
],
});
STEP 4: Client Configuration
Create or update your auth client file (src/lib/auth-client.ts):
import { createAuthClient } from "better-auth/react";
import { dodopaymentsClient } from "@dodopayments/better-auth";
export const authClient = createAuthClient({
baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000",
plugins: [dodopaymentsClient()],
});
STAGE 2: PLUGIN IMPLEMENTATION
After completing Stage 1, you can selectively implement any of these plugins based on your needs. Each plugin is independent and can be added or removed as needed.
PLUGIN SELECTION:
Before implementing any plugins, ask the user which plugins they want to implement:
"Which plugins would you like to implement? You can choose any combination of:
1. CHECKOUT - Enables secure payment processing and checkout flows
2. PORTAL - Provides customer self-service portal for subscriptions and payments
3. WEBHOOKS - Handles real-time payment events from Dodo Payments
Please specify which plugins you want (e.g., 'checkout and webhooks', 'all three', 'just portal', etc.)"
If the user doesn't respond or you cannot prompt the user, implement all three plugins by default.
Based on the user's selection, implement only the requested plugins from the sections below:
CHECKOUT PLUGIN
Purpose: Enables secure payment processing using Checkout Sessions (preferred) with product slug mapping. A legacy checkout endpoint is also exposed for backward compatibility but is deprecated.
SETUP TODO LIST FOR USER:
1. Create products in Dodo Payments Dashboard:
- Go to Dodo Payments Dashboard > Products
- Create your products (e.g., subscription plans, one-time purchases)
- Copy each product ID (starts with "pdt_")
- Note down the product names for creating friendly slugs
2. Plan your checkout URLs:
- Decide on your success URL (e.g., "/dashboard/success", "/thank-you")
- Ensure this URL exists in your application
Configuration:
Add checkout to your imports in src/lib/auth.ts:
import { dodopayments, checkout } from "@dodopayments/better-auth";
Add checkout plugin to the use array in your dodopayments configuration:
use: [
checkout({
products: [
{
productId: "pdt_xxxxxxxxxxxxxxxxxxxxx", // Your actual product ID from Dodo Payments
slug: "premium-plan", // Friendly slug for checkout
},
// Add more products as needed
],
successUrl: "/dashboard/success", // Your success page URL
authenticatedUsersOnly: true, // Require login for checkout
}),
],
Usage Example (Preferred - Checkout Sessions):
const { data: session, error } = await authClient.dodopayments.checkoutSession({
// Use the slug from your configuration OR provide product_cart directly
slug: "premium-plan",
// product_cart: [{ product_id: "pdt_xxxxxxxxxxxxxxxxxxxxx", quantity: 1 }],
referenceId: "order_123", // Optional reference
});
if (session) {
window.location.href = session.url;
}
Deprecated (Legacy Checkout):
const { data: checkout, error } = await authClient.dodopayments.checkout({
slug: "premium-plan",
customer: {
email: "customer@example.com",
name: "John Doe",
},
billing: {
city: "San Francisco",
country: "US",
state: "CA",
street: "123 Market St",
zipcode: "94103",
},
});
if (checkout) {
window.location.href = checkout.url;
}
Options:
- products: Array of products or async function returning products
- successUrl: URL to redirect after successful payment
- authenticatedUsersOnly: Require user authentication (default: false)
PORTAL PLUGIN
Purpose: Provides customer self-service capabilities for managing subscriptions and viewing payment history.
Configuration:
Add portal to your imports in src/lib/auth.ts:
import { dodopayments, portal } from "@dodopayments/better-auth";
Add portal plugin to the use array in your dodopayments configuration:
use: [
portal(),
],
Usage Examples:
// Access customer portal
const { data: customerPortal, error } = await authClient.dodopayments.customer.portal();
if (customerPortal && customerPortal.redirect) {
window.location.href = customerPortal.url;
}
// List customer subscriptions
const { data: subscriptions, error } = await authClient.dodopayments.customer.subscriptions.list({
query: {
limit: 10,
page: 1,
active: true,
},
});
// List customer payments
const { data: payments, error } = await authClient.dodopayments.customer.payments.list({
query: {
limit: 10,
page: 1,
status: "succeeded",
},
});
Note: All portal methods require user authentication.
WEBHOOKS PLUGIN
Purpose: Handles real-time payment events from Dodo Payments with secure signature verification.
BEFORE CONFIGURATION - Setup Webhook URL:
First, I need your domain name to generate the webhook URL and provide you with setup instructions.
STEP 1: Domain Name Input
What is your domain name? Please provide:
- For production: your domain name (e.g., "myapp.com", "api.mycompany.com")
- For staging: your staging domain (e.g., "staging.myapp.com")
- For development: use "localhost:3000" (or your local port)
STEP 2: After receiving your domain name, I will:
- Generate your webhook URL: https://[YOUR-DOMAIN]/api/auth/dodopayments/webhooks
- Provide you with a TODO list for webhook setup in Dodo Payments dashboard
- Give you the exact environment variable setup instructions
WEBHOOK SETUP TODO LIST (will be provided after domain input):
1. Configure webhook in Dodo Payments Dashboard:
- Go to Dodo Payments Dashboard > Developer > Webhooks
- Click "Add Webhook" or "Create Webhook"
- Enter webhook URL: https://[YOUR-DOMAIN]/api/auth/dodopayments/webhooks
- Select events you want to receive (or select all)
- Copy the generated webhook secret
2. Set webhook secret in your environment:
- For production: Set DODO_PAYMENTS_WEBHOOK_SECRET in your hosting provider environment
- For staging: Set DODO_PAYMENTS_WEBHOOK_SECRET in your staging environment
- For development: Add DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here to your .env file
3. Deploy your application with the webhook secret configured
STEP 3: Add webhooks to your imports in src/lib/auth.ts:
import { dodopayments, webhooks } from "@dodopayments/better-auth";
STEP 4: Add webhooks plugin to the use array in your dodopayments configuration:
use: [
webhooks({
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_SECRET!,
// Generic handler for all webhook events
onPayload: async (payload) => {
console.log("Received webhook:", payload.event_type);
},
// Payment event handlers
onPaymentSucceeded: async (payload) => {
console.log("Payment succeeded:", payload);
},
onPaymentFailed: async (payload) => {
console.log("Payment failed:", payload);
},
onPaymentProcessing: async (payload) => {
console.log("Payment processing:", payload);
},
onPaymentCancelled: async (payload) => {
console.log("Payment cancelled:", payload);
},
// Refund event handlers
onRefundSucceeded: async (payload) => {
console.log("Refund succeeded:", payload);
},
onRefundFailed: async (payload) => {
console.log("Refund failed:", payload);
},
// Dispute event handlers
onDisputeOpened: async (payload) => {
console.log("Dispute opened:", payload);
},
onDisputeExpired: async (payload) => {
console.log("Dispute expired:", payload);
},
onDisputeAccepted: async (payload) => {
console.log("Dispute accepted:", payload);
},
onDisputeCancelled: async (payload) => {
console.log("Dispute cancelled:", payload);
},
onDisputeChallenged: async (payload) => {
console.log("Dispute challenged:", payload);
},
onDisputeWon: async (payload) => {
console.log("Dispute won:", payload);
},
onDisputeLost: async (payload) => {
console.log("Dispute lost:", payload);
},
// Subscription event handlers
onSubscriptionActive: async (payload) => {
console.log("Subscription active:", payload);
},
onSubscriptionOnHold: async (payload) => {
console.log("Subscription on hold:", payload);
},
onSubscriptionRenewed: async (payload) => {
console.log("Subscription renewed:", payload);
},
onSubscriptionPlanChanged: async (payload) => {
console.log("Subscription plan changed:", payload);
},
onSubscriptionCancelled: async (payload) => {
console.log("Subscription cancelled:", payload);
},
onSubscriptionFailed: async (payload) => {
console.log("Subscription failed:", payload);
},
onSubscriptionExpired: async (payload) => {
console.log("Subscription expired:", payload);
},
// License key event handlers
onLicenseKeyCreated: async (payload) => {
console.log("License key created:", payload);
},
}),
],
Supported Webhook Event Handlers:
- onPayload: Generic handler for all webhook events
- onPaymentSucceeded: Payment completed successfully
- onPaymentFailed: Payment failed
- onPaymentProcessing: Payment is being processed
- onPaymentCancelled: Payment was cancelled
- onRefundSucceeded: Refund completed successfully
- onRefundFailed: Refund failed
- onDisputeOpened: Dispute was opened
- onDisputeExpired: Dispute expired
- onDisputeAccepted: Dispute was accepted
- onDisputeCancelled: Dispute was cancelled
- onDisputeChallenged: Dispute was challenged
- onDisputeWon: Dispute was won
- onDisputeLost: Dispute was lost
- onSubscriptionActive: Subscription became active
- onSubscriptionOnHold: Subscription was put on hold
- onSubscriptionRenewed: Subscription was renewed
- onSubscriptionPlanChanged: Subscription plan was changed
- onSubscriptionCancelled: Subscription was cancelled
- onSubscriptionFailed: Subscription failed
- onSubscriptionExpired: Subscription expired
- onLicenseKeyCreated: License key was created
COMBINING SELECTED PLUGINS:
After implementing the user's selected plugins, update your src/lib/auth.ts file to include all chosen plugins in the imports and use array:
Example for all three plugins:
import { dodopayments, checkout, portal, webhooks } from "@dodopayments/better-auth";
use: [
checkout({
// checkout configuration
}),
portal(),
webhooks({
// webhook configuration
}),
],
Example for just checkout and portal:
import { dodopayments, checkout, portal } from "@dodopayments/better-auth";
use: [
checkout({
// checkout configuration
}),
portal(),
],
Example for just webhooks:
import { dodopayments, webhooks } from "@dodopayments/better-auth";
use: [
webhooks({
// webhook configuration
}),
],
IMPORTANT NOTES:
1. Complete Stage 1 before implementing any plugins
2. Ask the user which plugins they want to implement, or implement all three if no response
3. Only implement the plugins the user specifically requested
4. ALWAYS provide TODO lists for external actions the user needs to complete:
- API key generation and environment variable setup
- Product creation in Dodo Payments dashboard (for checkout plugin)
- Webhook setup in Dodo Payments dashboard (for webhooks plugin)
- Domain name collection for webhook URL generation
5. For webhook plugin: Ask for the user's domain name and generate the exact webhook URL: https://[domain]/api/auth/webhooks/dodopayments
6. All client methods return { data, error } objects for proper error handling
7. Use test_mode for development and live_mode for production
8. The webhook endpoint is automatically created and secured with signature verification (if webhooks plugin is selected)
9. Customer portal and subscription listing require user authentication (if portal plugin is selected)
10. Handle errors appropriately and test webhook functionality in development before going live
11. Present all external setup tasks as clear TODO lists with specific environment variable names
```
# Billing SDK
Source: https://docs.dodopayments.com/developer-resources/billingsdk
Complete billing infrastructure for modern web applications with React components, CLI tooling, and full-stack integration
Billing SDK is a comprehensive open-source solution that provides complete billing infrastructure for modern web applications. It includes React components, CLI tooling, and full-stack integration support for multiple frameworks with Dodo Payments.
## Overview
Billing SDK offers:
* **React Components**: Pre-built, customizable billing components for React applications
* **CLI Tooling**: Command-line interface for project initialization and component management
* **Framework Support**: Integration with Next.js, Express.js, Hono, Fastify, and React
* **Payment Provider**: Full integration with Dodo Payments
## Quick Start
Get started with Billing SDK in minutes:
Use npx to run the CLI without installation:
```bash theme={null}
npx @billingsdk/cli --help
```
Or install it globally for frequent use:
```bash theme={null}
npm install -g @billingsdk/cli
```
Run the interactive setup to create a new billing project:
```bash theme={null}
npx @billingsdk/cli init
```
You'll be prompted to select:
* Your preferred framework (Next.js, Express.js, Hono, Fastify, React)
* Your payment provider (Dodo Payments)
Add individual billing components to your project:
```bash theme={null}
npx @billingsdk/cli add pricing-table-one
npx @billingsdk/cli add subscription-management
```
## Supported Frameworks
Billing SDK supports multiple frameworks with comprehensive Dodo Payments integration:
| Framework | Dodo Payments |
| ---------- | ------------- |
| Next.js | ✅ Yes |
| Express.js | ✅ Yes |
| Hono | ✅ Yes |
| React | ✅ Yes |
| Fastify | ✅ Yes |
## CLI Commands
### `@billingsdk/cli init`
Initialize a new billing project with complete setup including framework configuration, Dodo Payments integration, and essential dependencies.
**What it does:**
1. **Framework Selection**: Choose your preferred framework
2. **Template Installation**: Downloads and installs framework-specific templates with Dodo Payments integration
3. **Dependency Management**: Automatically installs required dependencies
4. **File Generation**: Creates necessary configuration files and boilerplate code
**Example:**
```bash theme={null}
npx @billingsdk/cli init
```
### `@billingsdk/cli add`
Add individual billing components to your existing project using the shadcn/ui registry system.
**Examples:**
```bash theme={null}
# Add a pricing table
npx @billingsdk/cli add pricing-table-one
# Add subscription management
npx @billingsdk/cli add subscription-management
# Add usage monitoring
npx @billingsdk/cli add usage-meter-circle
```
**What happens:**
1. Downloads the component configuration from the registry
2. Installs the component files in your `components/billingsdk/` directory
3. Updates your project configuration if needed
4. Installs any additional dependencies
## Generated Files Structure
After running `init`, you'll get a complete project structure with:
* **API Routes**: Comprehensive endpoints for checkout, customers, products, subscriptions, and webhooks
* **Hooks**: React hooks for billing operations (if using React/Next.js)
* **Library Files**: Payment provider client initialization and utilities
* **Environment Configuration**: Example environment variable files
### Next.js Example Structure
```
your-project/
├── app/api/
│ └── (dodopayments)/
│ ├── checkout/route.ts
│ ├── customer/route.ts
│ ├── customer/payments/route.ts
│ ├── customer/subscriptions/route.ts
│ ├── product/route.ts
│ ├── products/route.ts
│ └── webhook/route.ts
├── hooks/
│ └── useBilling.ts
├── lib/
│ └── dodopayments.ts
└── .env.example
```
## Environment Configuration
Configure your environment variables after initialization:
```bash theme={null}
# DodoPayments
DODO_PAYMENTS_API_KEY=your_api_key_here
DODO_PAYMENTS_ENVIRONMENT=test_mode
DODO_PAYMENTS_WEBHOOK_KEY=your_webhook_key_here
# App URL
NEXT_PUBLIC_APP_URL=http://localhost:3000
```
Always store your API keys securely using environment variables. Never commit them to version control.
## Available Components
Billing SDK provides a comprehensive collection of React components:
* **Pricing Tables**: Multiple variants for displaying product pricing
* **Subscription Management**: Components for managing customer subscriptions
* **Usage Monitoring**: Visual components for tracking usage-based billing
* **Checkout Components**: Pre-built checkout flows
* **Customer Portal**: Components for customer self-service
## Open Source
Billing SDK is built on open-source principles:
* 🔍 **Transparency**: Full visibility into how components work
* 🤝 **Community**: Built by developers, for developers
* 🔒 **Security**: Open code means better security through peer review
* 📈 **Innovation**: Faster iteration through community contributions
Billing SDK is released under the GNU General Public License (GPL), ensuring the project remains open and free.
## Getting Help
Need assistance with Billing SDK?
* **GitHub Issues**: [Open an issue](https://github.com/dodopayments/billingsdk/issues)
* **Documentation**: Visit [billingsdk.com/docs](https://billingsdk.com/docs)
* **LLMs Full Documentation**: [billingsdk.com/llms-full.txt](https://billingsdk.com/llms-full.txt)
## Contributing
We welcome contributions! See our [contribution guide](https://billingsdk.com/docs/contribution-open-source) for details on:
* Reporting bugs
* Requesting features
* Submitting pull requests
* Adding new components
* Improving documentation
## Learn More
Complete guide to Billing SDK with examples and best practices
Detailed CLI documentation for all commands and options
Browse all available components with live examples
# Build an AI Chat App with AI SDK & LLM Blueprint
Source: https://docs.dodopayments.com/developer-resources/build-an-ai-chat-app-with-usage-based-billing
Follow along as we build an AI chat app with automatic token tracking and usage-based billing
Let Sentra write your integration code for you.
Use our AI assistant in VS Code, Cursor, or Windsurf to generate SDK/API code, LLM Blueprint integration code, webhooks, and more - just by describing what you want.
Try Sentra: AI-Powered Integration →
In this tutorial, you'll build an **AI chat application** with automatic usage-based billing. We'll create everything from scratch: the billing meter, product configuration, and application code that powers conversations and tracks token usage in real-time.
This tutorial provides a complete working application with both backend and frontend. The chat app uses Google's Gemini AI and automatically tracks token usage without any manual counting required.
By the end of this tutorial, you'll have a working chat application that:
* Powers AI conversations using Google Gemini (AI SDK)
* Automatically tracks token usage (no manual code)
* Charges customers based on actual token consumption
* Includes a beautiful chat interface
## What We're Building
Let's start by understanding our AI chat service:
* **Service**: AI-powered chat using Google Gemini (AI SDK)
* **Pricing Model**: Pay-per-token (\$0.01 per 1,000 tokens)
* **Free Tier**: 10,000 free tokens per customer per month
* **Features**: Conversation history, automatic token tracking
Before we start, make sure you have:
* A Dodo Payments account
* A Google AI API key ([Get one from aistudio](https://aistudio.google.com/api-keys))
* Node.js v16+ installed
***
## Step 1: Create Your Usage Meter
We'll start by creating a meter in your Dodo Payments dashboard that will track AI token usage.
**What we're building**: A meter named "AI Token Usage Meter" that sums up all tokens consumed in chat conversations.
1. Log into your Dodo Payments dashboard
2. Click on Products in the left sidebar
3. Click on **Meters**
4. Click the **Create Meter** button
You should see a form where we'll configure our token tracking.
Now we'll enter the specific details for our AI chat service:
**Meter Name** → `AI Token Usage Meter`
**Description** → `Tracks token consumption from AI chat conversations using AI SDK`
**Event Name** → `ai_chat_usage`
The event name `ai_chat_usage` must match exactly what we'll send from our application code later. Event names are case-sensitive!
Set up the aggregation (how the meter counts our events):
**Aggregation Type**: Select **Sum** from the dropdown
**Aggregate Over**: Type → `totalTokens`
**Measurement Unit**: Type → `tokens`
We're using "Sum" because we want to add up all tokens consumed across multiple chat messages. The SDK automatically sends `totalTokens` in each event.
1. Double-check all your settings match the values above
2. Click **Create Meter**
**Meter created!** Your "AI Token Usage Meter" is now ready to start counting tokens. Next, we'll connect it to a billing product.
## Step 2: Get Your API Keys
Before we build the app, let's gather the API keys we'll need.
1. In your Dodo Payments dashboard, go to **Developers** → **API Keys**
2. Click **Create API Key**
3. Copy the API key - it will look like `test_abc123...`
Save this API key - we'll add it to our `.env` file later.
1. Visit [aistudio.google.com](https://aistudio.google.com/api-keys)
2. Click **Get API Key**
3. Create a new API key or use an existing one
4. Copy the key
Keep this key safe - we'll also add it to our `.env` file.
***
## Step 3: Create Your Billing Product
Now we need to create a product that defines our pricing (\$0.01 per 1,000 tokens with 10,000 free tokens). This connects our meter to actual billing.
**What we're building**: A product called "AI Chat Service" that charges based on token consumption with a generous free tier.
1. In your Dodo Payments dashboard, click **Products** in the left sidebar
2. Click **Create Product**
3. Select **Usage-Based** as the product type
This tells Dodo Payments that billing will be based on meter usage, not a fixed subscription.
Fill in the required details:
**Product Name**: → `AI Chat Service`
**Description**: → `AI-powered chat service with automatic token-based billing`
**Product Image**: Upload a relevant image
These will appear on customer invoices, so make them clear and professional.
Before connecting your meter, make sure you have selected **Usage Based Billing** as the price type for your product.
Additionally, set the **Fixed Price** to `0` to ensure customers are only charged based on their usage, with no base fee.
Now, link the meter you just created:
1. Scroll down to the **Associated Meter** section
2. Click **Add Meters**
3. From the dropdown, select **"AI Token Usage Meter"** (the one you created earlier)
4. Confirm that it appears in your product configuration
Your meter is now successfully connected to this product.
Here's where we define our business model:
**Price Per Unit**: Enter → `0.00001` (this is $0.01 per 1,000 tokens or $0.00001 per token)
**Free Threshold**: Enter → `10000` (customers get 10,000 free tokens per month)
**How billing works**: If a customer uses 25,000 tokens in a month, they'll be charged for 15,000 tokens (25,000 - 10,000 free) = 15,000 × $0.00001 = $0.15
1. Review all your settings:
* Name: AI Chat Service
* Meter: AI Token Usage Meter
* Price: \$0.01 per 1,000 tokens
* Free tier: 10,000 tokens
2. Click **Save Changes**
**Product created!** Your billing is now configured. Customers will automatically be charged based on their token usage.
## Step 4: Make a Test Purchase
Before we start building the app, let's create a test customer by making a purchase.
1. In your Dodo Payments dashboard, go to **Products**
2. Find your "AI Chat Service" product
3. Click the **Share** button next to your product
4. Copy the payment link that appears
1. Open the payment link in a new browser tab
2. Enter test payment details and complete purchase
After successful payment, you'll have a customer ID that we'll use in our application code.
1. Go back to your Dodo Payments dashboard
2. Navigate to **Sales** -> **Customers** in the left sidebar
3. Find the customer you just created (with the test email)
4. Copy the customer ID - it will look like `cus_123`
Save this customer ID - we'll use it when testing our chat application.
## Step 5: Build the Chat Application
Now we have our billing setup complete and a test customer created. Let's build the AI chat application with automatic token tracking.
Create a new directory and initialize the project:
```bash theme={null}
mkdir ai-chat-app
cd ai-chat-app
npm init -y
```
Install the packages we need:
```bash theme={null}
npm install express ai @ai-sdk/google @dodopayments/ingestion-blueprints dotenv
npm install --save-dev typescript @types/express @types/node tsx
```
Create `tsconfig.json`:
```json tsconfig.json theme={null}
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
Update `package.json` to add the module type and scripts:
```json package.json theme={null}
{
"type": "module",
"scripts": {
"dev": "tsx src/server.ts",
"build": "tsc",
"start": "node dist/server.js"
}
}
```
Create the folders and files:
```bash theme={null}
mkdir src public
```
Create a `.env` file in your project root:
```bash .env theme={null}
DODO_PAYMENTS_API_KEY=your_dodo_api_key_here
DODO_ENVIRONMENT=test_mode
GOOGLE_GENERATIVE_AI_API_KEY=your_google_api_key_here
PORT=3000
```
Replace the placeholder values with your actual API keys from Step 2.
Create `src/server.ts` and copy this complete server code:
Here's the complete AI chat server with integrated billing:
```typescript src/server.ts expandable theme={null}
import express, { Request, Response } from 'express';
import { generateText } from 'ai';
import { google } from '@ai-sdk/google';
import { createLLMTracker } from '@dodopayments/ingestion-blueprints';
import 'dotenv/config';
const app = express();
app.use(express.json());
app.use(express.static('public'));
// Replace with your test customer ID
const CUSTOMER_ID = 'cus_123';
// Create tracker once with your meter event name
const llmTracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY!,
environment: process.env.DODO_ENVIRONMENT as 'test_mode' | 'live_mode',
eventName: 'ai_chat_usage', // Must match your meter configuration
});
// Chat endpoint with conversation support
app.post('/chat', async (req: Request, res: Response) => {
try {
const { messages } = req.body;
if (!messages || !Array.isArray(messages)) {
return res.status(400).json({
error: 'Missing required field: messages (array)'
});
}
// Wrap AI SDK with automatic token tracking
const trackedClient = llmTracker.wrap({
client: { generateText },
customerId: CUSTOMER_ID
});
// Generate AI response - tokens are automatically tracked!
const response = await trackedClient.generateText({
model: google('gemini-2.5-flash'),
messages: messages
});
res.json({
message: response.text,
usage: {
totalTokens: response.usage.totalTokens
}
});
} catch (error: any) {
console.error('Chat error:', error);
res.status(500).json({
error: 'Failed to process chat',
details: error.message
});
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`🚀 Server running at http://localhost:${PORT}`);
console.log(`📊 Tracking event: ai_chat_usage`);
console.log(`👤 Customer ID: ${CUSTOMER_ID}`);
console.log(`🔧 Environment: ${process.env.DODO_ENVIRONMENT}`);
});
```
```bash .env theme={null}
# Add your API keys here
DODO_PAYMENTS_API_KEY=your_dodo_api_key_here
DODO_ENVIRONMENT=test_mode
GOOGLE_GENERATIVE_AI_API_KEY=your_google_api_key_here
PORT=3000
```
```json package.json expandable theme={null}
{
"name": "ai-chat-app",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "tsx src/server.ts",
"build": "tsc",
"start": "node dist/server.js"
},
"dependencies": {
"express": "^4.18.2",
"ai": "^3.0.0",
"@ai-sdk/google": "^0.0.15",
"@dodopayments/ingestion-blueprints": "latest",
"dotenv": "^16.0.3"
},
"devDependencies": {
"typescript": "^5.0.0",
"@types/express": "^4.17.17",
"@types/node": "^20.0.0",
"tsx": "^3.12.7"
}
}
```
***
## Step 6: Add the Chat Interface
Now let's add a beautiful chat interface with full conversation history! Create `public/index.html`:
```html public/index.html expandable theme={null}
AI Chat with Usage Billing
🤖 AI Chat Assistant
Powered by AI-SDK & Dodo Payments
💬
Start a Conversation
Ask me anything! Your token usage is automatically tracked.
```
***
## Step 7: Test Your Chat Application
Time to test our AI chat app and see the billing in action! Let's make sure everything works end-to-end.
**What we're testing**: We'll have some conversations with the AI, verify the token events reach Dodo Payments, and confirm billing calculations are correct.
First, make sure everything is set up:
1. Verify your `.env` file has all the API keys from Step 2
2. Start the development server:
```bash theme={null}
npm run dev
```
You should see:
```
🚀 Server running at http://localhost:3000
📊 Tracking event: ai_chat_usage
👤 Customer ID: {YOUR CUSTOMER_ID}
🔧 Environment: test_mode
```
Server is running! Time to chat.
1. Open your browser
2. Navigate to `http://localhost:3000`
3. You should see the beautiful chat interface
Make sure you update the `CUSTOMER_ID` in `server.ts` with your actual test customer ID from Step 4.
Let's test it out! Try these messages:
1. **"What is artificial intelligence?"**
2. **"How does machine learning work?"**
3. **"Can you explain neural networks?"**
Watch the token usage display update after each response!
If you see the AI responding and token counts appearing, your app is working!
Now let's verify the events are being received:
1. Open your Dodo Payments dashboard
2. Go to **Usage Billing** → **AI Token Usage Meter**
3. Click on the **Events** tab
4. You should see your chat events listed
**What to look for:**
* Event names: `ai_chat_usage`
* Customer ID: Your test customer id
You should see one event for each message you sent!
Let's send some more messages and check if the token aggregation is working:
1. In your meter, go to the **Customers** tab
2. Find your test customer
3. Check the "Consumed Units" column - it should show the total tokens used
The meter is summing up all `totalTokens` values automatically!
Let's use enough tokens to exceed the free tier:
1. Have several more conversations (aim for \~15,000+ total tokens)
2. Check your **Customers** tab in meter dashboard again
3. You should now see:
* Consumed Units: 15,000+ tokens
* Chargeable Units: 5,000 (10,000 free tokens applied)
* Total Price: \~\$0.05
**Success!** Your usage-based billing is working perfectly. Customers will be automatically charged based on their actual token consumption.
***
## Troubleshooting
Common issues and their solutions:
**Possible causes:**
* Event name doesn't match meter configuration exactly
* Customer ID doesn't exist in your account
* API key is invalid or expired
* Network connectivity issues
**Solutions:**
1. Verify event name matches meter configuration exactly (case-sensitive: `ai_chat_usage`)
2. Check that customer ID exists in Dodo Payments dashboard
3. Test API key with a simple API call
4. Check server logs for error messages
**Possible causes:**
* Model not returning usage information
* Incorrect SDK version
**Solutions:**
1. Test if model returns usage:
```typescript theme={null}
const response = await generateText({...});
console.log('Usage:', response.usage);
```
2. Update to latest Blueprints SDK: `npm install @dodopayments/ingestion-blueprints@latest`
**Possible causes:**
* Wrong API key for environment
* Extra spaces or quotes in `.env` file
**Solutions:**
* Ensure test key starts with `test_`, live key starts with `live_`
* Remove any quotes around keys in `.env` file
* Generate a new key if needed
Need help?
* [Discord Community](https://discord.gg/bYqAp4ayYh)
* [support@dodopayments.com](mailto:support@dodopayments.com)
***
## Congratulations! You Built an AI Chat App
You now have a fully functional AI chat application with automatic token usage tracking and billing powered by Dodo Payments. 🎉
### Learn More
Learn more about LLM Ingestion Blueprint
Check out the GitHub repository for more examples
Get help and share your projects on Discord
# Checkout Sessions
Source: https://docs.dodopayments.com/developer-resources/checkout-session
Create secure, hosted checkout experiences that handle the complete payment flow for both one-time purchases and subscriptions with full customization control.
Get your first checkout session running in under 5 minutes
Explore the full API documentation and interactively test Checkout Session requests and responses.
**Session Validity**: Checkout sessions are valid for 24 hours by default. If you pass `confirm=true` in your request, the session will only be valid for 15 minutes.
## Prerequisites
You'll need an active Dodo Payments merchant account with API access.
Generate your API credentials from the Dodo Payments dashboard:
Create your products in the Dodo Payments dashboard before implementing checkout sessions.
## Creating Your First Checkout Session
```javascript expandable theme={null}
import DodoPayments from 'dodopayments';
// Initialize the Dodo Payments client
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
});
async function createCheckoutSession() {
try {
const session = await client.checkoutSessions.create({
// Products to sell - use IDs from your Dodo Payments dashboard
product_cart: [
{
product_id: 'prod_123', // Replace with your actual product ID
quantity: 1
}
],
// Pre-fill customer information to reduce friction
customer: {
email: 'customer@example.com',
name: 'John Doe',
phone_number: '+1234567890'
},
// Billing address for tax calculation and compliance
billing_address: {
street: '123 Main St',
city: 'San Francisco',
state: 'CA',
country: 'US', // Required: ISO 3166-1 alpha-2 country code
zipcode: '94102'
},
// Where to redirect after successful payment
return_url: 'https://yoursite.com/checkout/success',
// Custom data for your internal tracking
metadata: {
order_id: 'order_123',
source: 'web_app'
}
});
// Redirect your customer to this URL to complete payment
console.log('Checkout URL:', session.checkout_url);
console.log('Session ID:', session.session_id);
return session;
} catch (error) {
console.error('Failed to create checkout session:', error);
throw error;
}
}
// Example usage in an Express.js route
app.post('/create-checkout', async (req, res) => {
try {
const session = await createCheckoutSession();
res.json({ checkout_url: session.checkout_url });
} catch (error) {
res.status(500).json({ error: 'Failed to create checkout session' });
}
});
```
```python expandable theme={null}
import os
from dodopayments import DodoPayments
# Initialize the Dodo Payments client
client = DodoPayments(
bearer_token=os.environ.get("DODO_PAYMENTS_API_KEY"),
)
def create_checkout_session():
"""
Create a checkout session for a single product with customer data pre-filled.
Returns the session object containing checkout_url for customer redirection.
"""
try:
session = client.checkout_sessions.create(
# Products to sell - use IDs from your Dodo Payments dashboard
product_cart=[
{
"product_id": "prod_123", # Replace with your actual product ID
"quantity": 1
}
],
# Pre-fill customer information to reduce checkout friction
customer={
"email": "customer@example.com",
"name": "John Doe",
"phone_number": "+1234567890"
},
# Billing address for tax calculation and compliance
billing_address={
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"country": "US", # Required: ISO 3166-1 alpha-2 country code
"zipcode": "94102"
},
# Where to redirect after successful payment
return_url="https://yoursite.com/checkout/success",
# Custom data for your internal tracking
metadata={
"order_id": "order_123",
"source": "web_app"
}
)
# Redirect your customer to this URL to complete payment
print(f"Checkout URL: {session.checkout_url}")
print(f"Session ID: {session.session_id}")
return session
except Exception as error:
print(f"Failed to create checkout session: {error}")
raise error
# Example usage in a Flask route
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.route('/create-checkout', methods=['POST'])
def create_checkout():
try:
session = create_checkout_session()
return jsonify({"checkout_url": session.checkout_url})
except Exception as error:
return jsonify({"error": "Failed to create checkout session"}), 500
```
```javascript expandable theme={null}
// Direct API call using fetch - useful for any JavaScript environment
async function createCheckoutSession() {
try {
const response = await fetch('https://test.dodopayments.com/checkouts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.DODO_PAYMENTS_API_KEY}`
},
body: JSON.stringify({
// Products to sell - use IDs from your Dodo Payments dashboard
product_cart: [
{
product_id: 'prod_123', // Replace with your actual product ID
quantity: 1
}
],
// Pre-fill customer information to reduce checkout friction
customer: {
email: 'customer@example.com',
name: 'John Doe',
phone_number: '+1234567890'
},
// Billing address for tax calculation and compliance
billing_address: {
street: '123 Main St',
city: 'San Francisco',
state: 'CA',
country: 'US', // Required: ISO 3166-1 alpha-2 country code
zipcode: '94102'
},
// Where to redirect after successful payment
return_url: 'https://yoursite.com/checkout/success',
// Custom data for your internal tracking
metadata: {
order_id: 'order_123',
source: 'web_app'
}
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const session = await response.json();
// Redirect your customer to this URL to complete payment
console.log('Checkout URL:', session.checkout_url);
console.log('Session ID:', session.session_id);
return session;
} catch (error) {
console.error('Failed to create checkout session:', error);
throw error;
}
}
// Example: Redirect user to checkout
createCheckoutSession().then(session => {
window.location.href = session.checkout_url;
});
```
### API Response
All methods above return the same response structure:
```json theme={null}
{
"session_id": "cks_Gi6KGJ2zFJo9rq9Ukifwa",
"checkout_url": "https://test.checkout.dodopayments.com/session/cks_Gi6KGJ2zFJo9rq9Ukifwa"
}
```
Extract the `checkout_url` from the API response.
Direct your customer to the checkout URL to complete their purchase.
```javascript theme={null}
// Redirect immediately
window.location.href = session.checkout_url;
// Or open in new window
window.open(session.checkout_url, '_blank');
```
After payment, customers are redirected to your `return_url` with additional query parameters for payment status.
## Request Body
Essential fields needed for every checkout session
Additional configuration to customize your checkout experience
### Required Fields
Array of products to include in the checkout session. Each product must have a valid `product_id` from your Dodo Payments dashboard.
**Important**: Multiple product carts can only contain one-time payment products. You cannot mix subscription products with one-time products in the same checkout session.
The unique identifier of the product from your Dodo Payments dashboard.
**Example:** `"prod_123abc456def"`
Quantity of the product. Must be greater than 0.
**Example:** `1` for single item, `3` for multiple quantities
**Find Your Product IDs**: You can find product IDs in your Dodo Payments dashboard under Products → View Details, or by using the [List Products API](/api-reference/products/get-products).
### Optional Fields
Configure these fields to customize the checkout experience and add business logic to your payment flow.
Pre-fill customer details to reduce checkout friction and improve conversion rates.
Customer's email address. Used for receipts, order confirmations, and customer identification.
**Best Practice**: Always include email when available to ensure customers receive payment confirmations.
Customer's full name as it should appear on receipts and invoices.
**Example**: `"John Doe"` or `"Jane Smith"`
Customer's phone number in international format. Required for some payment methods and fraud prevention.
**Format**: Include country code, e.g., `"+1234567890"` for US numbers
Billing address information for accurate tax calculation, fraud prevention, and regulatory compliance.
When `confirm` is set to `true`, all billing address fields become required for successful session creation.
Complete street address including house number, street name, and apartment/unit number if applicable.
**Example**: `"123 Main St, Apt 4B"` or `"456 Oak Avenue"`
City or municipality name.
**Example**: `"San Francisco"`, `"New York"`, `"London"`
State, province, or region name. Use full names or standard abbreviations.
**Example**: `"California"` or `"CA"`, `"Ontario"` or `"ON"`
Two-letter ISO country code (ISO 3166-1 alpha-2). This field is always required when billing\_address is provided.
**Examples**: `"US"` (United States), `"CA"` (Canada), `"GB"` (United Kingdom), `"DE"` (Germany)
View complete list of supported countries and their ISO codes
Postal code, ZIP code, or equivalent based on country requirements.
**Examples**: `"94102"` (US), `"M5V 3A8"` (Canada), `"SW1A 1AA"` (UK)
Control which payment methods are available to customers during checkout. This helps optimize for specific markets or business requirements.
**Available Options**: `credit`, `debit`, `upi_collect`, `upi_intent`, `apple_pay`, `google_pay`, `amazon_pay`, `klarna`, `affirm`, `afterpay_clearpay`, `sepa`, `ach`
**Critical**: Always include `credit` and `debit` as fallback options to prevent checkout failures when preferred payment methods are unavailable.
**Example**:
```json expandable theme={null}
["apple_pay", "google_pay", "credit", "debit"]
```
Override the default currency selection with a fixed billing currency. Uses ISO 4217 currency codes.
**Supported Currencies**: `USD`, `EUR`, `GBP`, `CAD`, `AUD`, `INR`, and more
**Example**: `"USD"` for US Dollars, `"EUR"` for Euros
This field is only effective when adaptive pricing is enabled. If adaptive pricing is disabled, the product's default currency will be used.
Display previously saved payment methods for returning customers, improving checkout speed and user experience.
URL to redirect customers after successful payment or cancellation.
If true, finalizes all session details immediately. API will throw an error if required data is missing.
Apply a discount code to the checkout session.
Custom key-value pairs to store additional information about the session.
Customize the appearance and behavior of the checkout interface.
Theme for the checkout interface. Options: `light`, `dark`, or `system`.
Display order details section in the checkout interface.
Show the "on-demand" tag for applicable products.
Configure specific features and behaviors for the checkout session.
Allow customers to select their preferred currency during checkout.
Show discount code input field in the checkout interface.
Collect customer phone numbers during checkout.
Allow customers to enter tax identification numbers.
Force creation of a new customer record instead of updating existing ones.
Additional configuration for checkout sessions containing subscription products.
Number of days for the trial period before the first charge.
## Usage Examples
Here are 10 comprehensive examples showcasing different checkout session configurations for various business scenarios:
### 1. Simple Single Product Checkout
```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{
product_id: 'prod_ebook_guide',
quantity: 1
}
],
customer: {
email: 'customer@example.com',
name: 'John Doe'
},
return_url: 'https://yoursite.com/success'
});
```
### 2. Multi-Product Cart
```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{
product_id: 'prod_laptop',
quantity: 1
},
{
product_id: 'prod_mouse',
quantity: 2
},
{
product_id: 'prod_warranty',
quantity: 1
}
],
customer: {
email: 'customer@example.com',
name: 'John Doe',
phone_number: '+1234567890'
},
billing_address: {
street: '123 Tech Street',
city: 'San Francisco',
state: 'CA',
country: 'US',
zipcode: '94102'
},
return_url: 'https://electronics-store.com/order-confirmation'
});
```
### 3. Subscription with Trial Period
```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{
product_id: 'prod_monthly_plan',
quantity: 1
}
],
subscription_data: {
trial_period_days: 14
},
customer: {
email: 'user@startup.com',
name: 'Jane Smith'
},
return_url: 'https://saas-app.com/onboarding',
metadata: {
plan_type: 'professional',
referral_source: 'google_ads'
}
});
```
### 4. Pre-confirmed Checkout
When `confirm` is set to `true`, the customer will be taken directly to the checkout page, bypassing any confirmation steps.
```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{
product_id: 'prod_premium_course',
quantity: 1
}
],
customer: {
email: 'student@university.edu',
name: 'Alex Johnson',
phone_number: '+1555123456'
},
billing_address: {
street: '456 University Ave',
city: 'Boston',
state: 'MA',
country: 'US',
zipcode: '02134'
},
confirm: true,
return_url: 'https://learning-platform.com/course-access',
metadata: {
course_category: 'programming',
enrollment_date: '2024-01-15'
}
});
```
### 5. Checkout with Currency Override
The `billing_currency` override only takes effect when adaptive currency is enabled in your account settings. If adaptive currency is disabled, this parameter will have no effect.
```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{
product_id: 'prod_consulting_service',
quantity: 1
}
],
customer: {
email: 'client@company.co.uk',
name: 'Oliver Williams'
},
billing_address: {
street: '789 Business Park',
city: 'London',
state: 'England',
country: 'GB',
zipcode: 'SW1A 1AA'
},
billing_currency: 'GBP',
feature_flags: {
allow_currency_selection: true,
allow_tax_id: true
},
return_url: 'https://consulting-firm.com/service-confirmation'
});
```
### 6. Saved Payment Methods for Returning Customers
```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{
product_id: 'prod_monthly_subscription',
quantity: 1
}
],
customer: {
email: 'returning.customer@example.com',
name: 'Robert Johnson',
phone_number: '+1555987654'
},
show_saved_payment_methods: true,
feature_flags: {
allow_phone_number_collection: true,
always_create_new_customer: false
},
return_url: 'https://subscription-service.com/welcome-back',
metadata: {
customer_tier: 'premium',
account_age: 'returning'
}
});
```
### 7. B2B Checkout with Tax ID Collection
```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{
product_id: 'prod_enterprise_license',
quantity: 5
}
],
customer: {
email: 'procurement@enterprise.com',
name: 'Sarah Davis',
phone_number: '+1800555000'
},
billing_address: {
street: '1000 Corporate Blvd',
city: 'New York',
state: 'NY',
country: 'US',
zipcode: '10001'
},
feature_flags: {
allow_tax_id: true,
allow_phone_number_collection: true,
always_create_new_customer: false
},
return_url: 'https://enterprise-software.com/license-delivery',
metadata: {
customer_type: 'enterprise',
contract_id: 'ENT-2024-001',
sales_rep: 'john.sales@company.com'
}
});
```
### 8. Dark Theme Checkout with Discount Code
```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{
product_id: 'prod_gaming_chair',
quantity: 1
}
],
discount_code: 'BLACKFRIDAY2024',
customization: {
theme: 'dark',
show_order_details: true,
show_on_demand_tag: false
},
feature_flags: {
allow_discount_code: true
},
customer: {
email: 'gamer@example.com',
name: 'Mike Chen'
},
return_url: 'https://gaming-store.com/order-complete'
});
```
### 9. Regional Payment Methods (UPI for India)
```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{
product_id: 'prod_online_course_hindi',
quantity: 1
}
],
allowed_payment_method_types: [
'upi_collect',
'upi_intent',
'credit',
'debit'
],
customer: {
email: 'student@example.in',
name: 'Priya Sharma',
phone_number: '+919876543210'
},
billing_address: {
street: 'MG Road',
city: 'Bangalore',
state: 'Karnataka',
country: 'IN',
zipcode: '560001'
},
billing_currency: 'INR',
return_url: 'https://education-platform.in/course-access',
metadata: {
region: 'south_india',
language: 'hindi'
}
});
```
### 10. BNPL (Buy Now Pay Later) Checkout
```javascript expandable theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{
product_id: 'prod_luxury_watch',
quantity: 1
}
],
allowed_payment_method_types: [
'klarna',
'affirm',
'afterpay_clearpay',
'credit',
'debit'
],
customer: {
email: 'fashion.lover@example.com',
name: 'Emma Thompson',
phone_number: '+1234567890'
},
billing_address: {
street: '555 Fashion Ave',
city: 'Los Angeles',
state: 'CA',
country: 'US',
zipcode: '90210'
},
feature_flags: {
allow_phone_number_collection: true
},
return_url: 'https://luxury-store.com/purchase-confirmation',
metadata: {
product_category: 'luxury',
price_range: 'high_value'
}
});
```
## Moving from Dynamic Links to Checkout Sessions
### Key Differences
Previously, when creating a payment link with Dynamic Links, you were required to provide the customer's complete billing address.
With Checkout Sessions, this is no longer necessary. You can simply pass along whatever information you have, and we'll handle the rest. For example:
* If you only know the customer's billing country, just provide that.
* The checkout flow will automatically collect the missing details before moving the customer to the payment page.
* On the other hand, if you already have all the required information and want to skip directly to the payment page, you can pass the full data set and include `confirm=true` in your request body.
### Migration Process
Migrating from Dynamic Links to Checkout Sessions is straightforward:
Update your integration to use the new API or SDK method.
Adjust the request payload according to the Checkout Sessions format.
Yes. No additional handling or special migration steps are needed on your side.
# Convex Component
Source: https://docs.dodopayments.com/developer-resources/convex-component
Learn how to integrate Dodo Payments with your Convex backend using our Convex Component. Covers checkout functions, customer portal, webhooks, and secure environment setup.
Integrate Dodo Payments checkout with session-based flow.
Allow customers to manage subscriptions and details.
Receive and process Dodo Payments webhook events.
## Installation
Run the following command in your project root:
```bash theme={null}
npm install @dodopayments/convex
```
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 editing `convex.config.ts`, run `npx convex dev` once to generate the necessary types.
Set up environment variables in your Convex dashboard (**Settings** → **Environment Variables**). You can access the dashboard by running:
```bash theme={null}
npx convex dashboard
```
Add the following 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)
Always use Convex environment variables for sensitive information. Never commit secrets to version control.
## Component Setup Examples
First, create an internal query to fetch users from your database. This will be used in the payment functions to identify customers.
Before using this query, make sure to define the appropriate schema in your `convex/schema.ts` file.
```typescript theme={null}
// convex/users.ts
import { internalQuery } from "./_generated/server";
import { v } from "convex/values";
// Internal query to fetch user by auth ID
export const getByAuthId = internalQuery({
args: { authId: v.string() },
handler: async (ctx, { authId }) => {
return await ctx.db
.query("users")
.withIndex("by_auth_id", (q) => q.eq("authId", authId))
.first();
},
});
```
```typescript Convex Component Setup expandable theme={null}
// convex/dodo.ts
import { DodoPayments } 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 user from your database
const user = await ctx.runQuery(internal.users.getByAuthId, {
authId: identity.subject,
});
if (!user) {
return null; // User not found in database
}
return {
dodoCustomerId: user.dodoCustomerId, // Replace user.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",
});
// Export the API methods for use in your app
export const { checkout, customerPortal } = dodo.api();
```
Use this function to integrate Dodo Payments checkout into your Convex app. Uses session-based checkout with full feature support.
```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.");
}
},
});
```
Use this function to allow customers to manage their subscriptions and details via the Dodo Payments customer portal. The customer is automatically identified via the `identify` function.
```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.");
}
},
});
```
Use this handler to receive and process Dodo Payments webhook events securely in your Convex app. All webhook handlers receive the Convex `ActionCtx` as the first parameter, allowing you to use `ctx.runQuery()` and `ctx.runMutation()` to interact with your database.
```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;
```
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.
## Checkout Function
The Dodo Payments Convex component uses session-based checkout, providing a secure, customizable checkout experience with pre-configured product carts and customer details. This is the recommended approach for all payment flows.
### Usage
```typescript theme={null}
const result = await checkout(ctx, {
payload: {
product_cart: [{ product_id: "prod_123", quantity: 1 }],
customer: { email: "user@example.com" },
return_url: "https://example.com/success"
}
});
```
Refer [Checkout Sessions](/developer-resources/checkout-session) for more details and a complete list of supported fields.
### Response Format
The checkout function returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/session/..."
}
```
## Customer Portal Function
The Customer Portal Function enables you to seamlessly integrate the Dodo Payments customer portal into your Convex application.
### Usage
```typescript theme={null}
const result = await customerPortal(ctx, {
send_email: false
});
```
### Parameters
If set to true, sends an email to the customer with the portal link.
The customer is automatically identified using the `identify` function configured in your DodoPayments setup. This function should return the customer's `dodoCustomerId`.
## Webhook Handler
* **Method:** Only POST requests are supported. Other methods return 405.
* **Signature Verification:** Verifies the webhook signature using DODO\_PAYMENTS\_WEBHOOK\_SECRET. 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 the appropriate event handler based on the payload type.
### Supported Webhook Event Handlers
```typescript Typescript expandable theme={null}
onPayload?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onPaymentSucceeded?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onPaymentFailed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onPaymentProcessing?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onPaymentCancelled?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onRefundSucceeded?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onRefundFailed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onDisputeOpened?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onDisputeExpired?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onDisputeAccepted?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onDisputeCancelled?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onDisputeChallenged?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onDisputeWon?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onDisputeLost?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onSubscriptionActive?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onSubscriptionOnHold?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onSubscriptionRenewed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onSubscriptionPlanChanged?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onSubscriptionCancelled?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onSubscriptionFailed?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onSubscriptionExpired?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
onLicenseKeyCreated?: (ctx: GenericActionCtx, payload: WebhookPayload) => Promise;
```
## Frontend Usage
Use the checkout function from your React components with Convex hooks.
```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: "prod_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 ;
}
```
```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 ;
}
```
***
## Prompt for LLM
```
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 users from your database.
// convex/users.ts
import { internalQuery } from "./_generated/server";
import { v } from "convex/values";
// Internal query to fetch user by auth ID
export const getByAuthId = internalQuery({
args: { authId: v.string() },
handler: async (ctx, { authId }) => {
return await ctx.db
.query("users")
.withIndex("by_auth_id", (q) => q.eq("authId", authId))
.first();
},
});
Step 4: Create your payment functions file.
// convex/dodo.ts
import { DodoPayments } 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 user from your database
const user = await ctx.runQuery(internal.users.getByAuthId, {
authId: identity.subject,
});
if (!user) {
return null; // User not found in database
}
return {
dodoCustomerId: user.dodoCustomerId, // Replace user.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",
});
// 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: "prod_123", quantity: 1 }],
});
window.location.href = checkout_url;
};
return ;
}
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 ;
}
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. You can open the dashboard by running:
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.
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:///dodopayments-webhook`.
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.
```
# Official SDKs Overview
Source: https://docs.dodopayments.com/developer-resources/dodo-payments-sdks
Official SDKs for TypeScript, Python, PHP, Go, Ruby, Java, Kotlin, and C# to integrate Dodo Payments into your applications
Dodo Payments provides official SDKs for multiple programming languages, each designed with language-specific best practices and modern features for seamless payment integration.
## Available SDKs
Choose the SDK that matches your tech stack:
Type-safe integration for TypeScript and Node.js with promise-based API and auto-pagination
Pythonic interface with async/await support for Python 3.7+ applications
PSR-4 compliant SDK for modern PHP 8+ applications
Idiomatic Go interface with context support and strong typing
Elegant Ruby interface following Ruby conventions and best practices
Robust and thread-safe SDK for Java 8+ with Maven and Gradle support
Modern Kotlin SDK with coroutines, null safety, and extension functions
Type-safe SDK for .NET 8+ with async Task-based API (Beta)
Native mobile SDK for building secure payment experiences in iOS and Android apps
Command-line interface for interacting with the API from your terminal
## Quick Start
Get started with any SDK in minutes:
Use your language's package manager to install the SDK
```bash theme={null}
npm install dodopayments
```
```bash theme={null}
pip install dodopayments
```
```bash theme={null}
composer require dodopayments/client
```
```bash theme={null}
go get github.com/dodopayments/dodopayments-go
```
Configure the client with your API key
```typescript theme={null}
import DodoPayments from 'dodopayments';
const client = new DodoPayments({ bearerToken: 'your_api_key' });
```
```python theme={null}
from dodopayments import DodoPayments
client = DodoPayments(bearer_token="your_api_key")
```
```php theme={null}
use Dodopayments\Client;
$client = new Client(bearerToken: 'your_api_key');
```
```go theme={null}
import "github.com/dodopayments/dodopayments-go"
client := dodopayments.NewClient(option.WithBearerToken("your_api_key"))
```
Always store your API keys securely using environment variables. Never commit them to version control.
Create a checkout session or payment
You're now ready to process payments! Visit the individual SDK pages for detailed guides and examples.
## Key Features
All SDKs share these core capabilities:
* **Type Safety**: Strong typing for compile-time safety and better IDE support
* **Error Handling**: Comprehensive exception handling with detailed error messages
* **Authentication**: Simple API key authentication with environment variable support
* **Async Support**: Modern async/await patterns where applicable
* **Auto-Pagination**: Automatic pagination for list responses
* **Usage-Based Billing**: Built-in support for tracking and ingesting usage events
* **Testing**: Full sandbox environment support for development and testing
## React Native SDK
For mobile applications, we provide a dedicated React Native SDK:
Build secure payment experiences for iOS and Android apps with native UI components and simplified payment data collection
**Features:**
* Native UI components for Android and iOS
* Simplified security for collecting sensitive payment data
* Support for multiple payment methods
* Seamless integration with React Native apps
Apple Pay, Google Pay, Cash App, and UPI are not currently supported in the React Native SDK. Support for these payment methods is planned for future releases.
## Command-Line Interface
For terminal-based workflows and automation:
Auto-generated command-line interface with support for all 78 API endpoints
**Features:**
* Resource-based command structure for intuitive usage
* Multiple output formats (JSON, YAML, pretty, interactive)
* Shell completion for bash, zsh, and fish
* Perfect for scripting and CI/CD automation
```bash theme={null}
# Quick example
dodopayments payments list --format json | jq '.data[] | {id, amount}'
```
## Migration from Node.js SDK
We migrated from the Node.js SDK to the new TypeScript SDK. If you're using the legacy Node.js SDK, see the [migration guide](https://github.com/dodopayments/dodopayments-typescript/blob/main/MIGRATION.md) to update your integration.
## Framework Adapters
Integrate in under 10 lines of code with our framework adapters. Choose from our recommended frameworks or explore all supported options.
### Recommended Frameworks
React-based full-stack framework with App Router support
Authentication framework with built-in integrations
Open source Firebase alternative with Postgres and Auth
Backend-as-a-Service with real-time capabilities
## Getting Help
Need assistance with any SDK?
* **Discord**: Join our [community server](https://discord.gg/bYqAp4ayYh) for real-time help
* **Email**: Contact us at [support@dodopayments.com](mailto:support@dodopayments.com)
* **GitHub**: Open an issue on the respective SDK repository
* **Documentation**: Visit our [API reference](/api-reference/introduction)
## Contributing
We welcome contributions to all our SDKs! Each repository has a `CONTRIBUTING.md` file with guidelines for:
* Reporting bugs
* Requesting features
* Submitting pull requests
* Running tests locally
* Code style and conventions
Visit the individual SDK pages to access their GitHub repositories and contribution guidelines.
# Express Adaptor
Source: https://docs.dodopayments.com/developer-resources/express-adaptor
Learn how to integrate Dodo Payments with your Express App Router project using our Express Adaptor. Covers checkout, customer portal, webhooks, and secure environment setup.
Integrate Dodo Payments checkout into your Express app.
Allow customers to manage subscriptions and details.
Receive and process Dodo Payments webhook events.
## Installation
Run the following command in your project root:
```bash theme={null}
npm install @dodopayments/express
```
Create a .env file in your project root:
```env expandable theme={null}
DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode"
DODO_PAYMENTS_RETURN_URL=your-return-url
```
Never commit your .env file or secrets to version control.
## Route Handler Examples
Use this handler to integrate Dodo Payments checkout into your Express app. Supports static (GET), dynamic (POST), and session (POST) payment flows.
```typescript Express Route Handler expandable theme={null}
import { checkoutHandler } from '@dodopayments/express';
app.get('/api/checkout', checkoutHandler({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "static"
}))
app.post('/api/checkout', checkoutHandler({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "dynamic"
}))
app.post('/api/checkout', checkoutHandler({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "session"
}))
```
```curl Static Checkout Curl example expandable theme={null}
curl --request GET \
--url 'https://example.com/api/checkout?productId=pdt_fqJhl7pxKWiLhwQR042rh' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test
```
```curl Dynamic Checkout Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"billing": {
"city": "Texas",
"country": "US",
"state": "Texas",
"street": "56, hhh",
"zipcode": "560000"
},
"customer": {
"email": "test@example.com",
"name": "test"
},
"metadata": {},
"payment_link": true,
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1,
"billing_currency": "USD",
"discount_code": "IKHZ23M9GQ",
"return_url": "https://example.com",
"trial_period_days": 10
}'
```
```curl Checkout Session Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"product_cart": [
{
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1
}
],
"customer": {
"email": "test@example.com",
"name": "test"
},
"return_url": "https://example.com/success"
}'
```
Use this handler to allow customers to manage their subscriptions and details via the Dodo Payments customer portal.
```typescript Express Route Handler expandable theme={null}
import { CustomerPortal } from "@dodopayments/express";
app.get('/api/customer-portal', CustomerPortal({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
}))
```
```curl Customer Portal Curl example expandable theme={null}
curl --request GET \
--url 'https://example.com/api/customer-portal?customer_id=cus_9VuW4K7O3GHwasENg31m&send_email=true' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test
```
Use this handler to receive and process Dodo Payments webhook events securely in your Express app.
```typescript Express Route Handler expandable theme={null}
import { Webhooks } from "@dodopayments/express";
app.post('/api/webhook',Webhooks({
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
onPayload: async (payload) => {
// handle the payload
},
// ... other event handlers for granular control
}))
```
## Checkout Route Handler
Dodo Payments supports three types of payment flows for integrating payments into your website, this adaptor supports all types of payment flows.
* **Static Payment Links:** Instantly shareable URLs for quick, no-code payment collection.
* **Dynamic Payment Links:** Programmatically generate payment links with custom details using the API or SDKs.
* **Checkout Sessions:** Create secure, customizable checkout experiences with pre-configured product carts and customer details.
### Supported Query Parameters
Product identifier (e.g., ?productId=pdt\_nZuwz45WAs64n3l07zpQR).
Quantity of the product.
Customer's full name.
Customer's first name.
Customer's last name.
Customer's email address.
Customer's country.
Customer's address line.
Customer's city.
Customer's state/province.
Customer's zip/postal code.
Disable full name field.
Disable first name field.
Disable last name field.
Disable email field.
Disable country field.
Disable address line field.
Disable city field.
Disable state field.
Disable zip code field.
Specify the payment currency (e.g., USD).
Show currency selector.
Specify the payment amount (e.g., 1000 for \$10.00).
Show discount fields.
Any query parameter starting with metadata\_ will be passed as metadata.
If productId is missing, the handler returns a 400 response. Invalid query parameters also result in a 400 response.
### Response Format
Static checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/..."
}
```
* Send parameters as a JSON body in a POST request.
* Supports both one-time and recurring payments.
* For a complete list of supported POST body fields, refer to:
* [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)
### Response Format
Dynamic checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/..."
}
```
Checkout sessions provide a more secure, hosted checkout experience that handles the complete payment flow for both one-time purchases and subscriptions with full customization control.
Refer to [Checkout Sessions Integration Guide](https://docs.dodopayments.com/developer-resources/checkout-session) for more details and a complete list of supported fields.
### Response Format
Checkout sessions return a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/session/..."
}
```
## Customer Portal Route Handler
The Customer Portal Route Handler enables you to seamlessly integrate the Dodo Payments customer portal into your Express application.
### Query Parameters
The customer ID for the portal session (e.g., ?customer\_id=cus\_123).
If set to true, sends an email to the customer with the portal link.
Returns 400 if customer\_id is missing.
## Webhook Route Handler
* **Method:** Only POST requests are supported. Other methods return 405.
* **Signature Verification:** Verifies the webhook signature using webhookKey. 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 the appropriate event handler based on the payload type.
### Supported Webhook Event Handlers
```typescript Typescript expandable theme={null}
onPayload?: (payload: WebhookPayload) => Promise;
onPaymentSucceeded?: (payload: WebhookPayload) => Promise;
onPaymentFailed?: (payload: WebhookPayload) => Promise;
onPaymentProcessing?: (payload: WebhookPayload) => Promise;
onPaymentCancelled?: (payload: WebhookPayload) => Promise;
onRefundSucceeded?: (payload: WebhookPayload) => Promise;
onRefundFailed?: (payload: WebhookPayload) => Promise;
onDisputeOpened?: (payload: WebhookPayload) => Promise;
onDisputeExpired?: (payload: WebhookPayload) => Promise;
onDisputeAccepted?: (payload: WebhookPayload) => Promise;
onDisputeCancelled?: (payload: WebhookPayload) => Promise;
onDisputeChallenged?: (payload: WebhookPayload) => Promise;
onDisputeWon?: (payload: WebhookPayload) => Promise;
onDisputeLost?: (payload: WebhookPayload) => Promise;
onSubscriptionActive?: (payload: WebhookPayload) => Promise;
onSubscriptionOnHold?: (payload: WebhookPayload) => Promise;
onSubscriptionRenewed?: (payload: WebhookPayload) => Promise;
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise;
onSubscriptionCancelled?: (payload: WebhookPayload) => Promise;
onSubscriptionFailed?: (payload: WebhookPayload) => Promise;
onSubscriptionExpired?: (payload: WebhookPayload) => Promise;
onLicenseKeyCreated?: (payload: WebhookPayload) => Promise;
```
***
## Prompt for LLM
```
You are an expert Express.js developer assistant. Your task is to guide a user through integrating the @dodopayments/express adapter into their existing Express.js project.
The @dodopayments/express adapter provides route handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, designed to plug directly into an Express app.
First, install the necessary package. Use the package manager appropriate for the user's project (npm, yarn, or bun):
npm install @dodopayments/express
---
Here's how you should structure your response:
1. Ask the user which functionalities they want to integrate.
"Which parts of the @dodopayments/express 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)"
---
2. 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.
**Integration**:
Create routes in your Express app for static (GET), dynamic (POST), and checkout sessions (POST).
import { checkoutHandler } from '@dodopayments/express';
app.get('/api/checkout', checkoutHandler({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "static"
}));
app.post('/api/checkout', checkoutHandler({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "dynamic"
}));
// For checkout sessions
app.post('/api/checkout', checkoutHandler({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "session"
}));
Config Options:
bearerToken: Your Dodo Payments API key (recommended to be stored in DODO_PAYMENTS_API_KEY env variable).
returnUrl (optional): URL to redirect the user after successful checkout.
environment: "test_mode" or "live_mode"
type: "static" (GET), "dynamic" (POST), or "session" (POST)
GET (static checkout) expects query parameters:
productId (required)
quantity, customer fields (fullName, email, etc.), and metadata (metadata_*) are optional.
Returns: {"checkout_url": "https://checkout.dodopayments.com/..."}
POST (dynamic checkout) expects a JSON body with payment details (one-time or subscription). Reference the docs for the full POST schema:
One-time payments: https://docs.dodopayments.com/api-reference/payments/post-payments
Subscriptions: https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions
Returns: {"checkout_url": "https://checkout.dodopayments.com/..."}
POST (checkout sessions) - (Recommended) A more customizable checkout experience. Returns JSON with checkout_url: Parameters are sent as a JSON body. 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
If Customer Portal Route Handler is selected:
Purpose: This route allows customers to manage their subscriptions via the Dodo Payments portal.
Integration:
import { CustomerPortal } from "@dodopayments/express";
app.get('/api/customer-portal', CustomerPortal({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
}));
Query Parameters:
customer_id (required): e.g., ?customer_id=cus_123
send_email (optional): if true, customer is emailed the portal link
Returns 400 if customer_id is missing.
If Webhook Route Handler is selected:
Purpose: Processes incoming webhook events from Dodo Payments to trigger events in your app.
Integration:
import { Webhooks } from "@dodopayments/express";
app.post('/api/webhook', Webhooks({
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
onPayload: async (payload) => {
// Handle generic payload
},
// You can also provide fine-grained handlers for each event type below
}));
Features:
Only POST method is allowed — others return 405
Signature verification is performed using webhookKey. Returns 401 if invalid.
Zod-based payload validation. Returns 400 if invalid schema.
All handlers are async functions.
Supported Webhook Event Handlers:
You may pass in any of the following handlers:
onPayload
onPaymentSucceeded
onPaymentFailed
onPaymentProcessing
onPaymentCancelled
onRefundSucceeded
onRefundFailed
onDisputeOpened, onDisputeExpired, onDisputeAccepted, onDisputeCancelled, onDisputeChallenged, onDisputeWon, onDisputeLost
onSubscriptionActive, onSubscriptionOnHold, onSubscriptionRenewed, onSubscriptionPlanChanged, onSubscriptionCancelled, onSubscriptionFailed, onSubscriptionExpired
onLicenseKeyCreated
Environment Variable Setup:
Make sure to define these environment variables in your project:
DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode"
DODO_PAYMENTS_RETURN_URL=your-return-url
Use these inside your code as:
process.env.DODO_PAYMENTS_API_KEY
process.env.DODO_PAYMENTS_WEBHOOK_SECRET
Security Note: Do NOT commit secrets to version control. Use .env files locally and secrets managers in deployment environments (e.g., AWS, Vercel, Heroku, etc.).
```
# Fastify Adaptor
Source: https://docs.dodopayments.com/developer-resources/fastify-adaptor
Learn how to integrate Dodo Payments with your Fastify App Router project using our NextJS Adaptor. Covers checkout, customer portal, webhooks, and secure environment setup.
Integrate Dodo Payments checkout into your Fastify app.
Allow customers to manage subscriptions and details.
Receive and process Dodo Payments webhook events.
## Installation
Run the following command in your project root:
```bash theme={null}
npm install @dodopayments/fastify
```
Create a .env file in your project root:
```env expandable theme={null}
DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_RETURN_URL=https://yourapp.com/success
DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode""
```
Never commit your .env file or secrets to version control.
## Route Handler Examples
All examples assume you are using the Fastify App Router.
Use this handler to integrate Dodo Payments checkout into your Fastify app. Supports static (GET), dynamic (POST), and session (POST) payment flows.
```typescript Fastify Route Handler expandable theme={null}
// route.ts
import { Checkout } from '@dodopayments/fastify';
import Fastify from 'fastify'
const fastify = Fastify({})
const checkoutGet = Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
type: 'static'
});
const checkoutPost = Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
type: 'dynamic'
});
const checkoutSession = Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
type: 'session'
});
fastify.get('/api/checkout', checkoutGet.getHandler);
fastify.post('/api/checkout', checkoutPost.postHandler);
fastify.post('/api/checkout-session', checkoutSession.postHandler);
```
```curl Static Checkout Curl example expandable theme={null}
curl --request GET \
--url 'https://example.com/api/checkout?productId=pdt_fqJhl7pxKWiLhwQR042rh' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test
```
```curl Dynamic Checkout Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"billing": {
"city": "Texas",
"country": "US",
"state": "Texas",
"street": "56, hhh",
"zipcode": "560000"
},
"customer": {
"email": "test@example.com",
"name": "test"
},
"metadata": {},
"payment_link": true,
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1,
"billing_currency": "USD",
"discount_code": "IKHZ23M9GQ",
"return_url": "https://example.com",
"trial_period_days": 10
}'
```
```curl Checkout Session Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout-session \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"product_cart": [
{
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1
}
],
"customer": {
"email": "test@example.com",
"name": "test"
},
"return_url": "https://example.com/success"
}'
```
Use this handler to allow customers to manage their subscriptions and details via the Dodo Payments customer portal.
```typescript Fastify Route Handler expandable theme={null}
// route.ts
import { CustomerPortal } from "@dodopayments/fastify";
import Fastify from 'fastify'
const fastify = Fastify({})
const customerPortalHandler = CustomerPortal({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT
});
fastify.get('/api/customer-portal', customerPortalHandler);
```
```curl Customer Portal Curl example expandable theme={null}
curl --request GET \
--url 'https://example.com/api/customer-portal?customer_id=cus_9VuW4K7O3GHwasENg31m&send_email=true' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test
```
Use this handler to receive and process Dodo Payments webhook events securely in your Fastify app.
```typescript Fastify Route Handler expandable theme={null}
// route.ts
import Fastify from 'fastify'
import { Webhooks } from '@dodopayments/fastify'
const fastify = Fastify({})
fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) {
done(null, body)
})
fastify.post('/api/webhooks', Webhooks({
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
onPayload: async (payload) => {
// Handle Payload Here
console.log(payload)
}
}));
```
## Checkout Route Handler
Dodo Payments supports three types of payment flows for integrating payments into your website, this adaptor supports all types of payment flows.
* **Static Payment Links:** Instantly shareable URLs for quick, no-code payment collection.
* **Dynamic Payment Links:** Programmatically generate payment links with custom details using the API or SDKs.
* **Checkout Sessions:** Create secure, customizable checkout experiences with pre-configured product carts and customer details.
### Supported Query Parameters
Product identifier (e.g., ?productId=pdt\_nZuwz45WAs64n3l07zpQR).
Quantity of the product.
Customer's full name.
Customer's first name.
Customer's last name.
Customer's email address.
Customer's country.
Customer's address line.
Customer's city.
Customer's state/province.
Customer's zip/postal code.
Disable full name field.
Disable first name field.
Disable last name field.
Disable email field.
Disable country field.
Disable address line field.
Disable city field.
Disable state field.
Disable zip code field.
Specify the payment currency (e.g., USD).
Show currency selector.
Specify the payment amount (e.g., 1000 for \$10.00).
Show discount fields.
Any query parameter starting with metadata\_ will be passed as metadata.
If productId is missing, the handler returns a 400 response. Invalid query parameters also result in a 400 response.
### Response Format
Static checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/..."
}
```
* Send parameters as a JSON body in a POST request.
* Supports both one-time and recurring payments.
* For a complete list of supported POST body fields, refer to:
* [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)
### Response Format
Dynamic checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/..."
}
```
Checkout sessions provide a more secure, hosted checkout experiences that handle the complete payment flow for both one-time purchases and subscriptions with full customization control.
Refer to [Checkout Sessions Integration Guide](https://docs.dodopayments.com/developer-resources/checkout-session) for more details and a complete list of supported fields.
### Response Format
Checkout sessions return a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/session/..."
}
```
## Customer Portal Route Handler
The Customer Portal Route Handler enables you to seamlessly integrate the Dodo Payments customer portal into your Fastify application.
### Query Parameters
The customer ID for the portal session (e.g., ?customer\_id=cus\_123).
If set to true, sends an email to the customer with the portal link.
Returns 400 if customer\_id is missing.
## Webhook Route Handler
* **Method:** Only POST requests are supported. Other methods return 405.
* **Signature Verification:** Verifies the webhook signature using webhookKey. 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 the appropriate event handler based on the payload type.
### Supported Webhook Event Handlers
```typescript Typescript expandable theme={null}
onPayload?: (payload: WebhookPayload) => Promise;
onPaymentSucceeded?: (payload: WebhookPayload) => Promise;
onPaymentFailed?: (payload: WebhookPayload) => Promise;
onPaymentProcessing?: (payload: WebhookPayload) => Promise;
onPaymentCancelled?: (payload: WebhookPayload) => Promise;
onRefundSucceeded?: (payload: WebhookPayload) => Promise;
onRefundFailed?: (payload: WebhookPayload) => Promise;
onDisputeOpened?: (payload: WebhookPayload) => Promise;
onDisputeExpired?: (payload: WebhookPayload) => Promise;
onDisputeAccepted?: (payload: WebhookPayload) => Promise;
onDisputeCancelled?: (payload: WebhookPayload) => Promise;
onDisputeChallenged?: (payload: WebhookPayload) => Promise;
onDisputeWon?: (payload: WebhookPayload) => Promise;
onDisputeLost?: (payload: WebhookPayload) => Promise;
onSubscriptionActive?: (payload: WebhookPayload) => Promise;
onSubscriptionOnHold?: (payload: WebhookPayload) => Promise;
onSubscriptionRenewed?: (payload: WebhookPayload) => Promise;
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise;
onSubscriptionCancelled?: (payload: WebhookPayload) => Promise;
onSubscriptionFailed?: (payload: WebhookPayload) => Promise;
onSubscriptionExpired?: (payload: WebhookPayload) => Promise;
onLicenseKeyCreated?: (payload: WebhookPayload) => Promise;
```
***
## Prompt for LLM
```
You are an expert Fastify developer assistant. Your task is to guide a user through integrating the @dodopayments/fastify adapter into their existing Fastify project.
The @dodopayments/fastify adapter provides route handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, designed to plug directly into an Fastify app.
First, install the necessary package. Use the package manager appropriate for the user's project (npm, yarn, or bun):
npm install @dodopayments/fastify
---
Here's how you should structure your response:
1. Ask the user which functionalities they want to integrate.
"Which parts of the @dodopayments/fastify 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)"
---
2. Based on the user's selection, provide detailed integration steps for each chosen functionality.
---
**If Checkout Route Handler is selected:**
**Purpose**: This handler redirects users to the Dodo Payments checkout page.
**Integration**:
Create two routes in your Fastify app — one for static (GET) and one for dynamic (POST) checkout.
import { Checkout } from '@dodopayments/fastify';
import Fastify from 'fastify'
const fastify = Fastify({})
const checkoutGet = Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
type: 'static'
});
const checkoutPost = Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
type: 'dynamic'
});
const checkoutSession = Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
type: 'session'
});
fastify.get('/api/checkout', checkoutGet.getHandler);
fastify.post('/api/checkout', checkoutPost.postHandler);
fastify.post('/api/checkout-session', checkoutSession.postHandler);
Config Options:
bearerToken: Your Dodo Payments API key (recommended to be stored in DODO_PAYMENTS_API_KEY env variable).
returnUrl (optional): URL to redirect the user after successful checkout.
environment: "test_mode" or "live_mode"
type: "static" (GET), "dynamic" (POST), or "session" (POST)
GET (static checkout) expects query parameters:
productId (required)
quantity, customer fields (fullName, email, etc.), and metadata (metadata_*) are optional.
Returns: {"checkout_url": "https://checkout.dodopayments.com/..."}
POST (dynamic checkout) expects a JSON body with payment details (one-time or subscription). Returns: {"checkout_url": "https://checkout.dodopayments.com/..."}. Reference the docs for the full POST schema:
One-time payments: https://docs.dodopayments.com/api-reference/payments/post-payments
Subscriptions: https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions
POST (checkout sessions) - (Recommended) A more customizable checkout experience. Returns JSON with checkout_url: Parameters are sent as a JSON body. 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
If Customer Portal Route Handler is selected:
Purpose: This route allows customers to manage their subscriptions via the Dodo Payments portal.
Integration:
import { CustomerPortal } from "@dodopayments/fastify";
import Fastify from 'fastify'
const fastify = Fastify({})
const customerPortalHandler = CustomerPortal({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT
});
fastify.get('/api/customer-portal', customerPortalHandler);
Query Parameters:
customer_id (required): e.g., ?customer_id=cus_123
send_email (optional): if true, customer is emailed the portal link
Returns 400 if customer_id is missing.
If Webhook Route Handler is selected:
Purpose: Processes incoming webhook events from Dodo Payments to trigger events in your app.
Integration:
import Fastify from 'fastify'
import { Webhooks } from '@dodopayments/fastify'
const fastify = Fastify({})
fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) {
done(null, body)
})
fastify.post('/api/webhooks', Webhooks({
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
onPayload: async (payload) => {
// Handle Payload Here
console.log(payload)
}
}));
Features:
Only POST method is allowed — others return 405
Signature verification is performed using webhookKey. Returns 401 if invalid.
Zod-based payload validation. Returns 400 if invalid schema.
All handlers are async functions.
Supported Webhook Event Handlers:
You may pass in any of the following handlers:
onPayload
onPaymentSucceeded
onPaymentFailed
onPaymentProcessing
onPaymentCancelled
onRefundSucceeded
onRefundFailed
onDisputeOpened, onDisputeExpired, onDisputeAccepted, onDisputeCancelled, onDisputeChallenged, onDisputeWon, onDisputeLost
onSubscriptionActive, onSubscriptionOnHold, onSubscriptionRenewed, onSubscriptionPaused, onSubscriptionPlanChanged, onSubscriptionCancelled, onSubscriptionFailed, onSubscriptionExpired
onLicenseKeyCreated
Environment Variable Setup:
Make sure to define these environment variables in your project:
DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_RETURN_URL=https://yourapp.com/success
DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode""
Use these inside your code as:
process.env.DODO_PAYMENTS_API_KEY
process.env.DODO_PAYMENTS_WEBHOOK_KEY
Security Note: Do NOT commit secrets to version control. Use .env files locally and secrets managers in deployment environments (e.g., AWS, Vercel, Heroku, etc.).
```
# Hono Adaptor
Source: https://docs.dodopayments.com/developer-resources/hono-adaptor
Learn how to integrate Dodo Payments with your Hono App Router project using our NextJS Adaptor. Covers checkout, customer portal, webhooks, and secure environment setup.
Integrate Dodo Payments checkout into your Hono app.
Allow customers to manage subscriptions and details.
Receive and process Dodo Payments webhook events.
## Installation
Run the following command in your project root:
```bash theme={null}
npm install @dodopayments/hono
```
Create a .env file in your project root:
```env expandable theme={null}
DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_RETURN_URL=https://yourapp.com/success
DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode""
```
Never commit your .env file or secrets to version control.
## Route Handler Examples
All examples assume you are using the Hono App Router.
Use this handler to integrate Dodo Payments checkout into your Hono app. Supports static (GET), dynamic (POST), and session (POST) flows.
```typescript Hono Route Handler expandable theme={null}
// route.ts
import { Checkout } from '@dodopayments/hono';
import Hono from 'hono'
const app = new Hono()
app.get(
"/api/checkout",
Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
type: 'static'
})
);
app.post(
"/api/checkout",
Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
type: 'dynamic'
})
);
app.post(
"/api/checkout",
Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
type: 'session'
})
);
```
```curl Static Checkout Curl example expandable theme={null}
curl --request GET \
--url 'https://example.com/api/checkout?productId=pdt_fqJhl7pxKWiLhwQR042rh' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test
```
```curl Dynamic Checkout Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"billing": {
"city": "Texas",
"country": "US",
"state": "Texas",
"street": "56, hhh",
"zipcode": "560000"
},
"customer": {
"email": "test@example.com",
"name": "test"
},
"metadata": {},
"payment_link": true,
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1,
"billing_currency": "USD",
"discount_code": "IKHZ23M9GQ",
"return_url": "https://example.com",
"trial_period_days": 10
}'
```
```curl Checkout Session Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"product_cart": [
{
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1
}
],
"customer": {
"email": "test@example.com",
"name": "test"
},
"return_url": "https://example.com/success"
}'
```
Use this handler to allow customers to manage their subscriptions and details via the Dodo Payments customer portal.
```typescript Hono Route Handler expandable theme={null}
// route.ts
import { Checkout } from '@dodopayments/hono';
import Hono from 'hono'
const app = new Hono()
app.get(
"/api/customer-portal",
CustomerPortal({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT
})
);
```
```curl Customer Portal Curl example expandable theme={null}
curl --request GET \
--url 'https://example.com/api/customer-portal?customer_id=cus_9VuW4K7O3GHwasENg31m&send_email=true' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test
```
Use this handler to receive and process Dodo Payments webhook events securely in your Hono app.
```typescript Hono Route Handler expandable theme={null}
// route.ts
import Hono from 'hono'
import { Webhooks } from '@dodopayments/hono'
const app = new Hono()
app.post(
"/api/webhooks",
Webhooks({
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
onPayload: async (payload) => {
// Handle Payload Here
console.log(payload)
}
})
);
```
## Checkout Route Handler
Dodo Payments supports three types of payment flows for integrating payments into your website, this adaptor supports all types of payment flows.
* **Static Payment Links:** Instantly shareable URLs for quick, no-code payment collection.
* **Dynamic Payment Links:** Programmatically generate payment links with custom details using the API or SDKs.
* **Checkout Sessions:** Create secure, customizable checkout experiences with pre-configured product carts and customer details.
### Supported Query Parameters
Product identifier (e.g., ?productId=pdt\_nZuwz45WAs64n3l07zpQR).
Quantity of the product.
Customer's full name.
Customer's first name.
Customer's last name.
Customer's email address.
Customer's country.
Customer's address line.
Customer's city.
Customer's state/province.
Customer's zip/postal code.
Disable full name field.
Disable first name field.
Disable last name field.
Disable email field.
Disable country field.
Disable address line field.
Disable city field.
Disable state field.
Disable zip code field.
Specify the payment currency (e.g., USD).
Show currency selector.
Specify the payment amount (e.g., 1000 for \$10.00).
Show discount fields.
Any query parameter starting with metadata\_ will be passed as metadata.
If productId is missing, the handler returns a 400 response. Invalid query parameters also result in a 400 response.
### Response Format
Static checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/..."
}
```
* Send parameters as a JSON body in a POST request.
* Supports both one-time and recurring payments.
* For a complete list of supported POST body fields, refer to:
* [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)
### Response Format
Dynamic checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/..."
}
```
Checkout sessions provide a more secure, hosted checkout experience that handles the complete payment flow for both one-time purchases and subscriptions with full customization control.
Refer to [Checkout Sessions Integration Guide](https://docs.dodopayments.com/developer-resources/checkout-session) for more details and a complete list of supported fields.
### Response Format
Checkout sessions return a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/session/..."
}
```
## Customer Portal Route Handler
The Customer Portal Route Handler enables you to seamlessly integrate the Dodo Payments customer portal into your Hono application.
### Query Parameters
The customer ID for the portal session (e.g., ?customer\_id=cus\_123).
If set to true, sends an email to the customer with the portal link.
Returns 400 if customer\_id is missing.
## Webhook Route Handler
* **Method:** Only POST requests are supported. Other methods return 405.
* **Signature Verification:** Verifies the webhook signature using webhookKey. 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 the appropriate event handler based on the payload type.
### Supported Webhook Event Handlers
```typescript Typescript expandable theme={null}
onPayload?: (payload: WebhookPayload) => Promise;
onPaymentSucceeded?: (payload: WebhookPayload) => Promise;
onPaymentFailed?: (payload: WebhookPayload) => Promise;
onPaymentProcessing?: (payload: WebhookPayload) => Promise;
onPaymentCancelled?: (payload: WebhookPayload) => Promise;
onRefundSucceeded?: (payload: WebhookPayload) => Promise;
onRefundFailed?: (payload: WebhookPayload) => Promise;
onDisputeOpened?: (payload: WebhookPayload) => Promise;
onDisputeExpired?: (payload: WebhookPayload) => Promise;
onDisputeAccepted?: (payload: WebhookPayload) => Promise;
onDisputeCancelled?: (payload: WebhookPayload) => Promise;
onDisputeChallenged?: (payload: WebhookPayload) => Promise;
onDisputeWon?: (payload: WebhookPayload) => Promise;
onDisputeLost?: (payload: WebhookPayload) => Promise;
onSubscriptionActive?: (payload: WebhookPayload) => Promise;
onSubscriptionOnHold?: (payload: WebhookPayload) => Promise;
onSubscriptionRenewed?: (payload: WebhookPayload) => Promise;
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise;
onSubscriptionCancelled?: (payload: WebhookPayload) => Promise;
onSubscriptionFailed?: (payload: WebhookPayload) => Promise;
onSubscriptionExpired?: (payload: WebhookPayload) => Promise;
onLicenseKeyCreated?: (payload: WebhookPayload) => Promise;
```
***
## Prompt for LLM
```
You are an expert Hono developer assistant. Your task is to guide a user through integrating the @dodopayments/hono adapter into their existing Hono project.
The @dodopayments/hono adapter provides route handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, designed to plug directly into an Hono app.
First, install the necessary package. Use the package manager appropriate for the user's project (npm, yarn, or bun):
npm install @dodopayments/hono
---
Here's how you should structure your response:
1. Ask the user which functionalities they want to integrate.
"Which parts of the @dodopayments/hono 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)"
---
2. Based on the user's selection, provide detailed integration steps for each chosen functionality.
---
**If Checkout Route Handler is selected:**
**Purpose**: This handler redirects users to the Dodo Payments checkout page.
**Integration**:
Create two routes in your Hono app — one for static (GET) and one for dynamic (POST) checkout.
import { Checkout } from '@dodopayments/hono';
import Hono from 'hono'
const app = new Hono()
app.get(
"/api/checkout",
Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
type: 'static'
})
);
app.post(
"/api/checkout",
Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
type: 'session' // or 'dynamic' for dynamic link
})
);
Config Options:
bearerToken: Your Dodo Payments API key (recommended to be stored in DODO_PAYMENTS_API_KEY env variable).
returnUrl (optional): URL to redirect the user after successful checkout.
environment: "test_mode" or "live_mode"
type: "static" (GET) or "dynamic" (POST) or "session" (POST)
GET (static checkout) expects query parameters:
productId (required)
quantity, customer fields (fullName, email, etc.), and metadata (metadata_*) are optional.
POST (dynamic checkout) expects a JSON body with payment details (one-time or subscription). Reference the docs for the full POST schema:
One-time payments: https://docs.dodopayments.com/api-reference/payments/post-payments
Subscriptions: https://docs.dodopayments.com/api-reference/subscriptions/post-subscriptions
POST (checkout sessions) - (Recommended) A more customizable checkout experience. Returns JSON with checkout_url: Parameters are sent as a JSON body. 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
If Customer Portal Route Handler is selected:
Purpose: This route allows customers to manage their subscriptions via the Dodo Payments portal.
Integration:
import { Checkout } from '@dodopayments/hono';
import Hono from 'hono'
const app = new Hono()
app.get(
"/api/customer-portal",
CustomerPortal({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT
})
);
Query Parameters:
customer_id (required): e.g., ?customer_id=cus_123
send_email (optional): if true, customer is emailed the portal link
Returns 400 if customer_id is missing.
If Webhook Route Handler is selected:
Purpose: Processes incoming webhook events from Dodo Payments to trigger events in your app.
Integration:
import Hono from 'hono'
import { Webhooks } from '@dodopayments/hono'
const app = new Hono()
app.post(
"/api/webhooks",
Webhooks({
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
onPayload: async (payload) => {
// Handle Payload Here
console.log(payload)
}
})
);
Features:
Only POST method is allowed — others return 405
Signature verification is performed using webhookKey. Returns 401 if invalid.
Zod-based payload validation. Returns 400 if invalid schema.
All handlers are async functions.
Supported Webhook Event Handlers:
You may pass in any of the following handlers:
onPayload
onPaymentSucceeded
onPaymentFailed
onPaymentProcessing
onPaymentCancelled
onRefundSucceeded
onRefundFailed
onDisputeOpened, onDisputeExpired, onDisputeAccepted, onDisputeCancelled, onDisputeChallenged, onDisputeWon, onDisputeLost
onSubscriptionActive, onSubscriptionOnHold, onSubscriptionRenewed, onSubscriptionPaused, onSubscriptionPlanChanged, onSubscriptionCancelled, onSubscriptionFailed, onSubscriptionExpired
onLicenseKeyCreated
Environment Variable Setup:
Make sure to define these environment variables in your project:
DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_RETURN_URL=https://yourapp.com/success
DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode""
Use these inside your code as:
process.env.DODO_PAYMENTS_API_KEY
process.env.DODO_PAYMENTS_WEBHOOK_KEY
Security Note: Do NOT commit secrets to version control. Use .env files locally and secrets managers in deployment environments (e.g., AWS, Vercel, Heroku, etc.).
```
# API Gateway Blueprint
Source: https://docs.dodopayments.com/developer-resources/ingestion-blueprints/api-gateway
Track API calls and gateway-level usage for billing. Perfect for API-as-a-service platforms with high-volume request tracking.
## Use Cases
Explore common scenarios supported by the API Gateway Blueprint:
Track usage per customer for API platforms and charge based on number of calls.
Monitor API usage patterns and implement usage-based rate limiting.
Track response times and error rates alongside billing data.
Bill customers based on their API consumption across different endpoints.
Ideal for tracking API endpoint usage, rate limiting, and implementing usage-based API billing.
## Quick Start
Track API calls at the gateway level with automatic batching for high-volume scenarios:
```bash theme={null}
npm install @dodopayments/ingestion-blueprints
```
* **Dodo Payments API Key**: Get it from [Dodo Payments Dashboard](https://app.dodopayments.com/developer/api-keys)
Create a meter in your [Dodo Payments Dashboard](https://app.dodopayments.com/):
* **Event Name**: `api_call` (or your preferred name)
* **Aggregation Type**: `count` for tracking number of calls
* Configure additional properties if tracking metadata like response times, status codes, etc.
```javascript Single API Call theme={null}
import { Ingestion, trackAPICall } from '@dodopayments/ingestion-blueprints';
const ingestion = new Ingestion({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'api_call'
});
// Track a single API call
await trackAPICall(ingestion, {
customerId: 'customer_123',
metadata: {
endpoint: '/api/v1/users',
method: 'GET',
status_code: 200,
response_time_ms: 45
}
});
```
```javascript High-Volume with Batching theme={null}
import { Ingestion, createBatch } from '@dodopayments/ingestion-blueprints';
const ingestion = new Ingestion({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'live_mode',
eventName: 'api_call'
});
// Create batch for high-volume tracking
const batch = createBatch(ingestion, {
maxSize: 100, // Flush after 100 events
flushInterval: 5000 // Or flush every 5 seconds
});
// Add API calls to batch
batch.add({
customerId: 'customer_123',
metadata: {
endpoint: '/api/v1/products',
method: 'GET',
status_code: 200
}
});
// Clean up when done
await batch.cleanup();
```
```javascript Express.js Middleware theme={null}
import express from 'express';
import { Ingestion, createBatch } from '@dodopayments/ingestion-blueprints';
const app = express();
const ingestion = new Ingestion({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'live_mode',
eventName: 'api_call'
});
const batch = createBatch(ingestion, {
maxSize: 50,
flushInterval: 10000
});
// Middleware to track all API calls
app.use((req, res, next) => {
const startTime = Date.now();
res.on('finish', () => {
const responseTime = Date.now() - startTime;
batch.add({
customerId: req.user?.id || 'anonymous',
metadata: {
endpoint: req.path,
method: req.method,
status_code: res.statusCode,
response_time_ms: responseTime
}
});
});
next();
});
// Cleanup on shutdown
process.on('SIGTERM', async () => {
await batch.cleanup();
process.exit(0);
});
```
## Configuration
### Ingestion Configuration
Your Dodo Payments API key from the dashboard.
Environment mode: `test_mode` or `live_mode`.
Event name that matches your meter configuration.
### Track API Call Options
The customer ID for billing attribution.
Optional metadata about the API call like endpoint, method, status code, response time, etc.
### Batch Configuration
Maximum number of events before auto-flush. Default: `100`.
Auto-flush interval in milliseconds. Default: `5000` (5 seconds).
## Best Practices
**Use Batching for High Volume**: For applications handling more than 10 requests per second, use `createBatch()` to reduce overhead and improve performance.
**Always Clean Up Batches**: Call `batch.cleanup()` on application shutdown to flush pending events and prevent data loss.
# LLM Blueprint
Source: https://docs.dodopayments.com/developer-resources/ingestion-blueprints/llm
Effortlessly track LLM token usage for usage-based billing with automatic ingestion to Dodo Payments. Works with AI SDK, OpenAI, Anthropic, OpenRouter, Groq, and Google Gemini.
Get started in 2 minutes with automatic token tracking.
Complete API documentation for ingesting usage events.
Learn how to create and configure meters for billing.
Comprehensive guide to usage-based billing with meters.
Perfect for SaaS apps, AI chatbots, content generation tools, and any LLM-powered application that needs usage-based billing.
## Quick Start
Get started with automatic LLM token tracking in just 2 minutes:
Install the Dodo Payments Ingestion Blueprints:
```bash theme={null}
npm install @dodopayments/ingestion-blueprints
```
You'll need two API keys:
* **Dodo Payments API Key**: Get it from [Dodo Payments Dashboard](https://app.dodopayments.com/developer/api-keys)
* **LLM Provider API Key**: From AI SDK, OpenAI, Anthropic, Groq, etc.
Store your API keys securely in environment variables. Never commit them to version control.
Before tracking usage, create a meter in your Dodo Payments dashboard:
1. **Login** to [Dodo Payments Dashboard](https://app.dodopayments.com/)
2. **Navigate to** Products → Meters
3. **Click** "Create Meter"
4. **Configure your meter**:
* **Meter Name**: Choose a descriptive name (e.g., "LLM Token Usage")
* **Event Name**: Set a unique event identifier (e.g., `llm.chat_completion`)
* **Aggregation Type**: Select `sum` to add up token counts
* **Over Property**: Choose what to track:
* `inputTokens` - Track input/prompt tokens
* `outputTokens` - Track output/completion tokens (includes reasoning tokens when applicable)
* `totalTokens` - Track combined input + output tokens
The **Event Name** you set here must match exactly what you pass to the SDK (case-sensitive).
For detailed instructions, see the [Usage-Based Billing Guide](/developer-resources/usage-based-billing-guide).
Wrap your LLM client and start tracking automatically:
```javascript AI SDK theme={null}
import { createLLMTracker } from '@dodopayments/ingestion-blueprints';
import { generateText } from 'ai';
import { google } from '@ai-sdk/google';
const llmTracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'aisdk.usage',
});
const client = llmTracker.wrap({
client: { generateText },
customerId: 'customer_123'
});
const response = await client.generateText({
model: google('gemini-2.0-flash'),
prompt: 'Hello!',
maxOutputTokens: 500
});
console.log('Usage:', response.usage);
```
```javascript OpenRouter theme={null}
import { createLLMTracker } from '@dodopayments/ingestion-blueprints';
import OpenAI from 'openai';
const openrouter = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: process.env.OPENROUTER_API_KEY
});
const llmTracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'openrouter.usage'
});
const client = llmTracker.wrap({
client: openrouter,
customerId: 'customer_123'
});
const response = await client.chat.completions.create({
model: 'qwen/qwen3-max',
messages: [{ role: 'user', content: 'Hello!' }],
max_tokens: 500
});
console.log('Response:', response.choices[0].message.content);
console.log('Usage:', response.usage);
```
```javascript OpenAI theme={null}
import { createLLMTracker } from '@dodopayments/ingestion-blueprints';
import OpenAI from 'openai';
// 1. Create your LLM client (normal way)
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY
});
// 2. Create tracker ONCE at startup
const tracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode', // Use 'live_mode' for production
eventName: 'llm.chat_completion' // Match your meter's event name
});
// 3. Wrap & use - automatic tracking!
const client = tracker.wrap({
client: openai,
customerId: 'customer_123'
});
// Every API call is now automatically tracked
const response = await client.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: 'Hello!' }]
});
// ✨ Usage automatically sent to Dodo Payments!
console.log('Tokens used:', response.usage);
```
```javascript Anthropic theme={null}
import { createLLMTracker } from '@dodopayments/ingestion-blueprints';
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
const tracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'anthropic.usage'
});
const client = tracker.wrap({
client: anthropic,
customerId: 'customer_123'
});
const response = await client.messages.create({
model: 'claude-sonnet-4-0',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello Claude!' }]
});
console.log('Tokens used:', response.usage);
```
```javascript Groq theme={null}
import { createLLMTracker } from '@dodopayments/ingestion-blueprints';
import Groq from 'groq-sdk';
const groq = new Groq({
apiKey: process.env.GROQ_API_KEY
});
const tracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'groq.usage'
});
const client = tracker.wrap({
client: groq,
customerId: 'customer_123'
});
const response = await client.chat.completions.create({
model: 'llama-3.1-8b-instant',
messages: [{ role: 'user', content: 'Hello!' }]
});
console.log('Tokens:', response.usage);
```
```javascript Google Gemini theme={null}
import { createLLMTracker } from '@dodopayments/ingestion-blueprints';
import { GoogleGenAI } from '@google/genai';
const googleGenai = new GoogleGenAI({
apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY
});
const llmTracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'gemini.usage'
});
const client = llmTracker.wrap({
client: googleGenai,
customerId: 'customer_123'
});
const response = await client.models.generateContent({
model: 'gemini-2.5-flash',
contents: 'Why is the sky blue?'
});
console.log('Response:', response.text);
console.log('Usage:', response.usageMetadata);
```
That's it! Every API call now automatically tracks token usage and sends events to Dodo Payments for billing.
***
## Configuration
### Tracker Configuration
Create a tracker once at application startup with these required parameters:
Your Dodo Payments API key. Get it from the [API Keys page](https://app.dodopayments.com/developer/api-keys).
```javascript theme={null}
apiKey: process.env.DODO_PAYMENTS_API_KEY
```
The environment mode for the tracker.
* `test_mode` - Use for development and testing
* `live_mode` - Use for production
```javascript theme={null}
environment: 'test_mode' // or 'live_mode'
```
Always use `test_mode` during development to avoid affecting production metrics.
The event name that triggers your meter. Must match exactly what you configured in your Dodo Payments meter (case-sensitive).
```javascript theme={null}
eventName: 'llm.chat_completion'
```
This event name links your tracked usage to the correct meter for billing calculations.
### Wrapper Configuration
When wrapping your LLM client, provide these parameters:
Your LLM client instance (OpenAI, Anthropic, Groq, etc.).
```javascript theme={null}
client: openai
```
The unique customer identifier for billing. This should match your customer ID in Dodo Payments.
```javascript theme={null}
customerId: 'customer_123'
```
Use your application's user ID or customer ID to ensure accurate billing per customer.
Optional additional data to attach to the tracking event. Useful for filtering and analysis.
```javascript theme={null}
metadata: {
feature: 'chat',
userTier: 'premium',
sessionId: 'session_123',
modelVersion: 'gpt-4'
}
```
### Complete Configuration Example
```javascript Full Configuration theme={null}
import { createLLMTracker } from "@dodopayments/ingestion-blueprints";
import { generateText } from "ai";
import { google } from "@ai-sdk/google";
import "dotenv/config";
async function aiSdkExample() {
console.log("🤖 AI SDK Simple Usage Example\n");
try {
// 1. Create tracker
const llmTracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY!,
environment: "test_mode",
eventName: "your_meter_event_name",
});
// 2. Wrap the ai-sdk methods
const client = llmTracker.wrap({
client: { generateText },
customerId: "customer_123",
metadata: {
provider: "ai-sdk",
},
});
// 3. Use the wrapped function
const response = await client.generateText({
model: google("gemini-2.5-flash"),
prompt: "Hello, I am a cool guy! Tell me a fun fact.",
maxOutputTokens: 500,
});
console.log(response);
console.log(response.usage);
console.log("✅ Automatically tracked for customer\n");
} catch (error) {
console.error(error);
}
}
aiSdkExample().catch(console.error);
```
**Automatic Tracking:** The SDK automatically tracks token usage in the background without modifying the response. Your code remains clean and identical to using the original provider SDKs.
***
## Supported Providers
The LLM Blueprint works seamlessly with all major LLM providers and aggregators:
Track usage with the Vercel AI SDK for universal LLM support.
```javascript AI SDK Integration theme={null}
import { createLLMTracker } from '@dodopayments/ingestion-blueprints';
import { generateText } from 'ai';
import { google } from '@ai-sdk/google';
const llmTracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'aisdk.usage',
});
const client = llmTracker.wrap({
client: { generateText },
customerId: 'customer_123',
metadata: {
model: 'gemini-2.0-flash',
feature: 'chat'
}
});
const response = await client.generateText({
model: google('gemini-2.0-flash'),
prompt: 'Explain neural networks',
maxOutputTokens: 500
});
console.log('Usage:', response.usage);
```
**Tracked Metrics:**
* `inputTokens` → `inputTokens`
* `outputTokens` + `reasoningTokens` → `outputTokens`
* `totalTokens` → `totalTokens`
* Model name
When using reasoning-capable models through AI SDK (like Google's Gemini 2.5 Flash with thinking mode), reasoning tokens are automatically included in the `outputTokens` count for accurate billing.
Track token usage across 200+ models via OpenRouter's unified API.
```javascript OpenRouter Integration theme={null}
import { createLLMTracker } from '@dodopayments/ingestion-blueprints';
import OpenAI from 'openai';
// OpenRouter uses OpenAI-compatible API
const openrouter = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: process.env.OPENROUTER_API_KEY
});
const tracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'openrouter.usage'
});
const client = tracker.wrap({
client: openrouter,
customerId: 'user_123',
metadata: { provider: 'openrouter' }
});
const response = await client.chat.completions.create({
model: 'qwen/qwen3-max',
messages: [{ role: 'user', content: 'What is machine learning?' }],
max_tokens: 500
});
console.log('Response:', response.choices[0].message.content);
console.log('Usage:', response.usage);
```
**Tracked Metrics:**
* `prompt_tokens` → `inputTokens`
* `completion_tokens` → `outputTokens`
* `total_tokens` → `totalTokens`
* Model name
OpenRouter provides access to models from OpenAI, Anthropic, Google, Meta, and many more providers through a single API.
Track token usage from OpenAI's GPT models automatically.
```javascript OpenAI Integration theme={null}
import { createLLMTracker } from '@dodopayments/ingestion-blueprints';
import OpenAI from 'openai';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const tracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'openai.usage'
});
const client = tracker.wrap({
client: openai,
customerId: 'user_123'
});
// All OpenAI methods work automatically
const response = await client.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: 'Explain quantum computing' }]
});
console.log('Total tokens:', response.usage.total_tokens);
```
**Tracked Metrics:**
* `prompt_tokens` → `inputTokens`
* `completion_tokens` → `outputTokens`
* `total_tokens` → `totalTokens`
* Model name
Track token usage from Anthropic's Claude models.
```javascript Anthropic Integration theme={null}
import { createLLMTracker } from '@dodopayments/ingestion-blueprints';
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const tracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'anthropic.usage'
});
const client = tracker.wrap({
client: anthropic,
customerId: 'user_123'
});
const response = await client.messages.create({
model: 'claude-sonnet-4-0',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Explain machine learning' }]
});
console.log('Input tokens:', response.usage.input_tokens);
console.log('Output tokens:', response.usage.output_tokens);
```
**Tracked Metrics:**
* `input_tokens` → `inputTokens`
* `output_tokens` → `outputTokens`
* Calculated `totalTokens`
* Model name
Track ultra-fast LLM inference with Groq.
```javascript Groq Integration theme={null}
import { createLLMTracker } from '@dodopayments/ingestion-blueprints';
import Groq from 'groq-sdk';
const groq = new Groq({ apiKey: process.env.GROQ_API_KEY });
const tracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'groq.usage'
});
const client = tracker.wrap({
client: groq,
customerId: 'user_123'
});
const response = await client.chat.completions.create({
model: 'llama-3.1-8b-instant',
messages: [{ role: 'user', content: 'What is AI?' }]
});
console.log('Tokens:', response.usage);
```
**Tracked Metrics:**
* `prompt_tokens` → `inputTokens`
* `completion_tokens` → `outputTokens`
* `total_tokens` → `totalTokens`
* Model name
Track token usage from Google's Gemini models via the Google GenAI SDK.
```javascript Google Gemini Integration theme={null}
import { createLLMTracker } from '@dodopayments/ingestion-blueprints';
import { GoogleGenAI } from '@google/genai';
const googleGenai = new GoogleGenAI({
apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY
});
const tracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'gemini.usage'
});
const client = tracker.wrap({
client: googleGenai,
customerId: 'user_123'
});
const response = await client.models.generateContent({
model: 'gemini-2.5-flash',
contents: 'Explain quantum computing'
});
console.log('Response:', response.text);
console.log('Usage:', response.usageMetadata);
```
**Tracked Metrics:**
* `promptTokenCount` → `inputTokens`
* `candidatesTokenCount` + `thoughtsTokenCount` → `outputTokens`
* `totalTokenCount` → `totalTokens`
* Model version
**Gemini Thinking Mode:** When using Gemini models with thinking/reasoning capabilities (like Gemini 2.5 Pro), the SDK automatically includes `thoughtsTokenCount` (reasoning tokens) in `outputTokens` to accurately reflect the full computational cost.
***
## Advanced Usage
### Multiple Providers
Track usage across different LLM providers with separate trackers:
```javascript Multiple Provider Setup theme={null}
import { createLLMTracker } from '@dodopayments/ingestion-blueprints';
import OpenAI from 'openai';
import Groq from 'groq-sdk';
import Anthropic from '@anthropic-ai/sdk';
import { GoogleGenAI } from '@google/genai';
// Create separate trackers for different providers
const openaiTracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'live_mode',
eventName: 'openai.usage'
});
const groqTracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'live_mode',
eventName: 'groq.usage'
});
const anthropicTracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'live_mode',
eventName: 'anthropic.usage'
});
const geminiTracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'live_mode',
eventName: 'gemini.usage'
});
const openrouterTracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'live_mode',
eventName: 'openrouter.usage'
});
// Initialize clients
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const groq = new Groq({ apiKey: process.env.GROQ_API_KEY });
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const googleGenai = new GoogleGenAI({ apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY });
const openrouter = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: process.env.OPENROUTER_API_KEY
});
// Wrap clients
const trackedOpenAI = openaiTracker.wrap({ client: openai, customerId: 'user_123' });
const trackedGroq = groqTracker.wrap({ client: groq, customerId: 'user_123' });
const trackedAnthropic = anthropicTracker.wrap({ client: anthropic, customerId: 'user_123' });
const trackedGemini = geminiTracker.wrap({ client: googleGenai, customerId: 'user_123' });
const trackedOpenRouter = openrouterTracker.wrap({ client: openrouter, customerId: 'user_123' });
// Use whichever provider you need
const response = await trackedOpenAI.chat.completions.create({...});
// or
const geminiResponse = await trackedGemini.models.generateContent({...});
// or
const openrouterResponse = await trackedOpenRouter.chat.completions.create({...});
```
Use different event names for different providers to track usage separately in your meters.
### Express.js API Integration
Complete example of integrating LLM tracking into an Express.js API:
```javascript Express.js Server theme={null}
import express from 'express';
import { createLLMTracker } from '@dodopayments/ingestion-blueprints';
import OpenAI from 'openai';
const app = express();
app.use(express.json());
// Initialize OpenAI client
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
// Create tracker once at startup
const tracker = createLLMTracker({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.NODE_ENV === 'production' ? 'live_mode' : 'test_mode',
eventName: 'api.chat_completion'
});
// Chat endpoint with automatic tracking
app.post('/api/chat', async (req, res) => {
try {
const { message, userId } = req.body;
// Validate input
if (!message || !userId) {
return res.status(400).json({ error: 'Missing message or userId' });
}
// Wrap client for this specific user
const trackedClient = tracker.wrap({
client: openai,
customerId: userId,
metadata: {
endpoint: '/api/chat',
timestamp: new Date().toISOString()
}
});
// Make LLM request - automatically tracked
const response = await trackedClient.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: message }],
temperature: 0.7
});
const completion = response.choices[0].message.content;
res.json({
message: completion,
usage: response.usage
});
} catch (error) {
console.error('Chat error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
```
***
## What Gets Tracked
Every LLM API call automatically sends a usage event to Dodo Payments with the following structure:
```json Event Structure theme={null}
{
"event_id": "llm_1673123456_abc123",
"customer_id": "customer_123",
"event_name": "llm.chat_completion",
"timestamp": "2024-01-08T10:30:00Z",
"metadata": {
"inputTokens": 10,
"outputTokens": 25,
"totalTokens": 35,
"model": "gpt-4",
}
}
```
### Event Fields
Unique identifier for this specific event. Automatically generated by the SDK.
Format: `llm_[timestamp]_[random]`
The customer ID you provided when wrapping the client. Used for billing.
The event name that triggers your meter. Matches your tracker configuration.
ISO 8601 timestamp when the event occurred.
Token usage and additional tracking data:
* `inputTokens` - Number of input/prompt tokens used
* `outputTokens` - Number of output/completion tokens used (includes reasoning tokens when applicable)
* `totalTokens` - Total tokens (input + output)
* `model` - The LLM model used (e.g., "gpt-4")
* `provider` - The LLM provider (if included in wrapper metadata)
* Any custom metadata you provided when wrapping the client
**Reasoning Tokens:** For models with reasoning capabilities, `outputTokens` automatically includes both the completion tokens and reasoning tokens.
Your Dodo Payments meter uses the `metadata` fields (especially `inputTokens`, `outputTokens` or `totalTokens`) to calculate usage and billing.
***
# Object Storage Blueprint
Source: https://docs.dodopayments.com/developer-resources/ingestion-blueprints/object-storage
Track file uploads and storage usage for S3, Google Cloud Storage, Azure Blob, and other object storage services.
## Use Cases
Explore common scenarios supported by the Object Storage Blueprint:
Bill customers based on total storage usage and upload volume.
Track backup data uploads and charge per GB stored.
Monitor media uploads and bill for storage and bandwidth.
Track document uploads per customer for usage-based pricing.
Perfect for billing based on storage uploads, file hosting, CDN usage, or backup services.
## Quick Start
Track object storage uploads with bytes consumed:
```bash theme={null}
npm install @dodopayments/ingestion-blueprints
```
* **Dodo Payments API Key**: Get it from [Dodo Payments Dashboard](https://app.dodopayments.com/developer/api-keys)
* **Storage Provider API Key**: From AWS S3, Google Cloud Storage, Azure, etc.
Create a meter in your [Dodo Payments Dashboard](https://app.dodopayments.com/):
* **Event Name**: `object_storage_upload` (or your preferred name)
* **Aggregation Type**: `sum` to track total bytes uploaded
* **Over Property**: `bytes` to bill based on storage size
```javascript AWS S3 Upload theme={null}
import { Ingestion, trackObjectStorage } from '@dodopayments/ingestion-blueprints';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import fs from 'fs';
const ingestion = new Ingestion({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'object_storage_upload'
});
const s3 = new S3Client({ region: 'us-east-1' });
// Read the file (example: from disk or request)
const fileBuffer = fs.readFileSync('./document.pdf');
// Upload to S3
const command = new PutObjectCommand({
Bucket: 'my-bucket',
Key: 'uploads/document.pdf',
Body: fileBuffer
});
await s3.send(command);
// Track the upload
await trackObjectStorage(ingestion, {
customerId: 'customer_123',
bytes: fileBuffer.length
});
```
```javascript Google Cloud Storage theme={null}
import { Ingestion, trackObjectStorage } from '@dodopayments/ingestion-blueprints';
import { Storage } from '@google-cloud/storage';
import fs from 'fs';
const ingestion = new Ingestion({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'object_storage_upload'
});
const storage = new Storage();
const bucket = storage.bucket('my-bucket');
// Read the file
const fileBuffer = fs.readFileSync('./image.png');
// Upload to GCS
await bucket.file('uploads/image.png').save(fileBuffer);
// Track the upload
await trackObjectStorage(ingestion, {
customerId: 'customer_456',
bytes: fileBuffer.length,
metadata: {
bucket: 'my-bucket',
key: 'uploads/image.png'
}
});
```
## Configuration
### Ingestion Configuration
Your Dodo Payments API key from the dashboard.
Environment mode: `test_mode` or `live_mode`.
Event name that matches your meter configuration.
### Track Object Storage Options
The customer ID for billing attribution.
Number of bytes uploaded. Required for byte-based billing.
Optional metadata about the upload like bucket name, content type, etc.
## Best Practices
**Track Before or After Upload**: You can track the event before or after the actual upload depending on your error handling strategy.
**Handle Upload Failures**: Only track successful uploads to avoid billing for failed operations.
# Stream Blueprint
Source: https://docs.dodopayments.com/developer-resources/ingestion-blueprints/stream
Track streaming data consumption for video, audio, live streams, and real-time data transfer billing.
## Use Cases
Explore common scenarios supported by the Stream Blueprint:
Bill customers based on video bandwidth consumption and streaming quality.
Track audio streaming usage per user for subscription tiers.
Monitor live stream consumption and charge for bandwidth usage.
Track real-time data transfer for IoT and telemetry applications.
Perfect for video/audio streaming platforms, live streaming services, and real-time data applications.
## Quick Start
Track streaming bytes consumed by your customers:
```bash theme={null}
npm install @dodopayments/ingestion-blueprints
```
* **Dodo Payments API Key**: Get it from [Dodo Payments Dashboard](https://app.dodopayments.com/developer/api-keys)
Create a meter in your [Dodo Payments Dashboard](https://app.dodopayments.com/):
* **Event Name**: `stream_consumption` (or your preferred name)
* **Aggregation Type**: `sum` to track total bytes streamed
* **Over Property**: `bytes` to bill based on bandwidth usage
```javascript Video Streaming theme={null}
import { Ingestion, trackStreamBytes } from '@dodopayments/ingestion-blueprints';
const ingestion = new Ingestion({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'stream_consumption'
});
// Track video stream consumption
await trackStreamBytes(ingestion, {
customerId: 'customer_123',
bytes: 10485760, // 10MB
metadata: {
stream_type: 'video',
}
});
```
## Configuration
### Ingestion Configuration
Your Dodo Payments API key from the dashboard.
Environment mode: `test_mode` or `live_mode`.
Event name that matches your meter configuration.
### Track Stream Bytes Options
The customer ID for billing attribution.
Number of bytes consumed in the stream. Required for bandwidth-based billing.
Optional metadata about the stream like stream type, quality, sessionId, etc.
## Best Practices
**Track by Chunk**: For long streams, track consumption in chunks rather than waiting for the entire stream to complete.
**Accurate Byte Counting**: Ensure byte counts include all overhead (headers, protocol overhead) if billing for total bandwidth.
# Time Range Blueprint
Source: https://docs.dodopayments.com/developer-resources/ingestion-blueprints/time-range
Track resource consumption based on elapsed time for compute, serverless functions, containers, and runtime billing.
## Use Cases
Explore common scenarios supported by the Time Range Blueprint:
Bill based on function execution time and memory usage.
Track container running time for usage-based billing.
Monitor VM runtime and charge by the minute or hour.
Track processing time for data exports, reports, and batch jobs.
Perfect for billing based on compute time, function execution duration, container runtime, or any time-based usage.
## Quick Start
Track resource usage by time duration:
```bash theme={null}
npm install @dodopayments/ingestion-blueprints
```
* **Dodo Payments API Key**: Get it from [Dodo Payments Dashboard](https://app.dodopayments.com/developer/api-keys)
Create a meter in your [Dodo Payments Dashboard](https://app.dodopayments.com/):
* **Event Name**: `time_range_usage` (or your preferred name)
* **Aggregation Type**: `sum` to track total duration
* **Over Property**: `durationSeconds`, `durationMinutes`, or `durationMs`
```javascript Serverless Functions theme={null}
import { Ingestion, trackTimeRange } from '@dodopayments/ingestion-blueprints';
const ingestion = new Ingestion({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'function_execution'
});
// Track function execution time
const startTime = Date.now();
// Execute your function (example: image processing)
const result = await yourImageProcessingLogic();
const durationMs = Date.now() - startTime;
await trackTimeRange(ingestion, {
customerId: 'customer_123',
durationMs: durationMs
});
```
```javascript Container Runtime theme={null}
import { Ingestion, trackTimeRange } from '@dodopayments/ingestion-blueprints';
const ingestion = new Ingestion({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'container_runtime'
});
// Track container runtime in seconds
await trackTimeRange(ingestion, {
customerId: 'customer_456',
durationSeconds: 120
});
```
```javascript VM Instance Runtime theme={null}
import { Ingestion, trackTimeRange } from '@dodopayments/ingestion-blueprints';
const ingestion = new Ingestion({
apiKey: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode',
eventName: 'vm_runtime'
});
// Track VM runtime in minutes
await trackTimeRange(ingestion, {
customerId: 'customer_789',
durationMinutes: 60
});
```
## Configuration
### Ingestion Configuration
Your Dodo Payments API key from the dashboard.
Environment mode: `test_mode` or `live_mode`.
Event name that matches your meter configuration.
### Track Time Range Options
The customer ID for billing attribution.
Duration in milliseconds. Use for sub-second precision.
Duration in seconds. Most common for function execution and short tasks.
Duration in minutes. Useful for longer-running resources like VMs.
Optional metadata about the resource like CPU, memory, region, etc.
## Best Practices
**Choose the Right Unit**: Use milliseconds for short operations, seconds for functions, and minutes for longer-running resources.
**Accurate Timing**: Use `Date.now()` or `performance.now()` for accurate time tracking, especially for serverless functions.
# One-time Payments Integration Guide
Source: https://docs.dodopayments.com/developer-resources/integration-guide
This guide will help you integrate the Dodo Payments API into your website.
## Prerequisites
To integrate the Dodo Payments API, you'll need:
* A Dodo Payments merchant account
* API Credentials (API key and webhook secret key) from dashboard
## Dashboard Setup
1. Navigate to the [Dodo Payments Dashboard](https://app.dodopayments.com/)
2. Create a product (one-time payment or subscription)
3. Generate your API key:
* Go to Developer > API
* [Detailed Guide](/api-reference/introduction#api-key-generation)
* Copy the API key the in env named DODO\_PAYMENTS\_API\_KEY
4. Configure webhooks:
* Go to Developer > Webhooks
* Create a webhook URL for payment notifications
* Copy the webhook secret key in env
## Integration
### Payment Links
Choose the integration path that fits your use case:
* **Checkout Sessions (recommended)**: Best for most integrations. Create a session on your server and redirect customers to a secure, hosted checkout.
* **Overlay Checkout (embedded)**: Use when you need an in-page experience that embeds the hosted checkout on your site.
* **Static Payment Links**: No-code, instantly shareable URLs for quick payment collection.
* **Dynamic Payment Links**: Programmatically created links. However, Checkout Sessions are recommended and provide more flexibility.
#### 1. Checkout Sessions
Use Checkout Sessions to create a secure, hosted checkout experience for one-time payments or subscriptions. You create a session on your server, then redirect the customer to the returned `checkout_url`.
Checkout sessions are valid for 24 hours by default. If you pass confirm=true, sessions are valid for 15 minutes and all required fields must be provided.
Choose your preferred SDK or call the REST API.
```javascript theme={null}
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
});
const session = await client.checkoutSessions.create({
product_cart: [{ product_id: 'prod_123', quantity: 1 }],
customer: { email: 'customer@example.com', name: 'John Doe' },
return_url: 'https://yourapp.com/checkout/success',
});
```
```python theme={null}
import os
from dodopayments import DodoPayments
client = DodoPayments(bearer_token=os.environ.get("DODO_PAYMENTS_API_KEY"))
session = client.checkout_sessions.create(
product_cart=[{"product_id": "prod_123", "quantity": 1}],
customer={"email": "customer@example.com", "name": "John Doe"},
return_url="https://yourapp.com/checkout/success",
)
```
```javascript theme={null}
const response = await fetch('https://test.dodopayments.com/checkouts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.DODO_PAYMENTS_API_KEY}`,
},
body: JSON.stringify({
product_cart: [{ product_id: 'prod_123', quantity: 1 }],
customer: { email: 'customer@example.com', name: 'John Doe' },
return_url: 'https://yourapp.com/checkout/success',
}),
});
const session = await response.json();
```
After session creation, redirect to the `checkout_url` to start the hosted flow.
```javascript theme={null}
// Example in a browser context
window.location.href = session.checkout_url;
```
Prefer Checkout Sessions for the fastest, most reliable way to start taking payments. For advanced customization, see the full Checkout Sessions guide and the API Reference.
#### 2. Overlay Checkout
For a seamless in-page checkout experience, explore our [Overlay Checkout](/developer-resources/overlay-checkout) integration that allows customers to complete payments without leaving your website.
#### 3. Static Payment Links
Static payment links let you quickly accept payments by sharing a simple URL. You can customize the checkout experience by passing query parameters to pre-fill customer details, control form fields, and add custom metadata.
Start with the base URL and append your product ID:
```text theme={null}
https://checkout.dodopayments.com/buy/{productid}
```
Include essential query parameters:
* Number of items to purchase.
* URL to redirect after payment completion.
The redirect URL will include payment details as query parameters, for example: [https://example.com/?payment\_id=pay\_ts2ySpzg07phGeBZqePbH\&status=succeeded](https://example.com/?payment_id=pay_ts2ySpzg07phGeBZqePbH\&status=succeeded)
Add customer or billing fields as query parameters to streamline checkout.
* Customer's full name (ignored if firstName or lastName is provided).
* Customer's first name.
* Customer's last name.
* Customer's email address.
* Customer's country.
* Street address.
* City.
* State or province.
* Postal/ZIP code.
* true or false
You can disable specific fields to make them read-only for the customer. This is useful when you already have the customer's details (e.g., logged-in users).
To disable a field, provide its value and set the corresponding disable... flag to true:
```text Example theme={null}
&email=alice@example.com&disableEmail=true
```
| Field | Disable Flag | Required Parameter |
| ------------ | -------------------- | ------------------ |
| Full Name | `disableFullName` | `fullName` |
| First Name | `disableFirstName` | `firstName` |
| Last Name | `disableLastName` | `lastName` |
| Email | `disableEmail` | `email` |
| Country | `disableCountry` | `country` |
| Address Line | `disableAddressLine` | `addressLine` |
| City | `disableCity` | `city` |
| State | `disableState` | `state` |
| ZIP Code | `disableZipCode` | `zipCode` |
Disabling fields helps prevent accidental changes and ensures data consistency.
Setting showDiscounts=false will disable and hide the discounts section in the checkout form. Use this if you want to prevent customers from entering coupon or promo codes during checkout.
* Specifies the payment currency. Defaults to the billing country's currency.
* Show or hide the currency selector.
* Amount in cents (for Pay What You Want pricing only).
* Custom metadata fields (e.g., metadata\_orderId=123).
Send the completed payment link to your customer. When they visit, all query parameters are collected and stored with a session ID. The URL is then simplified to include just the session parameter (e.g., ?session=sess\_1a2b3c4d). The stored information persists through page refreshes and is accessible throughout the checkout process.
The customer's checkout experience is now streamlined and personalized based on your parameters.
#### 4. Dynamic Payment Links
Prefer Checkout Sessions for most use cases, they offer more flexibility and control.
Created via API call or our SDK with customer details. Here's an example:
There are two APIs for creating dynamic payment links:
* One-time Payment Link API [API reference](/api-reference/payments/post-payments)
* Subscription Payment Link API [API reference](/api-reference/subscriptions/post-subscriptions)
The guide below is for one-time payment link creation.
For detailed instructions on integrating subscriptions, refer to this [Subscription Integration Guide](/developer-resources/subscription-integration-guide).
Make sure you are passing `payment_link = true` to get payment link
```javascript theme={null}
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env['DODO_PAYMENTS_API_KEY'], // This is the default and can be omitted
});
async function main() {
const payment = await client.payments.create({
payment_link: true,
billing: { city: 'city', country: 'AF', state: 'state', street: 'street', zipcode: 0 },
customer: { email: 'email@email.com', name: 'name' },
product_cart: [{ product_id: 'product_id', quantity: 0 }],
});
console.log(payment.payment_id);
}
main();
```
```python theme={null}
import os
from dodopayments import DodoPayments
client = DodoPayments(
bearer_token=os.environ.get("DODO_PAYMENTS_API_KEY"), # This is the default and can be omitted (if using same name `DODO_PAYMENTS_API_KEY`)
)
payment = client.payments.create(
payment_link=True,
billing={
"city": "city",
"country": "AF",
"state": "state",
"street": "street",
"zipcode": 0,
},
customer={
"email": "email@email.com",
"name": "name",
},
product_cart=[{
"product_id": "product_id",
"quantity": 0,
}],
)
print(payment.payment_link)
```
```go theme={null}
package main
import (
"context"
"fmt"
"github.com/dodopayments/dodopayments-go"
"github.com/dodopayments/dodopayments-go/option"
)
func main() {
client := dodopayments.NewClient(
option.WithBearerToken("My Bearer Token"), // defaults to os.LookupEnv("DODO_PAYMENTS_API_KEY")
)
payment, err := client.Payments.New(context.TODO(), dodopayments.PaymentNewParams{
PaymentLink: dodopayments.F(true),
Billing: dodopayments.F(dodopayments.PaymentNewParamsBilling{
City: dodopayments.F("city"),
Country: dodopayments.F(dodopayments.CountryCodeAf),
State: dodopayments.F("state"),
Street: dodopayments.F("street"),
Zipcode: dodopayments.F(int64(0)),
}),
Customer: dodopayments.F(dodopayments.PaymentNewParamsCustomer{
Email: dodopayments.F("email"),
Name: dodopayments.F("name"),
}),
ProductCart: dodopayments.F([]dodopayments.PaymentNewParamsProductCart{dodopayments.PaymentNewParamsProductCart{
ProductID: dodopayments.F("product_id"),
Quantity: dodopayments.F(int64(0)),
}}),
})
if err != nil {
panic(err.Error())
}
fmt.Printf("%+v\n", payment.PaymentLink)
}
```
```javascript theme={null}
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { formData, cartItems } = paymentRequestSchema.parse(body);
const response = await fetch(`${process.env.NEXT_PUBLIC_DODO_TEST_API}/payments`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.DODO_API_KEY}`, // Replace with your API secret key generated from the Dodo Payments Dashboard
},
body: JSON.stringify({
billing: {
city: formData.city,
country: formData.country,
state: formData.state,
street: formData.addressLine,
zipcode: parseInt(formData.zipCode),
},
customer: {
email: formData.email,
name: `${formData.firstName} ${formData.lastName}`,
phone_number: formData.phoneNumber || undefined,
},
payment_link: true,
product_cart: cartItems.map((id) => ({
product_id: id,
quantity: 1,
})),
return_url: process.env.NEXT_PUBLIC_RETURN_URL,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => null);
return NextResponse.json(
{ error: "Payment link creation failed", details: errorData },
{ status: response.status }
);
}
const data = await response.json();
return NextResponse.json({ paymentLink: data.payment_link });
} catch (err) {
console.error("Payment error:", err);
return NextResponse.json(
{
error: err instanceof Error ? err.message : "An unknown error occurred",
},
{ status: 500 }
);
}
}
```
After creating the payment link, redirect your customers to complete their payment.
### Implementing Webhooks
Set up an API endpoint to receive payment notifications. Here's an example using Next.js:
```javascript theme={null}
import { Webhook } from "standardwebhooks";
import { headers } from "next/headers";
import { WebhookPayload } from "@/types/api-types";
const webhook = new Webhook(process.env.DODO_WEBHOOK_KEY!); // Replace with your secret key generated from the Dodo Payments Dashboard
export async function POST(request: Request) {
const headersList = headers();
const rawBody = await request.text();
const webhookHeaders = {
"webhook-id": headersList.get("webhook-id") || "",
"webhook-signature": headersList.get("webhook-signature") || "",
"webhook-timestamp": headersList.get("webhook-timestamp") || "",
};
await webhook.verify(rawBody, webhookHeaders);
const payload = JSON.parse(rawBody) as WebhookPayload;
// Process the payload according to your business logic
}
```
Our webhook implementation follows the [Standard Webhooks](https://standardwebhooks.com/) specification. For webhook type definitions, refer to our [Webhook Event Guide](/developer-resources/webhooks/intents/webhook-events-guide).
You can refer to this project with demo implementation on [GitHub](https://github.com/dodopayments/dodo-checkout-demo) using Next.js and TypeScript.
You can check out the live implementation [here](https://atlas.dodopayments.com/).
# MCP Server
Source: https://docs.dodopayments.com/developer-resources/mcp-server
Enable AI assistants to interact with the Dodo Payments API through the Model Context Protocol (MCP) for seamless payment operations, subscription management, and customer interactions.
## Overview
The Model Context Protocol (MCP) is an open standard that enables AI applications to securely connect to external data sources and APIs. The Dodo Payments MCP Server provides AI assistants like Claude, Cursor, and other MCP-compatible clients with structured access to your payment infrastructure.
### Key capabilities
* **Payment Operations**: Create, retrieve, and manage payments and refunds
* **Subscription Management**: Handle recurring billing, upgrades, and cancellations
* **Customer Administration**: Manage customer data and portal access
* **Product Catalog**: Create and update products, pricing, and discounts
* **License Management**: Activate, validate, and manage software licenses
* **Usage-Based Billing**: Track and bill for metered usage
## Installation
Choose the installation method that best fits your workflow.
### Remote MCP Server (Recommended)
Access the hosted MCP server without any local setup or installation. This is the fastest way to get started.
Navigate to [https://mcp.dodopayments.com](https://mcp.dodopayments.com) in your browser.
Copy the provided JSON configuration for your specific client. For Cursor or Claude Desktop, add this to your MCP settings:
```json theme={null}
{
"mcpServers": {
"dodopayments_api": {
"command": "npx",
"args": ["-y", "mcp-remote@latest", "https://mcp.dodopayments.com/sse"]
}
}
}
```
The OAuth flow will prompt you to:
* Enter your Dodo Payments API key
* Select your environment (test or live)
* Choose your MCP client type
Keep your API key secure. Use test mode keys during development.
Click **Login and Approve** to authorize the connection.
Once connected, your AI assistant can interact with the Dodo Payments API on your behalf.
### NPM Package
Install and run the MCP server locally using NPM.
```bash theme={null}
# Set your API key
export DODO_PAYMENTS_API_KEY="dodo_test_..."
# Run the latest version
npx -y dodopayments-mcp@latest
```
```json theme={null}
{
"mcpServers": {
"dodopayments_api": {
"command": "npx",
"args": ["-y", "dodopayments-mcp@latest"],
"env": {
"DODO_PAYMENTS_API_KEY": "dodo_test_..."
}
}
}
}
```
Use `@latest` to always pull the most recent version, or pin to a specific version like `@1.0.0` for stability.
### Docker
Run the MCP server in a containerized environment for consistent deployment.
```bash Docker theme={null}
# Pull the latest image
docker pull ghcr.io/dodopayments/mcp:latest
# Run the container
docker run -e DODO_PAYMENTS_API_KEY="dodo_test_..." \
-p 3000:3000 \
ghcr.io/dodopayments/mcp:latest
```
```yaml Docker Compose theme={null}
services:
dodopayments-mcp:
image: ghcr.io/dodopayments/mcp:latest
environment:
- DODO_PAYMENTS_API_KEY=dodo_test_...
ports:
- "3000:3000"
restart: unless-stopped
```
Docker images are available on [GitHub Container Registry](https://github.com/orgs/dodopayments/packages/container/package/mcp).
## Client Configuration
Configure the Dodo Payments MCP server in your preferred AI client.
Set up the Dodo Payments MCP server in Cursor to enable conversational access to your payments data.
Navigate to **Cursor Settings** > **Features** > **Model Context Protocol** or press `Cmd/Ctrl + Shift + P` and search for "MCP Settings".
Click **Edit Config** to open the `mcp_config.json` file.
Choose one of the following configurations:
**Remote Server (Recommended)**
```json theme={null}
{
"mcpServers": {
"dodopayments": {
"command": "npx",
"args": ["-y", "mcp-remote@latest", "https://mcp.dodopayments.com/sse"]
}
}
}
```
**Local NPX**
```json theme={null}
{
"mcpServers": {
"dodopayments": {
"command": "npx",
"args": ["-y", "dodopayments-mcp@latest"],
"env": {
"DODO_PAYMENTS_API_KEY": "dodo_test_..."
}
}
}
}
```
Save the configuration file and restart Cursor.
Verify the connection by asking the AI assistant about your Dodo Payments data.
Set up the Dodo Payments MCP server in Claude Desktop to enable conversational access to your payments data.
Find the Claude Desktop configuration file:
* **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
* **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
* **Linux**: `~/.config/Claude/claude_desktop_config.json`
Open the file in a text editor and add the Dodo Payments configuration:
**Remote Server (Recommended)**
```json theme={null}
{
"mcpServers": {
"dodopayments": {
"command": "npx",
"args": ["-y", "mcp-remote@latest", "https://mcp.dodopayments.com/sse"]
}
}
}
```
**Local NPX**
```json theme={null}
{
"mcpServers": {
"dodopayments": {
"command": "npx",
"args": ["-y", "dodopayments-mcp@latest"],
"env": {
"DODO_PAYMENTS_API_KEY": "dodo_test_..."
}
}
}
}
```
Close and reopen Claude Desktop for the changes to take effect.
Look for the 🔌 icon in Claude Desktop to confirm the MCP server is connected.
Set up the Dodo Payments MCP server in Cline (VS Code) to enable conversational access to your payments data.
Locate the Cline configuration file:
* **macOS**: `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
* **Windows**: `%APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json`
* **Linux**: `~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
Add the Dodo Payments MCP server configuration:
```json theme={null}
{
"mcpServers": {
"dodopayments": {
"command": "npx",
"args": ["-y", "dodopayments-mcp@latest"],
"env": {
"DODO_PAYMENTS_API_KEY": "dodo_test_..."
}
}
}
}
```
Reload the VS Code window (`Cmd/Ctrl + Shift + P` > "Reload Window") to activate the MCP server connection.
Set up the Dodo Payments MCP server in Zed to enable conversational access to your payments data.
Navigate to **Settings** > **Assistant** > **Model Context Protocol**.
Add the following configuration:
```json theme={null}
{
"mcpServers": {
"dodopayments": {
"command": "npx",
"args": ["-y", "dodopayments-mcp@latest"],
"env": {
"DODO_PAYMENTS_API_KEY": "dodo_test_..."
}
}
}
}
```
Save the configuration. The MCP server should now be available in Zed's AI assistant.
Set up the Dodo Payments MCP server in other MCP-compatible clients to enable conversational access to your payments data.
**Remote Server (Recommended)**
```json theme={null}
{
"mcpServers": {
"dodopayments": {
"command": "npx",
"args": ["-y", "mcp-remote@latest", "https://mcp.dodopayments.com/sse"]
}
}
}
```
**Local NPX**
```json theme={null}
{
"mcpServers": {
"dodopayments": {
"command": "npx",
"args": ["-y", "dodopayments-mcp@latest"],
"env": {
"DODO_PAYMENTS_API_KEY": "dodo_test_..."
}
}
}
}
```
Visit [modelcontextprotocol.io/clients](https://modelcontextprotocol.io/clients) for a complete list of MCP-compatible clients and their configuration methods.
## Tool Filtering
Control which API operations are exposed to your AI assistant to optimize performance and maintain security.
### Filter by operation type
Restrict access to read-only or write operations.
```bash theme={null}
# Only expose read operations (GET requests)
npx dodopayments-mcp --operation=read
# Only expose write operations (POST, PUT, DELETE)
npx dodopayments-mcp --operation=write
```
Use `--operation=read` during development to prevent accidental modifications to your data.
### Filter by resource
Limit tools to specific API resources.
```bash theme={null}
# Only expose payment-related tools
npx dodopayments-mcp --resource=payments
# Expose multiple resources
npx dodopayments-mcp --resource=payments --resource=customers
```
### Include or exclude specific tools
Fine-tune which individual tools are available.
```bash theme={null}
# Include only specific tools
npx dodopayments-mcp --tool=create_payments --tool=list_payments
# Exclude specific tools
npx dodopayments-mcp --no-tool=delete_products
# List all available tools
npx dodopayments-mcp --list
```
### Combine filters
Apply multiple filters simultaneously for precise control.
```bash theme={null}
# Only payment and subscription read operations
npx dodopayments-mcp --operation=read --resource=payments --resource=subscriptions
```
## Advanced Usage
### Programmatic integration
Build custom MCP servers or extend the existing one programmatically.
```javascript theme={null}
import { server, endpoints, init } from "dodopayments-mcp/server";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
// Initialize with all default endpoints
init({ server, endpoints });
// Start the server
const transport = new StdioServerTransport();
await server.connect(transport);
```
### Custom tool development
Extend the MCP server with your own tools and business logic.
```javascript theme={null}
import { server, init } from "dodopayments-mcp/server";
import createPayments from "dodopayments-mcp/tools/payments/create-payments";
import { zodToJsonSchema } from "zod-to-json-schema";
import { z } from "zod";
// Define a custom tool
const customTool = {
tool: {
name: 'calculate_payment_total',
description: 'Calculate payment total with tax and fees',
inputSchema: zodToJsonSchema(
z.object({
amount: z.number().describe('Base amount in cents'),
tax_rate: z.number().describe('Tax rate as decimal'),
})
),
},
handler: async (client, args) => {
const total = args.amount * (1 + args.tax_rate);
return {
content: [{
type: 'text',
text: JSON.stringify({ total, currency: 'usd' }),
}],
};
},
};
// Initialize with custom tools
init({
server,
endpoints: [createPayments, customTool]
});
```
## Security Best Practices
Protect your API credentials and maintain secure integrations.
**Never commit credentials to version control**
Store API keys in environment variables or secure secret management systems.
```bash theme={null}
# Use environment variables
export DODO_PAYMENTS_API_KEY="dodo_test_..."
# Use a .env file (add to .gitignore)
echo "DODO_PAYMENTS_API_KEY=dodo_test_..." > .env
```
**Rotate keys regularly**
Generate new API keys periodically and revoke old ones through your Dodo Payments dashboard.
**Use test keys for development**
Always use test mode API keys during development to avoid affecting production data.
**Apply tool filtering in production**
Limit exposed operations to only what your AI assistant needs.
```bash theme={null}
# Production: read-only access
npx dodopayments-mcp --operation=read
# Development: full access
npx dodopayments-mcp
```
**Implement authentication for custom endpoints**
Validate requests and enforce authorization in your custom tool handlers.
**Monitor API usage**
Track MCP server activity through your Dodo Payments dashboard and set up alerts for unusual patterns.
**Use HTTPS for remote servers**
Always deploy remote MCP servers behind HTTPS endpoints.
**Implement rate limiting**
Protect against abuse by implementing rate limits at both the MCP server and API levels.
**Restrict network access**
Configure firewall rules to limit which clients can connect to your MCP server.
## Troubleshooting
**Verify your API key**
Ensure your API key is correctly set and has the necessary permissions.
```bash theme={null}
# Test your API key
curl -H "Authorization: Bearer dodo_test_..." \
https://api.dodopayments.com/payments
```
**Check your network connection**
Verify you can reach the Dodo Payments API endpoints.
**Review client logs**
Enable verbose logging in your MCP client to diagnose connection problems.
**Confirm API key environment**
Ensure you're using test keys with test endpoints and live keys with production endpoints.
**Check key permissions**
Verify your API key has permissions for the operations you're attempting.
**Regenerate credentials**
If issues persist, generate a new API key through your dashboard.
**Validate input parameters**
Ensure the AI assistant is providing correctly formatted parameters for each tool.
**Review error messages**
Check the error response from the API for specific guidance on what went wrong.
**Test with API directly**
Verify the operation works when calling the Dodo Payments API directly via curl or Postman.
## Resources
Explore the complete Dodo Payments API documentation
Learn more about the Model Context Protocol standard
# Mobile Integration Guide
Source: https://docs.dodopayments.com/developer-resources/mobile-integration
Unified guide for integrating Dodo Payments into Android, iOS, React Native and Flutter mobile applications.
Get your mobile payment integration running in 4 simple steps
Complete code examples for Android, iOS, React Native, and Flutter
## Prerequisites
Before integrating Dodo Payments into your mobile app, ensure you have:
* **Dodo Payments Account**: Active merchant account with API access
* **API Credentials**: API key and webhook secret key from your dashboard
* **Mobile App Project**: Android, iOS, React Native, or Flutter application
* **Backend Server**: To securely handle checkout session creation
## Integration Workflow
The mobile integration follows a secure 4-step process where your backend handles API calls and your mobile app manages the user experience.
Learn how to create a checkout session in your backend using Node.js, Python, and more. See complete examples and parameter references in the dedicated Checkout Sessions API documentation.
**Security**: Checkout sessions must be created on your backend server, never in the mobile app. This protects your API keys and ensures proper validation.
Your mobile app calls your backend to get the checkout URL:
```swift theme={null}
func getCheckoutURL(productId: String, customerEmail: String, customerName: String) async throws -> String {
let url = URL(string: "https://your-backend.com/api/create-checkout-session")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let requestData: [String: Any] = [
"productId": productId,
"customerEmail": customerEmail,
"customerName": customerName
]
request.httpBody = try JSONSerialization.data(withJSONObject: requestData)
let (data, _) = try await URLSession.shared.data(for: request)
let response = try JSONDecoder().decode(CheckoutResponse.self, from: data)
return response.checkout_url
}
```
```kotlin theme={null}
suspend fun getCheckoutURL(productId: String, customerEmail: String, customerName: String): String {
val client = OkHttpClient()
val requestBody = JSONObject().apply {
put("productId", productId)
put("customerEmail", customerEmail)
put("customerName", customerName)
}.toString().toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url("https://your-backend.com/api/create-checkout-session")
.post(requestBody)
.build()
val response = client.newCall(request).execute()
val responseBody = response.body?.string()
val jsonResponse = JSONObject(responseBody ?: "")
return jsonResponse.getString("checkout_url")
}
```
```javascript theme={null}
const getCheckoutURL = async (productId, customerEmail, customerName) => {
try {
const response = await fetch('https://your-backend.com/api/create-checkout-session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
productId,
customerEmail,
customerName
})
});
const data = await response.json();
return data.checkout_url;
} catch (error) {
console.error('Failed to get checkout URL:', error);
throw error;
}
};
```
**Security**: Mobile apps only communicate with your backend, never directly with Dodo Payments API.
Open the checkout URL in a secure in-app browser for payment processing.
View full code and setup instructions for Android, iOS, and Flutter mobile payments.
Process payment completion via webhooks and redirect URLs to confirm payment status.
## Platform-Specific Integration
Choose your mobile platform below for complete implementation examples:
### Android Integration
#### Chrome Custom Tabs Implementation
```kotlin theme={null}
// Add Chrome Custom Tabs dependency to build.gradle
implementation 'androidx.browser:browser:1.5.0'
// In your Activity
class PaymentActivity : AppCompatActivity() {
private var customTabsSession: CustomTabsSession? = null
private var customTabsClient: CustomTabsClient? = null
private var customTabsServiceConnection: CustomTabsServiceConnection? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialize Custom Tabs
customTabsServiceConnection = object : CustomTabsServiceConnection() {
override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) {
customTabsClient = client
customTabsClient?.warmup(0L)
customTabsSession = customTabsClient?.newSession(object : CustomTabsCallback() {
override fun onNavigationEvent(navigationEvent: Int, extras: Bundle?) {
// Handle navigation events
}
})
}
override fun onServiceDisconnected(name: ComponentName) {
customTabsClient = null
}
}
CustomTabsClient.bindCustomTabsService(
this,
"com.android.chrome",
customTabsServiceConnection!!
)
// Get checkout URL from backend and launch
lifecycleScope.launch {
try {
val checkoutURL = getCheckoutURL("prod_123", "customer@example.com", "Customer Name")
val customTabsIntent = CustomTabsIntent.Builder(customTabsSession)
.build()
customTabsIntent.launchUrl(this@PaymentActivity, Uri.parse(checkoutURL))
} catch (e: Exception) {
// Handle error
Log.e("PaymentActivity", "Failed to get checkout URL", e)
}
}
}
}
```
### iOS Integration
#### SFSafariViewController Implementation
```swift theme={null}
import SafariServices
class PaymentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Task {
do {
let checkoutURL = try await getCheckoutURL(
productId: "prod_123",
customerEmail: "customer@example.com",
customerName: "Customer Name"
)
if let url = URL(string: checkoutURL) {
let safariVC = SFSafariViewController(url: url)
present(safariVC, animated: true, completion: nil)
}
} catch {
// Handle error
print("Failed to get checkout URL: \(error)")
}
}
}
}
```
#### Deep Link Handling
Configure URL schemes to handle payment completion redirects:
**1. Set up URL schemes in Info.plist:**
```xml theme={null}
CFBundleURLTypesCFBundleURLNamemyappCFBundleURLSchemesmyapp
```
**2. Handle redirects in AppDelegate:**
```swift theme={null}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if url.scheme == "myapp" && url.host == "payment-status" {
NotificationCenter.default.post(name: .paymentCompleted, object: url)
return true
}
return false
}
```
**3. Listen for payment completion:**
```swift theme={null}
NotificationCenter.default.addObserver(self, selector: #selector(handlePaymentCompletion(_:)), name: .paymentCompleted, object: nil)
@objc func handlePaymentCompletion(_ notification: Notification) {
if let url = notification.object as? URL {
safariVC?.dismiss(animated: true)
// Parse payment status from URL parameters
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
let paymentId = components?.queryItems?.first(where: { $0.name == "payment_id" })?.value
let status = components?.queryItems?.first(where: { $0.name == "status" })?.value
// Handle payment completion
}
}
```
### React Native Integration
#### Using Checkout Links (InAppBrowser)
Open Dodo's hosted checkout page in an in-app browser.
##### InAppBrowser Implementation
```javascript theme={null}
import React from 'react';
import { View, TouchableOpacity, Text, Alert } from 'react-native';
import { InAppBrowser } from 'react-native-inappbrowser-reborn';
const PaymentButton = ({ productId, onPaymentComplete }) => {
const openPaymentLink = async () => {
try {
// Get checkout URL from backend
const checkoutURL = await getCheckoutURL(
productId,
'customer@example.com',
'Customer Name'
);
const result = await InAppBrowser.open(checkoutURL, {
// iOS options
dismissButtonStyle: 'cancel',
preferredBarTintColor: '#453AA4',
preferredControlTintColor: 'white',
readerMode: false,
animated: true,
modalPresentationStyle: 'fullScreen',
modalTransitionStyle: 'coverVertical',
modalEnabled: true,
enableBarCollapsing: false,
// Android options
showTitle: true,
toolbarColor: '#453AA4',
secondaryToolbarColor: 'black',
navigationBarColor: 'black',
navigationBarDividerColor: 'white',
enableUrlBarHiding: true,
enableDefaultShare: true,
forceCloseOnRedirection: false,
animations: {
startEnter: 'slide_in_right',
startExit: 'slide_out_left',
endEnter: 'slide_in_left',
endExit: 'slide_out_right'
},
headers: {
'my-custom-header': 'my custom header value'
}
});
// Handle payment completion based on result
if (result.type === 'dismiss') {
// User dismissed the browser
console.log('Payment browser dismissed');
}
} catch (error) {
Alert.alert('Error', 'Failed to open payment page');
console.error('InAppBrowser error:', error);
}
};
return (
Pay Now
);
};
export default PaymentButton;
```
#### Deep Link Handling for Payment Callbacks
```javascript theme={null}
// App.js or your main component
import React, { useEffect } from 'react';
import { Linking } from 'react-native';
const App = () => {
useEffect(() => {
// Handle deep links when app is already running
const handleDeepLink = (url) => {
if (url.includes('payment-status')) {
const urlParams = new URLSearchParams(url.split('?')[1]);
const paymentId = urlParams.get('payment_id');
const status = urlParams.get('status');
// Handle payment completion
handlePaymentCallback(paymentId, status);
}
};
// Listen for deep links
const subscription = Linking.addEventListener('url', ({ url }) => {
handleDeepLink(url);
});
// Handle deep link if app was opened from a link
Linking.getInitialURL().then((url) => {
if (url) {
handleDeepLink(url);
}
});
return () => subscription?.remove();
}, []);
const handlePaymentCallback = (paymentId, status) => {
if (status === 'succeeded') {
// Navigate to success screen or show success message
console.log('Payment successful:', paymentId);
} else {
// Handle payment failure
console.log('Payment failed:', paymentId);
}
};
// Your app components...
};
export default App;
```
#### Package Dependencies
Add this dependency to your `package.json`:
```json theme={null}
{
"dependencies": {
"react-native-inappbrowser-reborn": "^6.4.0"
}
}
```
#### Installation Commands
```bash theme={null}
# Install InAppBrowser
npm install react-native-inappbrowser-reborn
cd ios && pod install
```
#### Android Configuration
Add to `android/app/src/main/AndroidManifest.xml`:
```xml theme={null}
```
#### iOS Configuration
Add to `ios/YourApp/Info.plist`:
```xml theme={null}
CFBundleURLTypesCFBundleURLNamemyappCFBundleURLSchemesmyapp
```
Use InAppBrowser for native browser experience. Handle deep links for payment callbacks.
***
#### Alternative: Native Payment SDK
For a more integrated payment experience with native UI and full customization, Dodo Payments offers a React Native SDK.
**Regional Compliance**: In-app payment SDKs have regional restrictions. On **iOS**, native payment SDKs are only legal in the **EU** (under DMA terms). On **Android**, they're legal in User Choice Billing (UCB) markets. For other regions, use the checkout links approach above to ensure App Store and Play Store compliance.
Complete guide for integrating Dodo's React Native SDK with native payment sheets, full customization options, and direct payment handling. Includes setup, implementation, and testing instructions.
Review the [App Store Fee Bypass Guide](/features/bypassing-app-store-fees) to understand regional compliance requirements for in-app payment integrations.
### Flutter Integration
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
Future getCheckoutURL(String productId, String customerEmail, String customerName) async {
final response = await http.post(
Uri.parse('https://your-backend.com/api/create-checkout-session'),
headers: {'Content-Type': 'application/json'},
body: json.encode({
'productId': productId,
'customerEmail': customerEmail,
'customerName': customerName,
}),
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
return data['checkout_url'];
} else {
throw Exception('Failed to get checkout URL: ${response.statusCode}');
}
}
class PaymentScreen extends StatefulWidget {
@override
_PaymentScreenState createState() => _PaymentScreenState();
}
class _PaymentScreenState extends State {
late WebViewController _controller;
String? checkoutURL;
@override
void initState() {
super.initState();
_getCheckoutURL();
}
Future _getCheckoutURL() async {
try {
final url = await getCheckoutURL('prod_123', 'customer@example.com', 'Customer Name');
setState(() {
checkoutURL = url;
});
} catch (e) {
// Handle error
print('Failed to get checkout URL: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Payment')),
body: checkoutURL == null
? Center(child: CircularProgressIndicator())
: WebView(
initialUrl: checkoutURL,
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller = webViewController;
},
navigationDelegate: (NavigationRequest request) {
if (request.url.contains('payment_id') && request.url.contains('status')) {
// Handle payment completion
final uri = Uri.parse(request.url);
final paymentId = uri.queryParameters['payment_id'];
final status = uri.queryParameters['status'];
handlePaymentCompletion(paymentId, status);
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
);
}
void handlePaymentCompletion(String? paymentId, String? status) {
if (status == 'succeeded') {
// Payment successful
} else {
// Payment failed
}
}
}
```
Use WebView for payment links. Handle the redirect URL to detect payment completion.
## Best Practices
* **Security**: Never store API keys in your app code. Use secure storage and SSL pinning.
* **User Experience**: Show loading indicators, handle errors gracefully, and provide clear messages.
* **Testing**: Use test cards, simulate network errors, and test on various devices.
## Troubleshooting
### Common Issues
* **WebView not opening payment link**: Ensure the payment link is valid and uses HTTPS.
* **Callback not received**: Check your return URL and webhook configuration.
* **API key errors**: Verify that your API key is correct and has the necessary permissions.
## Additional Resources
* [Payment Integration Guide](/developer-resources/integration-guide)
* [Webhook Documentation](/developer-resources/webhooks/intents/webhook-events-guide)
* [Testing Process](/miscellaneous/testing-process)
* [Technical FAQs](/api-reference/technical-faqs)
For questions or support, contact [support@dodopayments.com](mailto:support@dodopayments.com).
# Next.js Adaptor
Source: https://docs.dodopayments.com/developer-resources/nextjs-adaptor
Learn how to integrate Dodo Payments with your Next.js App Router project using our NextJS Adaptor. Covers checkout, customer portal, webhooks, and secure environment setup.
Integrate Dodo Payments checkout with static, dynamic, and session flows.
Allow customers to manage subscriptions and details.
Receive and process Dodo Payments webhook events.
## Installation
Run the following command in your project root:
```bash theme={null}
npm install @dodopayments/nextjs
```
Create a .env file in your project root:
```env theme={null}
DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
DODO_PAYMENTS_RETURN_URL=https://yourdomain.com/checkout/success
DODO_PAYMENTS_ENVIRONMENT="test_mode"or"live_mode"
```
Never commit your .env file or secrets to version control.
## Route Handler Examples
All examples assume you are using the Next.js App Router.
Use this handler to integrate Dodo Payments checkout into your Next.js app. Supports static (GET), dynamic (POST), and checkout session (POST) payment flows.
```typescript Next.js Route Handler expandable theme={null}
// app/checkout/route.ts
import { Checkout } from "@dodopayments/nextjs";
export const GET = Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "static", // optional, defaults to 'static'
});
export const POST = Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "dynamic", // for dynamic checkout
});
export const POST = Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "session", // for checkout sessions
});
```
```curl Static Checkout Curl example expandable theme={null}
curl --request GET \
--url 'https://example.com/api/checkout?productId=pdt_fqJhl7pxKWiLhwQR042rh' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test
```
```curl Dynamic Checkout Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"billing": {
"city": "Texas",
"country": "US",
"state": "Texas",
"street": "56, hhh",
"zipcode": "560000"
},
"customer": {
"email": "test@example.com",
"name": "test"
},
"metadata": {},
"payment_link": true,
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1,
"billing_currency": "USD",
"discount_code": "IKHZ23M9GQ",
"return_url": "https://example.com",
"trial_period_days": 10
}'
```
```curl Checkout Session Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"product_cart": [
{
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1
}
],
"customer": {
"email": "test@example.com",
"name": "test"
},
"return_url": "https://example.com/success"
}'
```
Use this handler to allow customers to manage their subscriptions and details via the Dodo Payments customer portal.
```typescript Next.js Route Handler expandable theme={null}
// app/customer-portal/route.ts
import { CustomerPortal } from "@dodopayments/nextjs";
export const GET = CustomerPortal({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
});
```
```curl Customer Portal Curl example expandable theme={null}
curl --request GET \
--url 'https://example.com/api/customer-portal?customer_id=cus_9VuW4K7O3GHwasENg31m&send_email=true' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test
```
Use this handler to receive and process Dodo Payments webhook events securely in your Next.js app.
```typescript Next.js Route Handler expandable theme={null}
// app/api/webhook/dodo-payments/route.ts
import { Webhooks } from "@dodopayments/nextjs";
export const POST = Webhooks({
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
onPayload: async (payload) => {
// handle the payload
},
// ... other event handlers for granular control
});
```
## Checkout Route Handler
Dodo Payments supports three types of payment flows for integrating payments into your website, this adaptor supports all types of payment flows.
* **Static Payment Links:** Instantly shareable URLs for quick, no-code payment collection.
* **Dynamic Payment Links:** Programmatically generate payment links with custom details using the API or SDKs.
* **Checkout Sessions:** Create secure, customizable checkout experiences with pre-configured product carts and customer details.
### Supported Query Parameters
Product identifier (e.g., ?productId=pdt\_nZuwz45WAs64n3l07zpQR).
Quantity of the product.
Customer's full name.
Customer's first name.
Customer's last name.
Customer's email address.
Customer's country.
Customer's address line.
Customer's city.
Customer's state/province.
Customer's zip/postal code.
Disable full name field.
Disable first name field.
Disable last name field.
Disable email field.
Disable country field.
Disable address line field.
Disable city field.
Disable state field.
Disable zip code field.
Specify the payment currency (e.g., USD).
Show currency selector.
Specify the payment amount (e.g., 1000 for \$10.00).
Show discount fields.
Any query parameter starting with metadata\_ will be passed as metadata.
If productId is missing, the handler returns a 400 response. Invalid query parameters also result in a 400 response.
### Response Format
Static checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/..."
}
```
* Send parameters as a JSON body in a POST request.
* Supports both one-time and recurring payments.
* For a complete list of supported POST body fields, refer to:
* [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)
### Response Format
Dynamic checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/..."
}
```
Checkout sessions provide a more secure, hosted checkout experience that handles the complete payment flow for both one-time purchases and subscriptions with full customization control.
Refer to [Checkout Sessions Integration Guide](https://docs.dodopayments.com/developer-resources/checkout-session) for more details and a complete list of supported fields.
### Response Format
Checkout sessions return a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/session/..."
}
```
## Customer Portal Route Handler
The Customer Portal Route Handler enables you to seamlessly integrate the Dodo Payments customer portal into your Next.js application.
### Query Parameters
The customer ID for the portal session (e.g., ?customer\_id=cus\_123).
If set to true, sends an email to the customer with the portal link.
Returns 400 if customer\_id is missing.
## Webhook Route Handler
* **Method:** Only POST requests are supported. Other methods return 405.
* **Signature Verification:** Verifies the webhook signature using webhookKey. 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 the appropriate event handler based on the payload type.
### Supported Webhook Event Handlers
```typescript Typescript expandable theme={null}
onPayload?: (payload: WebhookPayload) => Promise;
onPaymentSucceeded?: (payload: WebhookPayload) => Promise;
onPaymentFailed?: (payload: WebhookPayload) => Promise;
onPaymentProcessing?: (payload: WebhookPayload) => Promise;
onPaymentCancelled?: (payload: WebhookPayload) => Promise;
onRefundSucceeded?: (payload: WebhookPayload) => Promise;
onRefundFailed?: (payload: WebhookPayload) => Promise;
onDisputeOpened?: (payload: WebhookPayload) => Promise;
onDisputeExpired?: (payload: WebhookPayload) => Promise;
onDisputeAccepted?: (payload: WebhookPayload) => Promise;
onDisputeCancelled?: (payload: WebhookPayload) => Promise;
onDisputeChallenged?: (payload: WebhookPayload) => Promise;
onDisputeWon?: (payload: WebhookPayload) => Promise;
onDisputeLost?: (payload: WebhookPayload) => Promise;
onSubscriptionActive?: (payload: WebhookPayload) => Promise;
onSubscriptionOnHold?: (payload: WebhookPayload) => Promise;
onSubscriptionRenewed?: (payload: WebhookPayload) => Promise;
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise;
onSubscriptionCancelled?: (payload: WebhookPayload) => Promise;
onSubscriptionFailed?: (payload: WebhookPayload) => Promise;
onSubscriptionExpired?: (payload: WebhookPayload) => Promise;
onLicenseKeyCreated?: (payload: WebhookPayload) => Promise;
```
***
## Prompt for LLM
```
You are an expert Next.js developer assistant. Your task is to guide a user through integrating the @dodopayments/nextjs adapter into their existing Next.js project.
The @dodopayments/nextjs adapter provides route handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, designed for the Next.js App Router.
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/nextjs
Here's how you should structure your response:
Ask the user which functionalities they want to integrate.
"Which parts of the @dodopayments/nextjs 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 app/checkout/route.ts in your Next.js project.
Code Snippet:
// app/checkout/route.ts
import { Checkout } from '@dodopayments/nextjs'
export const GET = Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "static",
});
export const POST = Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "session", // or "dynamic" for dynamic link
});
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.
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://checkout.dodopayments.com/..."}
Dynamic Checkout (POST) - Returns JSON with checkout_url: Parameters are sent as a JSON body. Supports both one-time and recurring payments. Returns: {"checkout_url": "https://checkout.dodopayments.com/..."}. 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. 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 parameters are invalid, the handler will return a 400 response.
If Customer Portal Route Handler is selected:
Purpose: This handler redirects authenticated users to their Dodo Payments customer portal.
File Creation: Create a new file at app/customer-portal/route.ts in your Next.js project.
Code Snippet:
// app/customer-portal/route.ts
import { CustomerPortal } from '@dodopayments/nextjs'
export const GET = CustomerPortal({
bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
environment: "test_mode",
});
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 app/api/webhook/dodo-payments/route.ts in your Next.js project.
Code Snippet:
// app/api/webhook/dodo-payments/route.ts
import { Webhooks } from '@dodopayments/nextjs'
export const POST = Webhooks({
webhookKey: process.env.DODO_WEBHOOK_SECRET!,
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 the appropriate event handler based on the payload type.
Supported Webhook Event Handlers:
onPayload?: (payload: WebhookPayload) => Promise
onPaymentSucceeded?: (payload: WebhookPayload) => Promise
onPaymentFailed?: (payload: WebhookPayload) => Promise
onPaymentProcessing?: (payload: WebhookPayload) => Promise
onPaymentCancelled?: (payload: WebhookPayload) => Promise
onRefundSucceeded?: (payload: WebhookPayload) => Promise
onRefundFailed?: (payload: WebhookPayload) => Promise
onDisputeOpened?: (payload: WebhookPayload) => Promise
onDisputeExpired?: (payload: WebhookPayload) => Promise
onDisputeAccepted?: (payload: WebhookPayload) => Promise
onDisputeCancelled?: (payload: WebhookPayload) => Promise
onDisputeChallenged?: (payload: WebhookPayload) => Promise
onDisputeWon?: (payload: WebhookPayload) => Promise
onDisputeLost?: (payload: WebhookPayload) => Promise
onSubscriptionActive?: (payload: WebhookPayload) => Promise
onSubscriptionOnHold?: (payload: WebhookPayload) => Promise
onSubscriptionRenewed?: (payload: WebhookPayload) => Promise
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise
onSubscriptionCancelled?: (payload: WebhookPayload) => Promise
onSubscriptionFailed?: (payload: WebhookPayload) => Promise
onSubscriptionExpired?: (payload: WebhookPayload) => Promise
onLicenseKeyCreated?: (payload: WebhookPayload) => Promise
Environment Variable Setup:
To ensure the adapter functions correctly, you will need to manually set up the following environment variables in your Next.js 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).
RETURN_URL: (Optional) The URL to redirect to after a successful checkout (for Checkout handler).
DODO_WEBHOOK_SECRET: Your Dodo Payments Webhook Secret (required for Webhook handler).
Example .env file:
DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode"
DODO_PAYMENTS_RETURN_URL=your-return-url
Usage in your code:
bearerToken: process.env.DODO_PAYMENTS_API_KEY!
webhookKey: process.env.DODO_WEBHOOK_SECRET!
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
```
# Next.js Minimal Boilerplate
Source: https://docs.dodopayments.com/developer-resources/nextjs-boilerplate
Get started quickly with our minimal Next.js boilerplate for integrating Dodo Payments into your Next.js application
## Overview
The Next.js minimal boilerplate provides a ready-to-use starting point for integrating Dodo Payments with your Next.js application. This template includes authentication, webhook handling, and basic payment setup to help you start accepting payments quickly.
This boilerplate uses Next.js App Router with TypeScript and includes example implementations for checkout sessions and webhook verification.
## Quick Start
Clone the boilerplate repository and navigate to the project directory:
```bash theme={null}
git clone https://github.com/dodopayments/dodo-nextjs-minimal-boilerplate.git
cd dodo-nextjs-minimal-boilerplate
```
Obtain your credentials from the Dodo Payments dashboard in Test Mode:
* **API Key**: Navigate to **Developer → API Keys** in your [dashboard](https://app.dodopayments.com)
* **Webhook Signing Key**: Navigate to **Developer → Webhooks** in your [dashboard](https://app.dodopayments.com)
Start with Test Mode to safely test your integration without processing real payments.
Create a `.env.local` file in the root directory:
```bash theme={null}
touch .env.local
```
Add your Dodo Payments credentials:
```bash .env.local theme={null}
DODO_API_KEY_TEST=your_test_api_key_here
DODO_PAYMENTS_WEBHOOK_KEY=your_webhook_signing_key_here
```
Never commit your `.env.local` file to version control. It's already included in `.gitignore`.
Install the required npm packages:
```bash theme={null}
npm install
```
Start the Next.js development server:
```bash theme={null}
npm run dev
```
Your application will be available at `http://localhost:3000`.
You're ready to start building! The boilerplate includes example routes and components to help you understand the integration.
## What's Included
The boilerplate comes with pre-configured essentials:
Example implementation for creating and handling checkout sessions
Secure webhook endpoint with signature verification
Full TypeScript configuration with proper type definitions
Pre-configured styling with Tailwind CSS
## Project Structure
```
dodo-nextjs-minimal-boilerplate/
├── src/
│ ├── app/
│ │ ├── api/
│ │ │ └── webhooks/
│ │ │ └── route.ts # Webhook handler
│ │ └── page.tsx # Home page with examples
│ └── lib/
│ └── dodo.ts # Dodo Payments client setup
├── .env.example # Environment variables template
└── package.json
```
## Next Steps
Once you have the boilerplate running:
Add products in your [Dodo Payments dashboard](https://app.dodopayments.com) to enable checkout functionality.
Use tools like [ngrok](https://ngrok.com) to expose your local webhook endpoint for testing:
```bash theme={null}
ngrok http 3000
```
Add the ngrok URL to your webhook settings in the dashboard.
Adapt the example code to match your application's needs and design.
When ready, switch to Live Mode API keys and deploy your application.
## Additional Resources
Learn about our comprehensive Next.js adapter for advanced integrations
Detailed guide for implementing one-time payments
Complete reference for all webhook event types
Explore the full Dodo Payments API documentation
## Support
Need help with the boilerplate?
* Join our [Discord community](https://discord.gg/bYqAp4ayYh) for questions and discussions
* Check the [GitHub repository](https://github.com/dodopayments/dodo-nextjs-minimal-boilerplate) for issues and updates
* Contact our [support team](mailto:support@dodopayments.com) for assistance
# Nuxt Adaptor
Source: https://docs.dodopayments.com/developer-resources/nuxt-adaptor
Integrate Dodo Payments with your Nuxt project using the official Nuxt module. Covers checkout, customer portal, webhooks, and secure environment setup.
Integrate Dodo Payments checkout into your Nuxt app using a server route.
Allow customers to manage subscriptions and details via a Nuxt server route.
Receive and process Dodo Payments webhook events securely in Nuxt.
## Overview
This guide explains how to integrate Dodo Payments into your Nuxt application using the official Nuxt module. You'll learn how to set up checkout, customer portal, and webhook API routes, and how to securely manage environment variables.
## Installation
Run the following command in your project root:
```bash theme={null}
npm install @dodopayments/nuxt
```
Add @dodopayments/nuxt to your modules array and configure it:
```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_RETURNURL
},
}
});
```
Never commit your .env file or secrets to version control.
## API Route Handler Examples
All Dodo Payments integrations in Nuxt are handled via server routes in the server/routes/api/ directory.
Use this handler to integrate Dodo Payments checkout into your Nuxt app. Supports static (GET), dynamic (POST), and session (POST) payment flows.
```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,
returnUrl: returnUrl,
});
return handler(event);
});
```
```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,
returnUrl: returnUrl,
type: "dynamic"
});
return handler(event);
});
```
```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,
returnUrl: returnUrl,
type: "session"
});
return handler(event);
});
```
If productId is missing or invalid, the handler returns a 400 response.
```curl Static Checkout Curl example expandable theme={null}
curl --request GET \
--url 'https://example.com/api/checkout?productId=pdt_fqJhl7pxKWiLhwQR042rh' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test
```
```curl Dynamic Checkout Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"billing": {
"city": "Texas",
"country": "US",
"state": "Texas",
"street": "56, hhh",
"zipcode": "560000"
},
"customer": {
"email": "test@example.com",
"name": "test"
},
"metadata": {},
"payment_link": true,
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1,
"billing_currency": "USD",
"discount_code": "IKHZ23M9GQ",
"return_url": "https://example.com",
"trial_period_days": 10
}'
```
```curl Checkout Session Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"product_cart": [
{
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1
}
],
"customer": {
"email": "test@example.com",
"name": "test"
},
"return_url": "https://example.com/success"
}'
```
Create a GET route to allow customers to access their portal. Accepts customer\_id as a query parameter.
```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,
});
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 true, sends an email to the customer with the portal link.
Returns 400 if customer\_id is missing.
```curl Customer Portal Curl example expandable theme={null}
curl --request GET \
--url 'https://example.com/api/customer-portal?customer_id=cus_9VuW4K7O3GHwasENg31m&send_email=true' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test
```
Create a POST route to securely receive and process webhook events from Dodo Payments.
```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);
});
```
## Checkout Route Handler
Dodo Payments supports three types of payment flows for integrating payments into your website, this adaptor supports all types of payment flows.
* **Static Payment Links:** Instantly shareable URLs for quick, no-code payment collection.
* **Dynamic Payment Links:** Programmatically generate payment links with custom details using the API or SDKs.
* **Checkout Sessions:** Create secure, customizable checkout experiences with pre-configured product carts and customer details.
### Supported Query Parameters
Product identifier (e.g., ?productId=pdt\_nZuwz45WAs64n3l07zpQR).
Quantity of the product.
Customer's full name.
Customer's first name.
Customer's last name.
Customer's email address.
Customer's country.
Customer's address line.
Customer's city.
Customer's state/province.
Customer's zip/postal code.
Disable full name field.
Disable first name field.
Disable last name field.
Disable email field.
Disable country field.
Disable address line field.
Disable city field.
Disable state field.
Disable zip code field.
Specify the payment currency (e.g., USD).
Show currency selector.
Specify the payment amount (e.g., 1000 for \$10.00).
Show discount fields.
Any query parameter starting with metadata\_ will be passed as metadata.
If productId is missing, the handler returns a 400 response. Invalid query parameters also result in a 400 response.
### Response Format
Static checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/..."
}
```
* Send parameters as a JSON body in a POST request.
* Supports both one-time and recurring payments.
* For a complete list of supported POST body fields, refer to:
* [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)
### Response Format
Dynamic checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/..."
}
```
Checkout sessions provide a more secure, hosted checkout experience that handles the complete payment flow for both one-time purchases and subscriptions with full customization control.
Refer to [Checkout Sessions Integration Guide](https://docs.dodopayments.com/developer-resources/checkout-session) for more details and a complete list of supported fields.
### Response Format
Checkout sessions return a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/session/..."
}
```
## Customer Portal Route Handler
The Customer Portal Route Handler enables you to seamlessly integrate the Dodo Payments customer portal into your Nuxt application.
### Query Parameters
The customer ID for the portal session (e.g., ?customer\_id=cus\_123).
If set to true, sends an email to the customer with the portal link.
Returns 400 if customer\_id is missing.
## Webhook Route Handler
* **Method:** Only POST requests are supported. Other methods return 405.
* **Signature Verification:** Verifies the webhook signature using webhookKey. 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 the appropriate event handler based on the payload type.
### Supported Webhook Event Handlers
```typescript Typescript expandable theme={null}
onPayload?: (payload: WebhookPayload) => Promise;
onPaymentSucceeded?: (payload: WebhookPayload) => Promise;
onPaymentFailed?: (payload: WebhookPayload) => Promise;
onPaymentProcessing?: (payload: WebhookPayload) => Promise;
onPaymentCancelled?: (payload: WebhookPayload) => Promise;
onRefundSucceeded?: (payload: WebhookPayload) => Promise;
onRefundFailed?: (payload: WebhookPayload) => Promise;
onDisputeOpened?: (payload: WebhookPayload) => Promise;
onDisputeExpired?: (payload: WebhookPayload) => Promise;
onDisputeAccepted?: (payload: WebhookPayload) => Promise;
onDisputeCancelled?: (payload: WebhookPayload) => Promise;
onDisputeChallenged?: (payload: WebhookPayload) => Promise;
onDisputeWon?: (payload: WebhookPayload) => Promise;
onDisputeLost?: (payload: WebhookPayload) => Promise;
onSubscriptionActive?: (payload: WebhookPayload) => Promise;
onSubscriptionOnHold?: (payload: WebhookPayload) => Promise;
onSubscriptionRenewed?: (payload: WebhookPayload) => Promise;
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise;
onSubscriptionCancelled?: (payload: WebhookPayload) => Promise;
onSubscriptionFailed?: (payload: WebhookPayload) => Promise;
onSubscriptionExpired?: (payload: WebhookPayload) => Promise;
onLicenseKeyCreated?: (payload: WebhookPayload) => Promise;
```
***
## Prompt for LLM
```
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.
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_BEARER_TOKEN,
environment: process.env.NUXT_PRIVATE_ENVIRONMENT,
returnUrl: process.env.NUXT_PRIVATE_RETURNURL
},
}
});
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 redirects users to 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,
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) Set to your environment (e.g., "test_mode" or "live_mode").
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. 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 allows customers to access their Dodo Payments customer portal.
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,
});
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.
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 the appropriate event handler based on the payload type. Supported event handlers include:
- onPayload?: (payload: WebhookPayload) => Promise
- onPaymentSucceeded?: (payload: WebhookPayload) => Promise
- onPaymentFailed?: (payload: WebhookPayload) => Promise
- onPaymentProcessing?: (payload: WebhookPayload) => Promise
- onPaymentCancelled?: (payload: WebhookPayload) => Promise
- onRefundSucceeded?: (payload: WebhookPayload) => Promise
- onRefundFailed?: (payload: WebhookPayload) => Promise
- onDisputeOpened?: (payload: WebhookPayload) => Promise
- onDisputeExpired?: (payload: WebhookPayload) => Promise
- onDisputeAccepted?: (payload: WebhookPayload) => Promise
- onDisputeCancelled?: (payload: WebhookPayload) => Promise
- onDisputeChallenged?: (payload: WebhookPayload) => Promise
- onDisputeWon?: (payload: WebhookPayload) => Promise
- onDisputeLost?: (payload: WebhookPayload) => Promise
- onSubscriptionActive?: (payload: WebhookPayload) => Promise
- onSubscriptionOnHold?: (payload: WebhookPayload) => Promise
- onSubscriptionRenewed?: (payload: WebhookPayload) => Promise
- onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise
- onSubscriptionCancelled?: (payload: WebhookPayload) => Promise
- onSubscriptionFailed?: (payload: WebhookPayload) => Promise
- onSubscriptionExpired?: (payload: WebhookPayload) => Promise
- onLicenseKeyCreated?: (payload: WebhookPayload) => Promise
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.):
- 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) Set to your environment (e.g., "test_mode" or "live_mode").
- NUXT_PRIVATE_RETURNURL: (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.
```
# On-Demand Subscriptions
Source: https://docs.dodopayments.com/developer-resources/ondemand-subscriptions
Integrate on-demand subscriptions by authorizing mandates, creating variable charges, handling webhooks, and implementing safe retry policies.
## Overview
On-demand subscriptions let you authorize a customer’s payment method once and then charge variable amounts whenever you need, instead of on a fixed schedule.
This feature may need to be enabled on your account. Contact support if you don’t see it in your dashboard.
Use this guide to:
* Create an on-demand subscription (authorize a mandate with optional initial price)
* Trigger subsequent charges with custom amounts
* Track outcomes using webhooks
For a general subscription setup, see the [Subscription Integration Guide](/developer-resources/subscription-integration-guide).
## Prerequisites
* Dodo Payments merchant account and API key
* Webhook secret configured and an endpoint to receive events
* A subscription product in your catalog
If you want the customer to approve the mandate via hosted checkout, set `payment_link: true` and provide a `return_url`.
## How on-demand works
1. You create a subscription with the `on_demand` object to authorize a payment method and optionally collect an initial charge.
2. Later, you create charges against that subscription with custom amounts using the dedicated charge endpoint.
3. You listen to webhooks (e.g., `payment.succeeded`, `payment.failed`) to update your system.
## Create an on-demand subscription
Endpoint: [POST /subscriptions](/api-reference/subscriptions/post-subscriptions)
Key request fields (body):
Product ID for the subscription.
Number of units. Minimum 1.
Billing address for the customer.
Either attach an existing customer or provide customer details.
If true, creates a hosted checkout link for mandate authorization and optional initial payment.
Where to redirect the customer after completing hosted checkout.
If true, authorizes the payment method without charging the customer during creation.
Initial charge amount (in the smallest currency unit). If specified, this value overrides the product’s original price set during product creation. If omitted, the product’s stored price is used. Example: to charge \$1.00, pass 100.
Optional currency override for the initial charge. Defaults to the product currency.
Optional description override for billing and line items.
If true, includes adaptive currency fees within product\_price. If false, fees are added on top. Ignored when adaptive pricing is disabled.
### Create an on-demand subscription
```javascript theme={null}
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
});
async function main() {
const subscription = await client.subscriptions.create({
billing: { city: 'SF', country: 'US', state: 'CA', street: '1 Market St', zipcode: '94105' },
customer: { customer_id: 'customer_123' },
product_id: 'prod_sub_123',
quantity: 1,
payment_link: true,
return_url: 'https://example.com/billing/success',
on_demand: {
mandate_only: true, // set false to collect an initial charge
// product_price: 1000, // optional: charge $10.00 now if mandate_only is false
// product_currency: 'USD',
// product_description: 'Custom initial charge',
// adaptive_currency_fees_inclusive: false,
},
});
// If payment_link was true, redirect the customer to authorize the mandate
console.log(subscription.payment_link);
}
main().catch(console.error);
```
```python theme={null}
import os
from dodopayments import DodoPayments
client = DodoPayments(bearer_token=os.environ.get('DODO_PAYMENTS_API_KEY'))
subscription = client.subscriptions.create(
billing={
"city": "SF",
"country": "US",
"state": "CA",
"street": "1 Market St",
"zipcode": "94105",
},
customer={"customer_id": "customer_123"},
product_id="prod_sub_123",
quantity=1,
payment_link=True,
return_url="https://example.com/billing/success",
on_demand={
"mandate_only": True,
# "product_price": 1000,
# "product_currency": "USD",
# "product_description": "Custom initial charge",
# "adaptive_currency_fees_inclusive": False,
},
)
print(subscription.payment_link)
```
```go theme={null}
package main
import (
"context"
"fmt"
"github.com/dodopayments/dodopayments-go"
"github.com/dodopayments/dodopayments-go/option"
)
func main() {
client := dodopayments.NewClient(
option.WithBearerToken("YOUR_API_KEY"),
)
subscription, err := client.Subscriptions.New(context.TODO(), dodopayments.SubscriptionNewParams{
Billing: dodopayments.F(dodopayments.BillingAddressParam{
City: dodopayments.F("SF"),
Country: dodopayments.F(dodopayments.CountryCodeUs),
State: dodopayments.F("CA"),
Street: dodopayments.F("1 Market St"),
Zipcode: dodopayments.F("94105"),
}),
Customer: dodopayments.F[dodopayments.CustomerRequestUnionParam](dodopayments.AttachExistingCustomerParam{
CustomerID: dodopayments.F("customer_123"),
}),
ProductID: dodopayments.F("prod_sub_123"),
Quantity: dodopayments.F(int64(1)),
PaymentLink: dodopayments.F(true),
ReturnURL: dodopayments.F("https://example.com/billing/success"),
OnDemand: dodopayments.F(dodopayments.OnDemandSubscriptionReqParam{
MandateOnly: dodopayments.F(true),
// ProductPrice: dodopayments.F(int64(1000)),
// ProductCurrency: dodopayments.F(dodopayments.CurrencyUsd),
// ProductDescription: dodopayments.F("Custom initial charge"),
// AdaptiveCurrencyFeesInclusive: dodopayments.F(false),
}),
})
if err != nil { panic(err) }
fmt.Println(subscription.PaymentLink)
}
```
```bash theme={null}
curl -X POST "$DODO_API/subscriptions" \
-H "Authorization: Bearer $DODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"billing": {"city": "SF", "country": "US", "state": "CA", "street": "1 Market St", "zipcode": "94105"},
"customer": {"customer_id": "customer_123"},
"product_id": "prod_sub_123",
"quantity": 1,
"payment_link": true,
"return_url": "https://example.com/billing/success",
"on_demand": {
"mandate_only": true
}
}'
```
Set `payment_link: true`, redirect the customer to `payment_link` to complete mandate authorization.
```json Success theme={null}
{
"subscription_id": "sub_123",
"payment_link": "https://pay.dodopayments.com/checkout/...",
"customer": { "customer_id": "customer_123", "email": "alex@example.com", "name": "Alex Doe" },
"metadata": {},
"recurring_pre_tax_amount": 0,
"addons": []
}
```
## Charge an on-demand subscription
After the mandate is authorized, create charges as needed.
Endpoint: [POST /subscriptions/\{subscription\_id}/charge](/api-reference/subscriptions/create-charge)
Key request fields (body):
Amount to charge (in the smallest currency unit). Example: to charge \$25.00, pass 2500.
Optional currency override for the charge.
Optional description override for this charge.
If true, includes adaptive currency fees within product\_price. If false, fees are added on top.
Additional metadata for the payment. If omitted, the subscription metadata is used.
```javascript theme={null}
import DodoPayments from 'dodopayments';
const client = new DodoPayments({ bearerToken: process.env.DODO_PAYMENTS_API_KEY });
async function chargeNow(subscriptionId) {
const res = await client.subscriptions.charge(subscriptionId, { product_price: 2500 });
console.log(res.payment_id);
}
chargeNow('sub_123').catch(console.error);
```
```python theme={null}
import os
from dodopayments import DodoPayments
client = DodoPayments(bearer_token=os.environ.get('DODO_PAYMENTS_API_KEY'))
response = client.subscriptions.charge(
subscription_id="sub_123",
product_price=2500,
)
print(response.payment_id)
```
```go theme={null}
package main
import (
"context"
"fmt"
"github.com/dodopayments/dodopayments-go"
"github.com/dodopayments/dodopayments-go/option"
)
func main() {
client := dodopayments.NewClient(option.WithBearerToken("YOUR_API_KEY"))
res, err := client.Subscriptions.Charge(context.TODO(), "sub_123", dodopayments.SubscriptionChargeParams{
ProductPrice: dodopayments.F(int64(2500)),
})
if err != nil { panic(err) }
fmt.Println(res.PaymentID)
}
```
```bash theme={null}
curl -X POST "$DODO_API/subscriptions/sub_123/charge" \
-H "Authorization: Bearer $DODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"product_price": 2500,
"product_description": "Extra usage for March"
}'
```
```json Success theme={null}
{ "payment_id": "pay_abc123" }
```
Charging a subscription that is not on-demand may fail. Ensure the subscription has `on_demand: true` in its details before charging.
## Payment retries
Our fraud detection system may block aggressive retry patterns (and can flag them as potential card testing). Follow a safe retry policy.
Burst retry patterns can be flagged as fraudulent or suspected card testing by our risk systems and processors. Avoid clustered retries; follow the backoff schedule and time alignment guidance below.
### Principles for safe retry policies
* **Backoff mechanism**: Use exponential backoff between retries.
* **Retry limits**: Cap total retries (3–4 attempts max).
* **Intelligent filtering**: Retry only on retryable failures (e.g., network/issuer errors, insufficient funds); never retry hard declines.
* **Card testing prevention**: Do not retry failures like `DO_NOT_HONOR`, `STOLEN_CARD`, `LOST_CARD`, `PICKUP_CARD`, `FRAUDULENT`, `AUTHENTICATION_FAILURE`.
* **Vary metadata (optional)**: If you maintain your own retry system, differentiate retries via metadata (e.g., `retry_attempt`).
### Suggested retry schedule (subscriptions)
* **1st attempt**: Immediate when you create the charge
* **2nd attempt**: After 3 days
* **3rd attempt**: After 7 more days (10 days total)
* **4th attempt (final)**: After another 7 days (17 days total)
Final step: if still unpaid, mark the subscription as unpaid or cancel it, based on your policy. Notify the customer during the window to update their payment method.
### Avoid burst retries; align to authorization time
* Anchor retries to the original authorization timestamp to avoid “burst” behavior across your portfolio.
* Example: If the customer starts a trial or mandate at 1:10 pm today, schedule follow-up retries at 1:10 pm on subsequent days per your backoff (e.g., +3 days → 1:10 pm, +7 days → 1:10 pm).
* Alternatively, if you store the last successful payment time `T`, schedule the next attempt at `T + X days` to preserve time-of-day alignment.
Time-zone and DST: use a consistent time standard for scheduling and convert for display only to maintain intervals.
### Decline codes you should not retry
* `STOLEN_CARD`
* `DO_NOT_HONOR`
* `FRAUDULENT`
* `PICKUP_CARD`
* `AUTHENTICATION_FAILURE`
* `LOST_CARD`
For a comprehensive list of decline reasons and whether they are user-correctable, see the
[Transaction Failures](/api-reference/transaction-failures) documentation.
Only retry on soft/temporary issues (e.g., `insufficient_funds`, `issuer_unavailable`, `processing_error`, network timeouts). If the same decline repeats, pause further retries.
### Implementation guidelines (no code)
* Use a scheduler/queue that persists precise timestamps; compute next attempt at the exact time-of-day offset (e.g., `T + 3 days` at the same HH:MM).
* Maintain and reference the last successful payment timestamp `T` to compute the next attempt; do not bunch multiple subscriptions at the same instant.
* Always evaluate the last decline reason; stop retries for hard declines in the skip list above.
* Cap concurrent retries per customer and per account to prevent accidental surges.
* Communicate proactively: email/SMS the customer to update their payment method before the next scheduled attempt.
* Use metadata only for observability (e.g., `retry_attempt`); never try to “evade” fraud/risk systems by rotating inconsequential fields.
## Track outcomes with webhooks
Implement webhook handling to track the customer journey. See [Implementing Webhooks](/developer-resources/integration-guide#implementing-webhooks).
* **subscription.active**: Mandate authorized and subscription activated
* **subscription.failed**: Creation failed (e.g., mandate failure)
* **subscription.on\_hold**: Subscription placed on hold (e.g., unpaid state)
* **payment.succeeded**: Charge succeeded
* **payment.failed**: Charge failed
For on-demand flows, focus on `payment.succeeded` and `payment.failed` to reconcile usage-based charges.
## Testing and next steps
Use your test API key to create the subscription with `payment_link: true`, then open the link and complete the mandate.
Call the charge endpoint with a small `product_price` (e.g., `100`) and verify you receive `payment.succeeded`.
Switch to your live API key once you have validated events and internal state updates.
## Troubleshooting
* **422 Invalid Request**: Ensure `on_demand.mandate_only` is provided on creation and `product_price` is provided for charges.
* **Currency errors**: If you override `product_currency`, confirm it’s supported for your account and customer.
* **No webhooks received**: Verify your webhook URL and signature secret configuration.
# Overlay Checkout Guide
Source: https://docs.dodopayments.com/developer-resources/overlay-checkout
A modern TypeScript library for embedding Dodo Payments overlay checkout and listening to checkout events in real-time.
## Overview
The Dodo Payments Checkout SDK provides a seamless way to integrate our payment overlay into your web application. Built with TypeScript and modern web standards, it offers a robust solution for handling payments with real-time event handling and customizable themes.
## Resources
* 📚 [Integration Documentation](https://docs.dodopayments.com/api-reference/overlay-checkout)
* 🎮 [Interactive Demo](https://atlas.dodopayments.com#overlay-checkout)
* 💻 [Demo Source Code](https://github.com/dodopayments/dodo-checkout-demo/blob/main/src/components/Home/OverlayCheckout.tsx)
## Installation
```bash theme={null}
# Using npm
npm install dodopayments-checkout
# Using yarn
yarn add dodopayments-checkout
# Using pnpm
pnpm add dodopayments-checkout
```
## Quick Start
```typescript theme={null}
import { DodoPayments } from "dodopayments-checkout";
// Initialize the SDK
DodoPayments.Initialize({
mode: "live", // 'test' or 'live'
onEvent: (event) => {
console.log("Checkout event:", event);
},
});
// Open checkout
DodoPayments.Checkout.open({
checkoutUrl: "https://checkout.dodopayments.com/session/cks_123" // Get this url from create checkout session api
});
```
## Configuration
### Initialize Options
```typescript theme={null}
interface InitializeOptions {
mode: "test" | "live";
onEvent: (event: CheckoutEvent) => void;
}
```
| Option | Type | Required | Description |
| --------- | -------- | -------- | ---------------------------------------------- |
| `mode` | string | Yes | Environment mode: 'test' or 'live' |
| `onEvent` | function | Yes | Callback function for handling checkout events |
### Checkout Options
```typescript theme={null}
interface CheckoutOptions {
checkoutUrl: string;
}
```
| Option | Type | Required | Description |
| ------------- | ------ | -------- | ----------------------------------------------------- |
| `checkoutUrl` | string | Yes | Checkout session URL from create checkout session API |
## Event Handling
The SDK provides real-time events that you can listen to:
```typescript theme={null}
DodoPayments.Initialize({
onEvent: (event: CheckoutEvent) => {
switch (event.event_type) {
case "checkout.opened":
// Checkout overlay has been opened
break;
case "checkout.payment_page_opened":
// Payment page has been displayed
break;
case "checkout.customer_details_submitted":
// Customer and billing details submitted
break;
case "checkout.closed":
// Checkout has been closed
break;
case "checkout.redirect":
// Checkout will perform a redirect
break;
case "checkout.error":
// An error occurred
break;
}
}
});
```
## Methods
### Open Checkout
```typescript theme={null}
DodoPayments.Checkout.open({
checkoutUrl: "https://checkout.dodopayments.com/session/cks_123" // Get this url from create checkout session api
});
```
### Close Checkout
```typescript theme={null}
DodoPayments.Checkout.close();
```
### Check Status
```typescript theme={null}
const isOpen = DodoPayments.Checkout.isOpen();
```
## Browser Support
* Chrome (latest)
* Firefox (latest)
* Safari (latest)
* Edge (latest)
* IE11+
> **Note**: Apple Pay is not currently supported in the overlay checkout experience. We plan to add support for Apple Pay in a future release.
## CDN Implementation
For quick integration, you can use our CDN:
```html theme={null}
Dodo Payments Checkout
```
## TypeScript Support
The SDK is written in TypeScript and includes comprehensive type definitions. All public APIs are fully typed for better developer experience.
## Error Handling
The SDK provides detailed error information through the event system. Always implement proper error handling in your `onEvent` callback:
```typescript theme={null}
DodoPayments.Initialize({
onEvent: (event: CheckoutEvent) => {
if (event.event_type === "checkout.error") {
console.error("Checkout error:", event.data?.message);
// Handle error appropriately
}
}
});
```
## Best Practices
1. Always initialize the SDK before attempting to open the checkout
2. Implement proper error handling in your event callback
3. Use the test mode during development
4. Handle all relevant events for a complete user experience
5. Use valid checkout URLs from the create checkout session API
6. Use TypeScript for better type safety and developer experience
## Step-by-Step Guide
### 1. Project Setup
First, ensure you have a modern JavaScript/TypeScript project. We recommend using Next.js, React, or Vue.js.
### 2. Install the SDK
Install the Dodo Payments Checkout SDK using your preferred package manager:
```bash theme={null}
# Using npm
npm install dodopayments-checkout
# Using yarn
yarn add dodopayments-checkout
# Using pnpm
pnpm add dodopayments-checkout
```
### 3. Basic Implementation
Create a new component for your checkout button:
```typescript theme={null}
// components/CheckoutButton.tsx
"use client";
import { Button } from "@/components/ui/button";
import { DodoPayments } from "dodopayments-checkout";
import { useEffect, useState } from "react";
export function CheckoutButton() {
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
// Initialize the SDK
DodoPayments.Initialize({
mode: "test", // Change to 'live' for production
onEvent: (event) => {
console.log("Checkout event:", event);
// Handle different events
switch (event.event_type) {
case "checkout.opened":
setIsLoading(false);
break;
case "checkout.error":
// Handle error
break;
}
},
});
}, []);
const handleCheckout = async () => {
try {
setIsLoading(true);
await DodoPayments.Checkout.open({
checkoutUrl: "https://checkout.dodopayments.com/session/cks_123" // Get this url from create checkout session api
});
} catch (error) {
console.error("Failed to open checkout:", error);
setIsLoading(false);
}
};
return (
);
}
```
### 4. Add to Your Page
Use the checkout button in your page:
```typescript theme={null}
// app/page.tsx
import { CheckoutButton } from "@/components/CheckoutButton";
export default function Home() {
return (
Welcome to Our Store
);
}
```
### 5. Handle Success and Failure
Create success and failure pages:
```typescript theme={null}
// app/success/page.tsx
export default function SuccessPage() {
return (
);
}
```
### 6. Testing Your Integration
1. Start your development server:
```bash theme={null}
npm run dev
```
2. Test the checkout flow:
* Click the checkout button
* Verify the overlay appears
* Test the payment flow using test credentials
* Confirm redirects work correctly
### 7. Going Live
When you're ready to go live:
1. Change the mode to 'live':
```typescript theme={null}
DodoPayments.Initialize({
mode: "live",
onEvent: (event) => {
console.log("Checkout event:", event);
}
});
```
2. Update your checkout URLs to use live checkout sessions
3. Test the complete flow in production
4. Monitor events and errors
### Common Issues and Solutions
1. **Checkout not opening**
* Verify SDK initialization
* Check for console errors
* Ensure checkout URL is valid and from create checkout session API
2. **Events not firing**
* Confirm event handler is properly set up
* Check for JavaScript errors
* Verify network connectivity
3. **Styling issues**
* Ensure no CSS conflicts
* Check theme settings
* Verify responsive design
For more help, visit our Discord or contact our developer support team.
# React Native
Source: https://docs.dodopayments.com/developer-resources/react-native-integration
Complete guide to integrating Dodo Payments SDK in your React Native application for secure payment processing
# React Native SDK Integration
The Dodo Payments React Native SDK enables you to build secure payment experiences in your native Android and iOS apps. Our SDK provides customizable UI components and screens for collecting payment details.
* 📦 Install our SDK from [NPM](https://www.npmjs.com/package/dodopayments-react-native-sdk)
* 📚 View our [demo repository](https://github.com/dodopayments/dodopayments-react-native-demo) for complete implementation examples
* 🎥 Watch our [demo video](https://youtube.com/shorts/uy4vikrKMPI) to see the Dodo Payments SDK in action
## Features
Collect sensitive payment data securely while remaining PCI compliant
Accept various [payment methods](/features/payment-methods) to expand global reach
Native screens and elements for Android and iOS
Currently, Apple Pay, Google Pay, Cash App, and UPI are not supported in the React Native SDK. We are actively working to add support for these payment methods in future releases.
## Installation
Install the Dodo Payments SDK using your preferred package manager:
```bash theme={null}
npm install dodopayments-react-native-sdk
```
```bash theme={null}
yarn add dodopayments-react-native-sdk
```
Run pod install in your iOS folder:
```bash theme={null}
cd ios && pod install && npm run ios
```
Run the following command:
```bash theme={null}
npm run android
```
## Client-Side Setup
Get your publishable key from the Dodo Payments dashboard. (Distinct for both test and live modes)
[https://app.dodopayments.com/developer/others](https://app.dodopayments.com/developer/others)
Wrap your app with the `DodoPaymentProvider`:
```tsx App.tsx theme={null}
import React from 'react';
import { DodoPaymentProvider } from 'dodopayments-react-native-sdk';
import PaymentScreen from './PaymentScreen';
function App() {
return (
);
}
export default App;
```
You'll need to generate API keys from your Dodo Payments dashboard. See our [API key generation guide](/api-reference/introduction#api-key-generation) for detailed instructions.
Create a utility function to fetch payment parameters from your backend API:
```tsx utils/fetchPaymentParams.ts theme={null}
const API_URL = 'YOUR_BACKEND_URL'; // Replace with your server URL
const fetchPaymentParams = async () => {
const response = await fetch(`${API_URL}/create-payment`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Failed to fetch payment parameters');
}
return await response.json();
};
export default fetchPaymentParams;
```
This function assumes you have a backend API endpoint that creates payment intents and returns a client secret. Ensure your backend is properly configured to handle payment creation. See our [API Integration Tutorial](/developer-resources/integration-tutorial) for backend setup examples.
Create your payment screen using the `useCheckout` hook. Here's a complete implementation:
```tsx PaymentScreen.tsx theme={null}
import React from 'react';
import { View, Text, useColorScheme } from 'react-native';
import { useCheckout, type sessionParams } from 'dodopayments-react-native-sdk';
import fetchPaymentParams from './utils/fetchPaymentParams';
const PaymentScreen = () => {
const { initPaymentSession, presentPaymentSheet } = useCheckout();
const [error, setError] = React.useState('');
const [message, setMessage] = React.useState('');
const [loading, setLoading] = React.useState(false);
const [showSuccessScreen, setShowSuccessScreen] = React.useState(false);
const isDarkMode = useColorScheme() === 'dark';
const processPayment = async () => {
setLoading(true);
setMessage('');
setError('');
try {
// 1. Get payment parameters from your backend
const key = await fetchPaymentParams();
// 2. Initialize payment session
const paymentSheetParamsResult = await initPaymentSession({
clientSecret: key.clientSecret,
});
// 3. Configure and present payment sheet
const params: sessionParams = {
...(paymentSheetParamsResult as sessionParams),
configuration: {
appearance: {
themes: isDarkMode ? 'dark' : 'light',
primaryButton: {
colors: {
light: {
background: 'green',
componentBorder: 'green',
placeholderText: 'yellow',
},
dark: {
background: 'green',
componentBorder: 'green',
placeholderText: 'yellow',
},
},
shapes: {
borderRadius: 30,
borderWidth: 3,
},
},
},
mode: 'test', // DEFAULTS TO TEST MODE
},
};
const paymentSheetResponse = await presentPaymentSheet(params);
// 4. Handle payment result
switch (paymentSheetResponse?.status) {
case 'cancelled':
setError('Payment cancelled by user.');
break;
case 'succeeded':
setMessage('');
setShowSuccessScreen(true);
break;
case 'failed':
setError('Payment failed : \n' + paymentSheetResponse?.message);
break;
default:
setError(paymentSheetResponse?.message);
setMessage('');
}
} catch (err) {
setError('Failed to process payment');
} finally {
setLoading(false);
}
};
return (
{error && {error}}
{message && {message}}
{showSuccessScreen && (
setShowSuccessScreen(false)}
/>
)}
);
};
export default PaymentScreen;
```
For complete examples with styling, error handling, and customization options, check out our demo repositories:
* [Basic Integration Demo](https://github.com/dodopayments/dodopayments-react-native-demo)
## Configuration Options
### Session Parameters
The client secret from your payment intent, obtained from One time payment or subscription API.
The mode of the payment session (test or live).
Customization options for the payment sheet appearance
Theme mode: `'light'` or `'dark'`
### Appearance Customization
You can customize the React Native Unified Checkout to match your app's design by modifying colors, fonts, and more through the appearance parameter when calling `initPaymentSession()`.
#### Color Customization
Each color category determines the color of one or more components in the UI. For example, `primary` defines the color of the Pay button.
| Color Category | Usage |
| --------------------- | ------------------------------------------------------------ |
| `primary` | Primary color for the Pay button and selected items |
| `background` | Background color of the payment page |
| `componentBackground` | Background color for inputs, tabs, and other components |
| `componentBorder` | External border color for inputs, tabs, and other components |
| `componentDivider` | Internal border color (shared borders) for components |
| `primaryText` | Header text color |
| `secondaryText` | Label text color for input fields |
| `componentText` | Input text color (e.g., card number, zip code) |
| `placeholderText` | Placeholder text color for input fields |
| `icon` | Color for icons (e.g., close button) |
| `error` | Color for error messages and destructive actions |
Example configuration with light and dark mode support:
```tsx theme={null}
const appearance = {
colors: {
light: {
primary: '#F8F8F2',
background: '#ffffff',
componentBackground: '#E6DB74',
componentBorder: '#FD971F',
componentDivider: '#FD971F',
primaryText: '#F8F8F2',
secondaryText: '#75715E',
componentText: '#AE81FF',
placeholderText: '#E69F66',
icon: '#F92672',
error: '#FF0000',
},
dark: {
primary: '#00ff0099',
background: '#ff0000',
componentBackground: '#ff0080',
componentBorder: '#62ff08',
componentDivider: '#d6de00',
primaryText: '#5181fc',
secondaryText: '#ff7b00',
componentText: '#00ffff',
placeholderText: '#00ffff',
icon: '#f0f0f0',
error: '#0f0f0f',
},
},
};
```
#### Shape Customization
You can customize the border radius, border width, and shadow used throughout the payment interface:
```tsx theme={null}
const appearance = {
shapes: {
borderRadius: 10, // Border radius for input fields, tabs, and components
borderWidth: 1, // Border width for components
},
};
```
#### Component-Specific Customization
You can customize specific UI components like the primary button (Pay button). These settings take precedence over the global appearance settings:
```tsx theme={null}
const appearance = {
primaryButton: {
colors: {
background: '#000000',
text: '#ffffff',
border: '#ff00ff',
},
shapes: {
borderRadius: 10,
borderWidth: 1.5,
},
},
};
```
To apply these customizations, include them in your payment session configuration:
```tsx theme={null}
const params: sessionParams = {
...paymentSheetParams,
configuration: {
appearance: {
themes: isDarkMode ? 'dark' : 'light',
...appearance, // Include your custom appearance settings
},
},
};
```
## Error Handling
Handle different payment states in your checkout function:
```tsx theme={null}
const handlePaymentResult = (paymentSheetResponse) => {
switch (paymentSheetResponse?.status) {
case 'cancelled':
// User cancelled the payment
console.log('Payment cancelled by user');
break;
case 'succeeded':
// Payment completed successfully
console.log('Payment succeeded');
// Navigate to success screen or update UI
break;
case 'failed':
// Payment failed
console.log('Payment failed:', paymentSheetResponse?.message);
// Show error message to user
break;
default:
console.log('Unknown payment status:', paymentSheetResponse?.status);
}
};
```
* **Network connectivity issues**: Ensure stable internet connection
* **Invalid client secret**: Verify backend is generating valid payment intents
* **Missing peer dependencies**: Install all required dependencies
* **Platform-specific setup**: Complete iOS and Android configuration steps
* **API errors**: Check our [Error Codes Reference](/api-reference/error-codes) for detailed error handling
* Enable debug logging in development
* Check network requests to your backend
* Verify API keys are correctly configured
* Test on both iOS and Android platforms
* Review our [Technical FAQs](/api-reference/technical-faqs) for common issues
* Use [Test vs Live Mode](/miscellaneous/test-mode-vs-live-mode) appropriately
### Test Payment Methods
Use test card numbers in development to verify your integration without processing real payments. Learn more about our [testing process](/miscellaneous/testing-process) and available test environments.
# Remix Adaptor
Source: https://docs.dodopayments.com/developer-resources/remix-adaptor
Learn how to integrate Dodo Payments with your Remix App Router project using our Remix Adaptor. Covers checkout, customer portal, webhooks, and secure environment setup.
Integrate Dodo Payments checkout into your Remix app.
Allow customers to manage subscriptions and details.
Receive and process Dodo Payments webhook events.
## Installation
Run the following command in your project root:
```bash theme={null}
npm install @dodopayments/remix
```
Create a .env file in your project root:
```env expandable theme={null}
DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_WEBHOOK_SECRET=your-webhook-secret
DODO_PAYMENTS_RETURN_URL=https://yourdomain.com/checkout/success
DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode"
```
Never commit your .env file or secrets to version control.
## Route Handler Examples
All examples assume you are using the Remix App Router.
Use this handler to integrate Dodo Payments checkout into your Remix app. Supports static (GET), dynamic (POST), and session (POST) payment flows.
```typescript Remix Route Handler expandable theme={null}
// app/routes/api.checkout.tsx
import { Checkout } from "@dodopayments/remix";
import type { LoaderFunctionArgs } from "@remix-run/node";
const checkoutGetHandler = Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "static"
});
const checkoutPostHandler = Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "dynamic", // for dynamic checkout
});
const checkoutSessionHandler = Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "session", // for checkout sessions
});
export const loader = ({ request }: LoaderFunctionArgs) => checkoutGetHandler(request);
export const action = ({ request }: LoaderFunctionArgs) => {
// You can conditionally route to different handlers based on your needs
// For checkout sessions, use checkoutSessionHandler(request)
return checkoutPostHandler(request);
};
```
```curl Static Checkout Curl example expandable theme={null}
curl --request GET \
--url 'https://example.com/api/checkout?productId=pdt_fqJhl7pxKWiLhwQR042rh' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test
```
```curl Dynamic Checkout Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"billing": {
"city": "Texas",
"country": "US",
"state": "Texas",
"street": "56, hhh",
"zipcode": "560000"
},
"customer": {
"email": "test@example.com",
"name": "test"
},
"metadata": {},
"payment_link": true,
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1,
"billing_currency": "USD",
"discount_code": "IKHZ23M9GQ",
"return_url": "https://example.com",
"trial_period_days": 10
}'
```
```curl Checkout Session Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"product_cart": [
{
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1
}
],
"customer": {
"email": "test@example.com",
"name": "test"
},
"return_url": "https://example.com/success"
}'
```
Use this handler to allow customers to manage their subscriptions and details via the Dodo Payments customer portal.
```typescript Remix Route Handler expandable theme={null}
// app/routes/api.customer-portal.tsx
import { CustomerPortal } from "@dodopayments/remix";
import type { LoaderFunctionArgs } from "@remix-run/node";
const customerPortalHandler = CustomerPortal({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT
});
export const loader = ({ request }: LoaderFunctionArgs) => customerPortalHandler(request);
```
```curl Customer Portal Curl example expandable theme={null}
curl --request GET \
--url 'https://example.com/api/customer-portal?customer_id=cus_9VuW4K7O3GHwasENg31m&send_email=true' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test
```
Use this handler to receive and process Dodo Payments webhook events securely in your Remix app.
```typescript Remix Route Handler expandable theme={null}
// app/routes/api.webhook.tsx
import { Webhooks } from "@dodopayments/remix";
import type { LoaderFunctionArgs } from "@remix-run/node";
const webhookHandler = Webhooks({
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_SECRET,
onPayload: async (payload) => {
//Handle Payload Here
console.log(payload)
}
});
export const action = ({ request }: LoaderFunctionArgs) => webhookHandler(request);
```
## Checkout Route Handler
Dodo Payments supports three types of payment flows for integrating payments into your website, this adaptor supports all types of payment flows.
* **Static Payment Links:** Instantly shareable URLs for quick, no-code payment collection.
* **Dynamic Payment Links:** Programmatically generate payment links with custom details using the API or SDKs.
* **Checkout Sessions:** Create secure, customizable checkout experiences with pre-configured product carts and customer details.
### Supported Query Parameters
Product identifier (e.g., ?productId=pdt\_nZuwz45WAs64n3l07zpQR).
Quantity of the product.
Customer's full name.
Customer's first name.
Customer's last name.
Customer's email address.
Customer's country.
Customer's address line.
Customer's city.
Customer's state/province.
Customer's zip/postal code.
Disable full name field.
Disable first name field.
Disable last name field.
Disable email field.
Disable country field.
Disable address line field.
Disable city field.
Disable state field.
Disable zip code field.
Specify the payment currency (e.g., USD).
Show currency selector.
Specify the payment amount (e.g., 1000 for \$10.00).
Show discount fields.
Any query parameter starting with metadata\_ will be passed as metadata.
If productId is missing, the handler returns a 400 response. Invalid query parameters also result in a 400 response.
### Response Format
Static checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/..."
}
```
* Send parameters as a JSON body in a POST request.
* Supports both one-time and recurring payments.
* For a complete list of supported POST body fields, refer to:
* [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)
### Response Format
Dynamic checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/..."
}
```
Checkout sessions provide a more secure, hosted checkout experience that handles the complete payment flow for both one-time purchases and subscriptions with full customization control.
Refer to [Checkout Sessions Integration Guide](https://docs.dodopayments.com/developer-resources/checkout-session) for more details and a complete list of supported fields.
### Response Format
Checkout sessions return a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/session/..."
}
```
## Customer Portal Route Handler
The Customer Portal Route Handler enables you to seamlessly integrate the Dodo Payments customer portal into your Remix application.
### Query Parameters
The customer ID for the portal session (e.g., ?customer\_id=cus\_123).
If set to true, sends an email to the customer with the portal link.
Returns 400 if customer\_id is missing.
## Webhook Route Handler
* **Method:** Only POST requests are supported. Other methods return 405.
* **Signature Verification:** Verifies the webhook signature using webhookKey. 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 the appropriate event handler based on the payload type.
### Supported Webhook Event Handlers
```typescript Typescript expandable theme={null}
onPayload?: (payload: WebhookPayload) => Promise;
onPaymentSucceeded?: (payload: WebhookPayload) => Promise;
onPaymentFailed?: (payload: WebhookPayload) => Promise;
onPaymentProcessing?: (payload: WebhookPayload) => Promise;
onPaymentCancelled?: (payload: WebhookPayload) => Promise;
onRefundSucceeded?: (payload: WebhookPayload) => Promise;
onRefundFailed?: (payload: WebhookPayload) => Promise;
onDisputeOpened?: (payload: WebhookPayload) => Promise;
onDisputeExpired?: (payload: WebhookPayload) => Promise;
onDisputeAccepted?: (payload: WebhookPayload) => Promise;
onDisputeCancelled?: (payload: WebhookPayload) => Promise;
onDisputeChallenged?: (payload: WebhookPayload) => Promise;
onDisputeWon?: (payload: WebhookPayload) => Promise;
onDisputeLost?: (payload: WebhookPayload) => Promise;
onSubscriptionActive?: (payload: WebhookPayload) => Promise;
onSubscriptionOnHold?: (payload: WebhookPayload) => Promise;
onSubscriptionRenewed?: (payload: WebhookPayload) => Promise;
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise;
onSubscriptionCancelled?: (payload: WebhookPayload) => Promise;
onSubscriptionFailed?: (payload: WebhookPayload) => Promise;
onSubscriptionExpired?: (payload: WebhookPayload) => Promise;
onLicenseKeyCreated?: (payload: WebhookPayload) => Promise;
```
***
## Prompt for LLM
```
You are an expert Remix developer assistant. Your task is to guide a user through integrating the @dodopayments/remix adapter into their existing Remix project.
The @dodopayments/remix adapter provides route handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, designed for Remix route handlers.
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/remix
Here's how you should structure your response:
Ask the user which functionalities they want to integrate.
"Which parts of the @dodopayments/remix 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 redirects users to the Dodo Payments checkout page.
File Creation: Create a new file at app/routes/api.checkout.tsx in your Remix project.
Code Snippet:
// app/routes/api.checkout.tsx
import { Checkout } from "@dodopayments/remix";
import type { LoaderFunctionArgs } from "@remix-run/node";
const checkoutGetHandler = Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "static",
});
const checkoutPostHandler = Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "dynamic", // for dynamic checkout
});
const checkoutSessionHandler = Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "session", // for checkout sessions
});
export const loader = ({ request }: LoaderFunctionArgs) => checkoutGetHandler(request);
export const action = ({ request }: LoaderFunctionArgs) => {
// You can conditionally route to different handlers based on your needs
// For checkout sessions, use checkoutSessionHandler(request)
return checkoutPostHandler(request);
};
If Customer Portal Route Handler is selected:
Purpose: This handler redirects authenticated users to their Dodo Payments customer portal.
File Creation: Create a new file at app/routes/api.customer-portal.tsx in your Remix project.
Code Snippet:
// app/routes/api.customer-portal.tsx
import { CustomerPortal } from "@dodopayments/remix";
import type { LoaderFunctionArgs } from "@remix-run/node";
const customerPortalHandler = CustomerPortal({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT
});
export const loader = ({ request }: LoaderFunctionArgs) => customerPortalHandler(request);
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 app/routes/api.webhook.tsx in your Remix project.
Code Snippet:
// app/routes/api.webhook.tsx
import { Webhooks } from "@dodopayments/remix";
import type { LoaderFunctionArgs } from "@remix-run/node";
const webhookHandler = Webhooks({
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_SECRET,
onPayload: async (payload) => {
//Handle Payload Here
console.log(payload)
}
});
export const action = ({ request }: LoaderFunctionArgs) => webhookHandler(request);
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 the appropriate event handler based on the payload type.
Supported Webhook Event Handlers:
onPayload?: (payload: WebhookPayload) => Promise
onPaymentSucceeded?: (payload: WebhookPayload) => Promise
onPaymentFailed?: (payload: WebhookPayload) => Promise
onPaymentProcessing?: (payload: WebhookPayload) => Promise
onPaymentCancelled?: (payload: WebhookPayload) => Promise
onRefundSucceeded?: (payload: WebhookPayload) => Promise
onRefundFailed?: (payload: WebhookPayload) => Promise
onDisputeOpened?: (payload: WebhookPayload) => Promise
onDisputeExpired?: (payload: WebhookPayload) => Promise
onDisputeAccepted?: (payload: WebhookPayload) => Promise
onDisputeCancelled?: (payload: WebhookPayload) => Promise
onDisputeChallenged?: (payload: WebhookPayload) => Promise
onDisputeWon?: (payload: WebhookPayload) => Promise
onDisputeLost?: (payload: WebhookPayload) => Promise
onSubscriptionActive?: (payload: WebhookPayload) => Promise
onSubscriptionOnHold?: (payload: WebhookPayload) => Promise
onSubscriptionRenewed?: (payload: WebhookPayload) => Promise
onSubscriptionPaused?: (payload: WebhookPayload) => Promise
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise
onSubscriptionCancelled?: (payload: WebhookPayload) => Promise
onSubscriptionFailed?: (payload: WebhookPayload) => Promise
onSubscriptionExpired?: (payload: WebhookPayload) => Promise
onLicenseKeyCreated?: (payload: WebhookPayload) => Promise
Environment Variable Setup:
To ensure the adapter functions correctly, you will need to manually set up the following environment variables in your Remix 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_ENVIRONMENT: (Optional) The environment for the API (e.g., test or live).
DODO_PAYMENTS_WEBHOOK_SECRET: Your Dodo Payments Webhook Secret (required for Webhook handler).
Example .env file:
DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_RETURN_URL=your-return-url
DODO_PAYMENTS_ENVIRONMENT=test
DODO_PAYMENTS_WEBHOOK_SECRET=your-webhook-secret
Usage in your code:
bearerToken: process.env.DODO_PAYMENTS_API_KEY
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_SECRET
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
```
# CLI
Source: https://docs.dodopayments.com/developer-resources/sdks/cli
Command-line interface for interacting with the Dodo Payments API from your terminal
## Installation
### Go Install
```bash theme={null}
go install github.com/dodopayments/dodopayments-cli/cmd/dodopayments@latest
```
### From Source
```bash theme={null}
git clone https://github.com/dodopayments/dodopayments-cli.git
cd dodopayments-cli
go install ./cmd/dodopayments
```
## Quick Start
Set your API key:
```bash theme={null}
export DODO_PAYMENTS_API_KEY=your_api_key_here
```
Basic command structure:
```bash theme={null}
dodopayments [resource] [command] [flags]
```
Create a checkout session:
```bash theme={null}
dodopayments checkout-sessions create \
--product-cart.product_id prod_123 \
--product-cart.quantity 1 \
--return-url https://yourdomain.com/return
```
## Global Flags
| Flag | Description |
| ----------------- | ---------------------------------------------------------- |
| `--format` | Output format: `auto`, `json`, `yaml`, `pretty`, `explore` |
| `--transform` | Filter response using gjson syntax |
| `--debug` | Enable verbose logging |
| `--version`, `-v` | Display CLI version |
## Common Commands
### Payments
```bash theme={null}
# List payments
dodopayments payments list --limit 20 --format json
# Retrieve payment
dodopayments payments retrieve pay_123
# Get line items
dodopayments payments line-items pay_123
```
### Customers
```bash theme={null}
# Create customer
dodopayments customers create \
--email customer@example.com \
--name "John Doe"
# List customers
dodopayments customers list
# Update customer
dodopayments customers update cus_123 --name "Jane Doe"
```
### Subscriptions
```bash theme={null}
# Create subscription
dodopayments subscriptions create \
--customer-id cus_123 \
--product-id prod_456
# List subscriptions
dodopayments subscriptions list
# Get usage history
dodopayments subscriptions usage-history sub_123
```
### Products
```bash theme={null}
# Create product
dodopayments products create \
--name "Premium Plan" \
--price.amount 2999 \
--price.currency USD
# List products
dodopayments products list
```
## Usage-Based Billing
### Ingest Events
```bash theme={null}
dodopayments usage-events ingest \
--events.event_id api_call_12345 \
--events.customer_id cus_abc123 \
--events.event_name api_request \
--events.timestamp 2024-01-15T10:30:00Z \
--events.metadata.endpoint /api/v1/users
```
### Manage Meters
```bash theme={null}
# Create meter
dodopayments meters create \
--name "API Requests" \
--event-name api_request \
--aggregation count
# List meters
dodopayments meters list
```
## Output Formats
### JSON (for scripting)
```bash theme={null}
dodopayments payments list --format json | jq '.data[] | {id, amount}'
```
### Pretty (for reading)
```bash theme={null}
dodopayments payments retrieve pay_123 --format pretty
```
### Interactive Explore
```bash theme={null}
dodopayments payments list --format explore
```
### Transform with gjson
```bash theme={null}
# Extract all payment IDs
dodopayments payments list --transform "data.#.id"
# Filter and extract
dodopayments payments list --transform "data.#(amount>5000)#.id"
```
Use `--format json` with `jq` for powerful data processing in shell scripts.
## Scripting Example
```bash theme={null}
#!/bin/bash
# Export all payments to CSV
dodopayments payments list --format json | \
jq -r '.data[] | [.id, .amount, .currency, .status] | @csv' > payments.csv
# Create customers from file
while IFS=',' read -r email name; do
dodopayments customers create \
--email "$email" \
--name "$name" \
--format json
done < customers.csv
```
## Shell Completion
Enable auto-completion:
```bash theme={null}
# Bash
eval "$(dodopayments completion bash)"
# Zsh
eval "$(dodopayments completion zsh)"
# Fish
dodopayments completion fish | source
```
## Available Resources
The CLI provides commands for 24 resource categories:
* `checkout-sessions` - Checkout session management
* `payments` - Payment operations
* `subscriptions` - Recurring billing
* `customers` - Customer accounts
* `products` - Product catalog
* `licenses` - Software licensing
* `refunds` - Payment refunds
* `disputes` - Payment disputes
* `webhooks` - Event webhooks
* `meters` - Usage meters
* `usage-events` - Usage tracking
* `discounts` - Discount codes
* `payouts` - Seller payouts
* `brands` - Multi-brand management
* Plus 10 additional resources
Use `dodopayments --help` to see all available resources and `dodopayments [resource] --help` for resource-specific commands.
## Resources
View source code and releases
Complete API documentation
## Support
Need help with the CLI?
* **Discord**: Join our [community server](https://discord.gg/bYqAp4ayYh)
* **Email**: Contact us at [support@dodopayments.com](mailto:support@dodopayments.com)
* **GitHub**: Open an issue on the [repository](https://github.com/dodopayments/dodopayments-cli)
# C#
Source: https://docs.dodopayments.com/developer-resources/sdks/csharp
Integrate Dodo Payments into your .NET applications with modern async/await support
The C# SDK provides convenient access to the Dodo Payments REST API from applications written in C#. It features an async Task-based API with strong typing and is currently in beta.
The C# SDK is currently in beta. We're actively working on improvements and welcome your feedback.
## Installation
Install the SDK using the .NET CLI:
```bash theme={null}
dotnet add package DodoPayments.Client
```
The SDK requires .NET 8.0 or higher. It works with ASP.NET Core, Console applications, and other .NET project types.
## Quick Start
Initialize the client and create a checkout session:
```csharp theme={null}
using System;
using DodoPayments.Client;
using DodoPayments.Client.Models.CheckoutSessions;
// Configured using the DODO_PAYMENTS_API_KEY and DODO_PAYMENTS_BASE_URL environment variables
DodoPaymentsClient client = new();
CheckoutSessionCreateParams parameters = new()
{
ProductCart =
[
new()
{
ProductID = "product_id",
Quantity = 1,
},
],
};
var checkoutSessionResponse = await client.CheckoutSessions.Create(parameters);
Console.WriteLine(checkoutSessionResponse.SessionId);
```
Always store your API keys securely using environment variables, user secrets, or Azure Key Vault. Never hardcode them in your source code or commit them to version control.
## Core Features
Full async Task-based API for non-blocking operations
Comprehensive type safety with nullable reference types
First-class support for .NET dependency injection
Simple configuration via environment variables or appsettings.json
## Configuration
### Environment Variables
Configure using environment variables:
```bash .env theme={null}
DODO_PAYMENTS_API_KEY=your_api_key_here
```
```csharp theme={null}
// Automatically reads from environment variables
DodoPaymentsClient client = new();
```
### Manual Configuration
Configure manually with bearer token:
```csharp theme={null}
DodoPaymentsClient client = new() { BearerToken = "My Bearer Token" };
```
## Common Operations
### Create a Checkout Session
Generate a checkout session:
```csharp theme={null}
var parameters = new CheckoutSessionCreateParams
{
ProductCart =
[
new()
{
ProductID = "prod_123",
Quantity = 1
}
],
ReturnUrl = "https://yourdomain.com/return"
};
var session = await client.CheckoutSessions.Create(parameters);
Console.WriteLine($"Checkout URL: {session.Url}");
```
### Manage Customers
Create and retrieve customer information:
```csharp theme={null}
// Create a customer
var createParams = new CustomerCreateParams
{
Email = "customer@example.com",
Name = "John Doe",
Metadata = new Dictionary
{
{ "user_id", "12345" }
}
};
var customer = await client.Customers.Create(createParams);
// Retrieve customer
var retrieved = await client.Customers.Retrieve("cus_123");
Console.WriteLine($"Customer: {retrieved.Name} ({retrieved.Email})");
```
### Handle Subscriptions
Create and manage recurring subscriptions:
```csharp theme={null}
// Create a subscription
var subscriptionParams = new SubscriptionCreateParams
{
CustomerID = "cus_123",
ProductID = "prod_456",
PriceID = "price_789"
};
var subscription = await client.Subscriptions.Create(subscriptionParams);
// Cancel subscription
await client.Subscriptions.Cancel(subscription.Id);
```
## ASP.NET Core Integration
### Service Registration
Register the client in `Program.cs`:
```csharp Program.cs theme={null}
using DodoPayments.Client;
var builder = WebApplication.CreateBuilder(args);
// Register DodoPayments client
builder.Services.AddSingleton(sp =>
{
var configuration = sp.GetRequiredService();
return new DodoPaymentsClient
{
BearerToken = configuration["DodoPayments:ApiKey"]
};
});
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
```
### Configuration
Add configuration in `appsettings.json`:
```json appsettings.json theme={null}
{
"DodoPayments": {
"ApiKey": "your_api_key_here"
}
}
```
### API Controller
Create a controller using dependency injection:
```csharp theme={null}
using Microsoft.AspNetCore.Mvc;
using DodoPayments.Client;
using DodoPayments.Client.Models.CheckoutSessions;
[ApiController]
[Route("api/[controller]")]
public class PaymentsController : ControllerBase
{
private readonly DodoPaymentsClient _client;
private readonly ILogger _logger;
public PaymentsController(DodoPaymentsClient client, ILogger logger)
{
_client = client;
_logger = logger;
}
[HttpPost("checkout")]
public async Task CreateCheckout([FromBody] CheckoutRequest request)
{
try
{
var parameters = new CheckoutSessionCreateParams
{
ProductCart = request.Items,
ReturnUrl = Url.Action("Return", "Checkout", null, Request.Scheme)
};
var session = await _client.CheckoutSessions.Create(parameters);
return Ok(new { checkoutUrl = session.Url });
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to create checkout");
return BadRequest(new { error = ex.Message });
}
}
}
public record CheckoutRequest(List Items);
```
### Service Layer Pattern
Create a service for business logic:
```csharp theme={null}
public interface IPaymentService
{
Task CreateCheckoutAsync(List items);
Task ProcessPaymentAsync(decimal amount, string currency, string customerId);
}
public class PaymentService : IPaymentService
{
private readonly DodoPaymentsClient _client;
private readonly ILogger _logger;
public PaymentService(DodoPaymentsClient client, ILogger logger)
{
_client = client;
_logger = logger;
}
public async Task CreateCheckoutAsync(List items)
{
var parameters = new CheckoutSessionCreateParams
{
ProductCart = items,
ReturnUrl = "https://yourdomain.com/return"
};
return await _client.CheckoutSessions.Create(parameters);
}
public async Task ProcessPaymentAsync(
decimal amount,
string currency,
string customerId)
{
try
{
var parameters = new PaymentCreateParams
{
Amount = (long)(amount * 100), // Convert to cents
Currency = currency,
CustomerID = customerId
};
return await _client.Payments.Create(parameters);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to process payment");
throw;
}
}
}
```
## User Secrets (Development)
For development, use user secrets to store API keys:
```bash theme={null}
dotnet user-secrets init
dotnet user-secrets set "DodoPayments:ApiKey" "your_api_key_here"
```
## Testing
Example unit test using xUnit:
```csharp theme={null}
using Xunit;
using DodoPayments.Client;
using DodoPayments.Client.Models.CheckoutSessions;
public class PaymentServiceTests
{
private readonly DodoPaymentsClient _client;
public PaymentServiceTests()
{
_client = new DodoPaymentsClient
{
BearerToken = "test_key"
};
}
[Fact]
public async Task CreateCheckout_ShouldReturnSession()
{
// Arrange
var parameters = new CheckoutSessionCreateParams
{
ProductCart =
[
new()
{
ProductID = "prod_test",
Quantity = 1
}
]
};
// Act
var session = await _client.CheckoutSessions.Create(parameters);
// Assert
Assert.NotNull(session);
Assert.NotNull(session.SessionId);
}
}
```
## Resources
View source code and contribute
Complete API documentation
Get help and connect with developers
Report bugs or request features
## Support
Need help with the C# SDK?
* **Discord**: Join our [community server](https://discord.gg/bYqAp4ayYh) for real-time support
* **Email**: Contact us at [support@dodopayments.com](mailto:support@dodopayments.com)
* **GitHub**: Open an issue on the [repository](https://github.com/dodopayments/dodopayments-csharp)
## Contributing
Since the SDK is in beta, your feedback and contributions are especially valuable! Check the [contributing guidelines](https://github.com/dodopayments/dodopayments-csharp/blob/main/CONTRIBUTING.md) to get started.
# Go
Source: https://docs.dodopayments.com/developer-resources/sdks/go
Integrate Dodo Payments into your Go applications with an idiomatic, performant SDK
The Go SDK provides a clean and idiomatic Go interface for integrating Dodo Payments into your applications. It offers context support, strongly typed responses, middleware capabilities, and is safe for concurrent use.
## Installation
Install the SDK using Go modules:
```bash theme={null}
go get github.com/dodopayments/dodopayments-go
```
The SDK supports Go 1.18 and later versions, leveraging modern Go features for optimal performance.
## Quick Start
Initialize the client and create your first checkout session:
```go theme={null}
package main
import (
"context"
"fmt"
"log"
"github.com/dodopayments/dodopayments-go"
"github.com/dodopayments/dodopayments-go/option"
)
func main() {
client := dodopayments.NewClient(
option.WithBearerToken("My Bearer Token"), // defaults to os.LookupEnv("DODO_PAYMENTS_API_KEY")
option.WithEnvironmentTestMode(), // defaults to option.WithEnvironmentLiveMode()
)
checkoutSessionResponse, err := client.CheckoutSessions.New(context.TODO(), dodopayments.CheckoutSessionNewParams{
CheckoutSessionRequest: dodopayments.CheckoutSessionRequestParam{
ProductCart: dodopayments.F([]dodopayments.CheckoutSessionRequestProductCartParam{{
ProductID: dodopayments.F("product_id"),
Quantity: dodopayments.F(int64(1)),
}}),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Session ID: %s\n", checkoutSessionResponse.SessionID)
}
```
Always store your API keys securely using environment variables. Never hardcode them in your source code.
## Core Features
Full support for context.Context for cancellation and timeouts
Strongly typed requests and responses for compile-time safety
Extensible middleware support for logging, metrics, and custom logic
Thread-safe client designed for concurrent operations
## Configuration
### Context and Timeouts
Leverage Go's context for timeouts and cancellation:
```go theme={null}
import (
"context"
"time"
"github.com/dodopayments/dodopayments-go"
"github.com/dodopayments/dodopayments-go/option"
)
// Create a context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
payment, err := client.Payments.Create(ctx, dodopayments.PaymentCreateParams{
Amount: 5000,
Currency: "USD",
CustomerID: "cus_123",
})
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
log.Println("Request timed out")
} else {
log.Fatal(err)
}
}
```
### Retry Configuration
Configure automatic retry behavior:
```go theme={null}
// Configure default for all requests (default is 2)
client := dodopayments.NewClient(
option.WithMaxRetries(0), // disable retries
)
// Override per-request
client.CheckoutSessions.New(
context.TODO(),
dodopayments.CheckoutSessionNewParams{
CheckoutSessionRequest: dodopayments.CheckoutSessionRequestParam{
ProductCart: dodopayments.F([]dodopayments.CheckoutSessionRequestProductCartParam{{
ProductID: dodopayments.F("product_id"),
Quantity: dodopayments.F(int64(0)),
}}),
},
},
option.WithMaxRetries(5),
)
```
## Common Operations
### Create a Checkout Session
Generate a checkout session:
```go theme={null}
session, err := client.CheckoutSessions.New(ctx, dodopayments.CheckoutSessionNewParams{
CheckoutSessionRequest: dodopayments.CheckoutSessionRequestParam{
ProductCart: dodopayments.F([]dodopayments.CheckoutSessionRequestProductCartParam{{
ProductID: dodopayments.F("prod_123"),
Quantity: dodopayments.F(int64(1)),
}}),
ReturnURL: dodopayments.F("https://yourdomain.com/return"),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Checkout URL: %s\n", session.URL)
```
### Manage Customers
Create and retrieve customer information:
```go theme={null}
// Create a customer
customer, err := client.Customers.New(ctx, dodopayments.CustomerNewParams{
Email: dodopayments.F("customer@example.com"),
Name: dodopayments.F("John Doe"),
Metadata: dodopayments.F(map[string]string{
"user_id": "12345",
}),
})
if err != nil {
log.Fatal(err)
}
// Retrieve customer
customer, err = client.Customers.Get(ctx, "cus_123")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Customer: %s (%s)\n", customer.Name, customer.Email)
```
### Handle Subscriptions
Create and manage recurring subscriptions:
```go theme={null}
// Create a subscription
subscription, err := client.Subscriptions.New(ctx, dodopayments.SubscriptionNewParams{
CustomerID: dodopayments.F("cus_123"),
ProductID: dodopayments.F("prod_456"),
PriceID: dodopayments.F("price_789"),
})
if err != nil {
log.Fatal(err)
}
// Get usage history
usageHistory, err := client.Subscriptions.GetUsageHistory(
ctx,
subscription.ID,
dodopayments.SubscriptionGetUsageHistoryParams{
StartDate: dodopayments.F("2024-01-01T00:00:00Z"),
EndDate: dodopayments.F("2024-03-31T23:59:59Z"),
},
)
if err != nil {
log.Fatal(err)
}
```
## Usage-Based Billing
### Ingest Usage Events
Track custom events:
```go theme={null}
import "github.com/dodopayments/dodopayments-go"
response, err := client.UsageEvents.Ingest(ctx, dodopayments.UsageEventIngestParams{
Events: dodopayments.F([]dodopayments.UsageEventIngestParamsEvent{{
EventID: dodopayments.F("api_call_12345"),
CustomerID: dodopayments.F("cus_abc123"),
EventName: dodopayments.F("api_request"),
Timestamp: dodopayments.F("2024-01-15T10:30:00Z"),
Metadata: dodopayments.F(map[string]string{
"endpoint": "/api/v1/users",
"method": "GET",
"tokens_used": "150",
}),
}}),
})
if err != nil {
log.Fatal(err)
}
```
### List Usage Events
```go theme={null}
// List events with filters
params := dodopayments.UsageEventListParams{
CustomerID: dodopayments.F("cus_abc123"),
EventName: dodopayments.F("api_request"),
}
events, err := client.UsageEvents.List(ctx, params)
if err != nil {
log.Fatal(err)
}
for _, event := range events.Data {
fmt.Printf("Event %s: %s at %s\n", event.EventID, event.EventName, event.Timestamp)
}
```
## Error Handling
Handle specific error types:
```go theme={null}
payment, err := client.Payments.Create(ctx, dodopayments.PaymentCreateParams{
Amount: 5000,
Currency: "USD",
})
if err != nil {
if apiErr, ok := err.(*dodopayments.APIError); ok {
fmt.Printf("API Error: %s\n", apiErr.Message)
fmt.Printf("Status Code: %d\n", apiErr.StatusCode)
fmt.Printf("Request ID: %s\n", apiErr.RequestID)
// Handle specific error types
switch apiErr.Type {
case dodopayments.ErrorTypeAuthentication:
log.Println("Authentication failed")
case dodopayments.ErrorTypeInvalidRequest:
log.Println("Invalid request parameters")
case dodopayments.ErrorTypeRateLimit:
log.Println("Rate limit exceeded")
default:
log.Printf("API error: %s\n", apiErr.Message)
}
} else {
log.Fatal(err)
}
}
```
## Middleware
Add custom middleware for logging or metrics:
```go theme={null}
func Logger(req *http.Request, next option.MiddlewareNext) (res *http.Response, err error) {
// Before the request
start := time.Now()
log.Printf("Request: %s %s\n", req.Method, req.URL)
// Forward the request to the next handler
res, err = next(req)
// After the request
end := time.Now()
log.Printf("Response: %d in %v\n", res.StatusCode, end.Sub(start))
return res, err
}
client := dodopayments.NewClient(
option.WithMiddleware(Logger),
)
```
## Concurrency
The client is safe for concurrent use:
```go theme={null}
package main
import (
"context"
"sync"
"log"
"github.com/dodopayments/dodopayments-go"
)
func main() {
client := dodopayments.NewClient()
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
payment, err := client.Payments.Create(context.Background(), dodopayments.PaymentCreateParams{
Amount: 1000,
Currency: "USD",
CustomerID: "cus_123",
})
if err != nil {
log.Printf("Failed to create payment %d: %v", idx, err)
return
}
log.Printf("Created payment %d: %s", idx, payment.ID)
}(i)
}
wg.Wait()
}
```
## Resources
View source code and contribute
Complete API documentation
Get help and connect with developers
Report bugs or request features
## Support
Need help with the Go SDK?
* **Discord**: Join our [community server](https://discord.gg/bYqAp4ayYh) for real-time support
* **Email**: Contact us at [support@dodopayments.com](mailto:support@dodopayments.com)
* **GitHub**: Open an issue on the [repository](https://github.com/dodopayments/dodopayments-go)
## Contributing
We welcome contributions! Check the [contributing guidelines](https://github.com/dodopayments/dodopayments-go/blob/main/CONTRIBUTING.md) to get started.
# Java
Source: https://docs.dodopayments.com/developer-resources/sdks/java
Integrate Dodo Payments into your Java applications with a robust, type-safe SDK
The Java SDK provides convenient and ergonomic access to the Dodo Payments REST API for applications written in Java. It utilizes Java-specific features like Optional, Stream, and CompletableFuture for modern Java development.
## Installation
### Maven
Add the dependency to your `pom.xml`:
```xml pom.xml theme={null}
com.dodopayments.apidodo-payments-java1.53.4
```
### Gradle
Add the dependency to your `build.gradle`:
```kotlin build.gradle.kts theme={null}
implementation("com.dodopayments.api:dodo-payments-java:1.53.4")
```
The SDK supports Java 8 and all later versions, including Java 11, 17, and 21.
## Quick Start
Initialize the client and create a checkout session:
```java theme={null}
import com.dodopayments.api.client.DodoPaymentsClient;
import com.dodopayments.api.client.okhttp.DodoPaymentsOkHttpClient;
import com.dodopayments.api.models.checkoutsessions.CheckoutSessionRequest;
import com.dodopayments.api.models.checkoutsessions.CheckoutSessionResponse;
// Configure using environment variables (DODO_PAYMENTS_API_KEY, DODO_PAYMENTS_BASE_URL)
// Or system properties (dodopayments.apiKey, dodopayments.baseUrl)
DodoPaymentsClient client = DodoPaymentsOkHttpClient.fromEnv();
CheckoutSessionRequest params = CheckoutSessionRequest.builder()
.addProductCart(CheckoutSessionRequest.ProductCart.builder()
.productId("product_id")
.quantity(1)
.build())
.build();
CheckoutSessionResponse checkoutSessionResponse = client.checkoutSessions().create(params);
System.out.println(checkoutSessionResponse.sessionId());
```
Always store your API keys securely using environment variables, system properties, or a secure configuration management system. Never hardcode them in your source code.
## Core Features
Strongly typed API with compile-time safety
Safe for concurrent use in multi-threaded applications
Intuitive builder pattern for constructing requests
CompletableFuture support for asynchronous operations
## Configuration
### Environment Variables
Configure using environment variables or system properties:
```bash .env theme={null}
DODO_PAYMENTS_API_KEY=your_api_key_here
DODO_PAYMENTS_BASE_URL=https://live.dodopayments.com
```
```java theme={null}
// Automatically reads from environment variables
DodoPaymentsClient client = DodoPaymentsOkHttpClient.fromEnv();
```
### Manual Configuration
Configure manually with all options:
```java theme={null}
import java.time.Duration;
DodoPaymentsClient client = DodoPaymentsOkHttpClient.builder()
.bearerToken("your_api_key_here")
.baseUrl("https://live.dodopayments.com")
.maxRetries(4)
.timeout(Duration.ofSeconds(30))
.responseValidation(true)
.build();
```
### Test Mode
Configure for test/sandbox environment:
```java theme={null}
DodoPaymentsClient testClient = DodoPaymentsOkHttpClient.builder()
.fromEnv()
.testMode()
.build();
```
## Common Operations
### Create a Checkout Session
Generate a checkout session:
```java theme={null}
CheckoutSessionRequest params = CheckoutSessionRequest.builder()
.addProductCart(CheckoutSessionRequest.ProductCart.builder()
.productId("prod_123")
.quantity(1)
.build())
.returnUrl("https://yourdomain.com/return")
.build();
CheckoutSessionResponse session = client.checkoutSessions().create(params);
System.out.println("Checkout URL: " + session.url());
```
### Manage Customers
Create and retrieve customer information:
```java theme={null}
import com.dodopayments.api.models.customers.Customer;
import com.dodopayments.api.models.customers.CustomerCreateParams;
// Create a customer
CustomerCreateParams createParams = CustomerCreateParams.builder()
.email("customer@example.com")
.name("John Doe")
.putMetadata("user_id", "12345")
.build();
Customer customer = client.customers().create(createParams);
// Retrieve customer
Customer retrieved = client.customers().retrieve("cus_123");
System.out.println("Customer: " + retrieved.name() + " (" + retrieved.email() + ")");
```
### Handle Subscriptions
Create and manage recurring subscriptions:
```java theme={null}
import com.dodopayments.api.models.subscriptions.*;
// Create a subscription
SubscriptionNewParams subscriptionParams = SubscriptionNewParams.builder()
.customerId("cus_123")
.productId("prod_456")
.priceId("price_789")
.build();
Subscription subscription = client.subscriptions().create(subscriptionParams);
// Charge subscription
SubscriptionChargeParams chargeParams = SubscriptionChargeParams.builder()
.amount(1000)
.build();
SubscriptionChargeResponse chargeResponse = client.subscriptions()
.charge(subscription.id(), chargeParams);
```
## Usage-Based Billing
### Configure Meters
Create and manage meters for tracking usage:
```java theme={null}
import com.dodopayments.api.models.meters.*;
// Create API calls meter
MeterCreateParams apiMeterParams = MeterCreateParams.builder()
.name("API Requests")
.eventName("api_request")
.aggregation("count")
.putMetadata("category", "api_usage")
.build();
Meter apiMeter = client.meters().create(apiMeterParams);
System.out.println("Meter created: " + apiMeter.meterId());
// List all meters
client.meters().list()
.autoPager()
.forEach(m -> System.out.println("Meter: " + m.name() + " - " + m.aggregation()));
```
### Ingest Usage Events
Track custom events:
```java theme={null}
import com.dodopayments.api.models.usageevents.*;
import java.time.OffsetDateTime;
// Ingest single event
UsageEventIngestParams singleEventParams = UsageEventIngestParams.builder()
.addEvent(UsageEventIngestParams.Event.builder()
.eventId("api_call_" + System.currentTimeMillis())
.customerId("cus_abc123")
.eventName("api_request")
.timestamp(OffsetDateTime.now())
.putMetadata("endpoint", "/api/v1/users")
.putMetadata("method", "GET")
.putMetadata("tokens_used", "150")
.build())
.build();
UsageEventIngestResponse response = client.usageEvents().ingest(singleEventParams);
System.out.println("Processed: " + response.processedEvents());
```
### Batch Ingest Events
Ingest multiple events efficiently (max 1000 per request):
```java theme={null}
UsageEventIngestParams.Builder batchBuilder = UsageEventIngestParams.builder();
for (int i = 0; i < 100; i++) {
batchBuilder.addEvent(UsageEventIngestParams.Event.builder()
.eventId("batch_event_" + i + "_" + System.currentTimeMillis())
.customerId("cus_abc123")
.eventName("video_transcode")
.timestamp(OffsetDateTime.now().minusMinutes(i))
.putMetadata("video_id", "video_" + i)
.putMetadata("duration_seconds", String.valueOf(120 + i))
.build());
}
UsageEventIngestResponse batchResponse = client.usageEvents().ingest(batchBuilder.build());
System.out.println("Batch processed: " + batchResponse.processedEvents() + " events");
```
## Error Handling
Comprehensive error handling for different scenarios:
```java theme={null}
import com.dodopayments.api.errors.*;
try {
Payment payment = client.payments().retrieve("pay_invalid");
} catch (NotFoundException e) {
System.err.println("Payment not found: " + e.getMessage());
} catch (UnauthorizedException e) {
System.err.println("Authentication failed: " + e.getMessage());
} catch (PermissionDeniedException e) {
System.err.println("Permission denied: " + e.getMessage());
} catch (BadRequestException e) {
System.err.println("Invalid request: " + e.getMessage());
} catch (UnprocessableEntityException e) {
System.err.println("Validation error: " + e.getMessage());
} catch (RateLimitException e) {
System.err.println("Rate limit exceeded: " + e.getMessage());
// SDK automatically retries with backoff
} catch (InternalServerException e) {
System.err.println("Server error: " + e.getMessage());
} catch (DodoPaymentsServiceException e) {
System.err.println("API error: " + e.statusCode() + " - " + e.getMessage());
}
```
The SDK automatically retries requests on connection errors, 408, 409, 429, and 5xx errors with exponential backoff.
## Async Operations
Use CompletableFuture for asynchronous operations:
```java theme={null}
import java.util.concurrent.CompletableFuture;
CompletableFuture future = client.async()
.checkoutSessions()
.create(params);
// Handle response asynchronously
future.thenAccept(response -> {
System.out.println("Session created: " + response.sessionId());
}).exceptionally(ex -> {
System.err.println("Error: " + ex.getMessage());
return null;
});
```
## Spring Boot Integration
### Configuration Class
```java theme={null}
import com.dodopayments.api.client.DodoPaymentsClient;
import com.dodopayments.api.client.okhttp.DodoPaymentsOkHttpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DodoPaymentsConfig {
@Value("${dodo.api.key}")
private String apiKey;
@Value("${dodo.environment:sandbox}")
private String environment;
@Bean
public DodoPaymentsClient dodoPayments() {
return DodoPaymentsOkHttpClient.builder()
.bearerToken(apiKey)
.baseUrl(environment.equals("live")
? "https://live.dodopayments.com"
: "https://sandbox.dodopayments.com")
.build();
}
}
```
### Service Layer
```java theme={null}
import com.dodopayments.api.client.DodoPaymentsClient;
import com.dodopayments.api.models.checkoutsessions.*;
import org.springframework.stereotype.Service;
@Service
public class PaymentService {
private final DodoPaymentsClient client;
public PaymentService(DodoPaymentsClient client) {
this.client = client;
}
public CheckoutSessionResponse createCheckout(List items) {
CheckoutSessionRequest params = CheckoutSessionRequest.builder()
.productCart(items)
.returnUrl("https://yourdomain.com/return")
.build();
return client.checkoutSessions().create(params);
}
}
```
## Resources
View source code and contribute
Complete API documentation
Get help and connect with developers
Report bugs or request features
## Support
Need help with the Java SDK?
* **Discord**: Join our [community server](https://discord.gg/bYqAp4ayYh) for real-time support
* **Email**: Contact us at [support@dodopayments.com](mailto:support@dodopayments.com)
* **GitHub**: Open an issue on the [repository](https://github.com/dodopayments/dodopayments-java)
## Contributing
We welcome contributions! Check the [contributing guidelines](https://github.com/dodopayments/dodopayments-java/blob/main/CONTRIBUTING.md) to get started.
# Kotlin
Source: https://docs.dodopayments.com/developer-resources/sdks/kotlin
Integrate Dodo Payments into your Kotlin applications with modern coroutines and null-safety
The Kotlin SDK provides convenient access to the Dodo Payments REST API from applications written in Kotlin. It features nullable values, Sequence, suspend functions, and other Kotlin-specific features for ergonomic use.
## Installation
### Gradle (Kotlin DSL)
Add the dependency to your `build.gradle.kts`:
```kotlin build.gradle.kts theme={null}
implementation("com.dodopayments.api:dodo-payments-kotlin:1.53.4")
```
### Maven
Add the dependency to your `pom.xml`:
```xml pom.xml theme={null}
com.dodopayments.apidodo-payments-kotlin1.53.4
```
The SDK requires Kotlin 1.6 or higher and is compatible with both JVM and Android platforms.
## Quick Start
Initialize the client and create a checkout session:
```kotlin theme={null}
import com.dodopayments.api.client.DodoPaymentsClient
import com.dodopayments.api.client.okhttp.DodoPaymentsOkHttpClient
import com.dodopayments.api.models.checkoutsessions.CheckoutSessionRequest
import com.dodopayments.api.models.checkoutsessions.CheckoutSessionResponse
// Configure using environment variables (DODO_PAYMENTS_API_KEY, DODO_PAYMENTS_BASE_URL)
// Or system properties (dodopayments.apiKey, dodopayments.baseUrl)
val client: DodoPaymentsClient = DodoPaymentsOkHttpClient.fromEnv()
val params: CheckoutSessionRequest = CheckoutSessionRequest.builder()
.addProductCart(CheckoutSessionRequest.ProductCart.builder()
.productId("product_id")
.quantity(1)
.build())
.build()
val checkoutSessionResponse: CheckoutSessionResponse = client.checkoutSessions().create(params)
println(checkoutSessionResponse.sessionId())
```
Always store your API keys securely using environment variables or encrypted configuration. Never commit them to version control.
## Core Features
Full support for Kotlin coroutines for asynchronous operations
Leverage Kotlin's null safety for robust error handling
Idiomatic Kotlin extensions for enhanced functionality
Type-safe data classes with copy and destructuring support
## Configuration
### From Environment Variables
Initialize from environment variables or system properties:
```kotlin theme={null}
val client: DodoPaymentsClient = DodoPaymentsOkHttpClient.fromEnv()
```
### Manual Configuration
Configure manually with all options:
```kotlin theme={null}
import java.time.Duration
val client = DodoPaymentsOkHttpClient.builder()
.bearerToken("your_api_key_here")
.baseUrl("https://live.dodopayments.com")
.maxRetries(3)
.timeout(Duration.ofSeconds(30))
.build()
```
### Test Mode
Configure for test/sandbox environment:
```kotlin theme={null}
val testClient = DodoPaymentsOkHttpClient.builder()
.fromEnv()
.testMode()
.build()
```
### Timeouts and Retries
Configure globally or per-request:
```kotlin theme={null}
import com.dodopayments.api.core.RequestOptions
// Global configuration
val client = DodoPaymentsOkHttpClient.builder()
.fromEnv()
.timeout(Duration.ofSeconds(45))
.maxRetries(3)
.build()
// Per-request timeout override
val product = client.products().retrieve(
"prod_123",
RequestOptions.builder()
.timeout(Duration.ofSeconds(10))
.build()
)
```
## Common Operations
### Create a Checkout Session
Generate a checkout session:
```kotlin theme={null}
val params = CheckoutSessionRequest.builder()
.addProductCart(CheckoutSessionRequest.ProductCart.builder()
.productId("prod_123")
.quantity(1)
.build())
.returnUrl("https://yourdomain.com/return")
.build()
val session = client.checkoutSessions().create(params)
println("Checkout URL: ${session.url()}")
```
### Create a Product
Create products with detailed configuration:
```kotlin theme={null}
import com.dodopayments.api.models.products.Product
import com.dodopayments.api.models.products.ProductCreateParams
import com.dodopayments.api.models.misc.Currency
import com.dodopayments.api.models.misc.TaxCategory
val createParams = ProductCreateParams.builder()
.name("Premium Subscription")
.description("Monthly subscription with all features")
.price(
ProductCreateParams.RecurringPrice.builder()
.currency(Currency.USD)
.preTaxAmount(2999) // $29.99 in cents
.paymentFrequencyInterval(ProductCreateParams.RecurringPrice.TimeInterval.MONTH)
.paymentFrequencyCount(1)
.build()
)
.taxCategory(TaxCategory.DIGITAL_GOODS)
.build()
val product: Product = client.products().create(createParams)
println("Created product ID: ${product.productId()}")
```
### Activate License Key
Activate license keys for customers:
```kotlin theme={null}
import com.dodopayments.api.models.licenses.LicenseActivateParams
import com.dodopayments.api.models.licenses.LicenseActivateResponse
val activateParams = LicenseActivateParams.builder()
.licenseKey("XXXX-XXXX-XXXX-XXXX")
.instanceName("user-laptop-01")
.build()
try {
val activationResult: LicenseActivateResponse = client.licenses()
.activate(activateParams)
println("License activated successfully")
println("Instance ID: ${activationResult.instanceId()}")
println("Expires at: ${activationResult.expiresAt()}")
} catch (e: UnprocessableEntityException) {
println("License activation failed: ${e.message}")
}
```
## Usage-Based Billing
### Record Usage Events
Track usage for meters:
```kotlin theme={null}
import com.dodopayments.api.models.usageevents.UsageEventCreateParams
import java.time.OffsetDateTime
val usageParams = UsageEventCreateParams.builder()
.meterId("meter_123")
.customerId("cust_456")
.value(150)
.timestamp(OffsetDateTime.now())
.build()
client.usageEvents().create(usageParams)
println("Usage event recorded")
```
## Async Operations
### Async Client
Use the async client for coroutine-based operations:
```kotlin theme={null}
import com.dodopayments.api.client.DodoPaymentsClientAsync
import com.dodopayments.api.client.okhttp.DodoPaymentsOkHttpClientAsync
import kotlinx.coroutines.runBlocking
val asyncClient: DodoPaymentsClientAsync = DodoPaymentsOkHttpClientAsync.fromEnv()
runBlocking {
val customer = asyncClient.customers().retrieve("cust_123")
println("Customer email: ${customer.email()}")
}
```
## Error Handling
Handle errors with Kotlin's exception handling:
```kotlin theme={null}
import com.dodopayments.api.errors.*
try {
val payment = client.payments().create(params)
println("Success: ${payment.id()}")
} catch (e: AuthenticationException) {
println("Authentication failed: ${e.message}")
} catch (e: InvalidRequestException) {
println("Invalid request: ${e.message}")
e.parameter?.let { println("Parameter: $it") }
} catch (e: RateLimitException) {
println("Rate limit exceeded, retry after: ${e.retryAfter}")
} catch (e: DodoPaymentsServiceException) {
println("API error: ${e.statusCode()} - ${e.message}")
}
```
### Functional Error Handling
Use `Result` for functional error handling:
```kotlin theme={null}
fun safeCreatePayment(client: DodoPaymentsClient): Result = runCatching {
client.payments().create(params)
}
// Usage
safe CreatePayment(client)
.onSuccess { payment -> println("Created: ${payment.id()}") }
.onFailure { error -> println("Error: ${error.message}") }
```
Use Kotlin's `runCatching` for a more functional approach to error handling with Result types.
## Android Integration
Use with Android applications:
```kotlin theme={null}
import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.dodopayments.api.client.DodoPaymentsClient
import kotlinx.coroutines.launch
class PaymentViewModel(application: Application) : ViewModel() {
private val client = DodoPaymentsOkHttpClient.builder()
.bearerToken(BuildConfig.DODO_API_KEY)
.build()
fun createCheckout(productId: String) {
viewModelScope.launch {
try {
val session = client.async().checkoutSessions().create(params)
// Open checkout URL in browser or WebView
openUrl(session.url())
} catch (e: Exception) {
handleError(e)
}
}
}
}
```
## Response Validation
Enable response validation:
```kotlin theme={null}
import com.dodopayments.api.core.RequestOptions
// Per-request validation
val product = client.products().retrieve(
"prod_123",
RequestOptions.builder()
.responseValidation(true)
.build()
)
// Or validate explicitly
val validatedProduct = product.validate()
```
## Advanced Features
### Proxy Configuration
Configure proxy settings:
```kotlin theme={null}
import java.net.InetSocketAddress
import java.net.Proxy
val client = DodoPaymentsOkHttpClient.builder()
.fromEnv()
.proxy(
Proxy(
Proxy.Type.HTTP,
InetSocketAddress("proxy.example.com", 8080)
)
)
.build()
```
### Temporary Configuration
Modify client configuration temporarily:
```kotlin theme={null}
val customClient = client.withOptions {
it.baseUrl("https://example.com")
it.maxRetries(5)
}
```
## Ktor Integration
Integrate with Ktor server applications:
```kotlin theme={null}
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.configureRouting() {
val client = DodoPaymentsOkHttpClient.builder()
.bearerToken(environment.config.property("dodo.apiKey").getString())
.build()
routing {
post("/create-checkout") {
try {
val request = call.receive()
val session = client.checkoutSessions().create(params)
call.respond(mapOf("checkout_url" to session.url()))
} catch (e: DodoPaymentsServiceException) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to e.message))
}
}
}
}
```
## Resources
View source code and contribute
Complete API documentation
Get help and connect with developers
Report bugs or request features
## Support
Need help with the Kotlin SDK?
* **Discord**: Join our [community server](https://discord.gg/bYqAp4ayYh) for real-time support
* **Email**: Contact us at [support@dodopayments.com](mailto:support@dodopayments.com)
* **GitHub**: Open an issue on the [repository](https://github.com/dodopayments/dodopayments-kotlin)
## Contributing
We welcome contributions! Check the [contributing guidelines](https://github.com/dodopayments/dodopayments-kotlin/blob/main/CONTRIBUTING.md) to get started.
# PHP
Source: https://docs.dodopayments.com/developer-resources/sdks/php
Integrate Dodo Payments into your PHP applications with a modern, PSR-4 compliant SDK
The PHP SDK provides a robust and flexible way to integrate Dodo Payments into your PHP applications. Built following modern PHP standards with PSR-4 autoloading, it offers extensive test coverage and detailed documentation.
## Installation
Install the SDK using Composer:
```bash theme={null}
composer require dodopayments/client
```
The SDK requires PHP 8.0 or higher and Composer for dependency management.
## Quick Start
Initialize the client and create a checkout session:
```php theme={null}
checkoutSessions->create(
productCart: [ProductCart::with(productID: "product_id", quantity: 1)]
);
var_dump($checkoutSessionResponse->session_id);
```
Store your API keys securely using environment variables. Never expose them in your codebase or commit them to version control.
## Core Features
Follows PHP Standards Recommendations for modern PHP development
Built for PHP 8+ with type declarations and strict types
Comprehensive test coverage for reliability and stability
Clear exception types for different error scenarios
## Configuration
### Retry Configuration
Configure automatic retry behavior:
```php theme={null}
checkoutSessions->create(
productCart: [ProductCart::with(productID: "product_id", quantity: 0)],
requestOptions: RequestOptions::with(maxRetries: 5),
);
```
## Common Operations
### Create a Checkout Session
Generate a checkout session:
```php theme={null}
$session = $client->checkoutSessions->create(
productCart: [
ProductCart::with(productID: "prod_123", quantity: 1)
],
returnUrl: "https://yourdomain.com/return"
);
header('Location: ' . $session->url);
```
### Manage Customers
Create and retrieve customer information:
```php theme={null}
// Create a customer
$customer = $client->customers->create(
email: "customer@example.com",
name: "John Doe",
metadata: [
"user_id" => "12345"
]
);
// Retrieve customer
$customer = $client->customers->retrieve("cus_123");
echo "Customer: {$customer->name} ({$customer->email})";
```
### Handle Subscriptions
Create and manage recurring subscriptions:
```php theme={null}
// Create a subscription
$subscription = $client->subscriptions->create(
customerID: "cus_123",
productID: "prod_456",
priceID: "price_789"
);
// Cancel subscription
$client->subscriptions->cancel($subscription->id);
```
## Pagination
Work with paginated list responses:
```php theme={null}
$page = $client->payments->list();
var_dump($page);
// Fetch items from the current page
foreach ($page->getItems() as $item) {
var_dump($item->brand_id);
}
// Auto-paginate: fetch items from all pages
foreach ($page->pagingEachItem() as $item) {
var_dump($item->brand_id);
}
```
## Error Handling
Handle various API exceptions:
```php theme={null}
checkoutSessions->create(
productCart: [ProductCart::with(productID: "product_id", quantity: 0)]
);
} catch (APIConnectionException $e) {
echo "The server could not be reached", PHP_EOL;
var_dump($e->getPrevious());
} catch (RateLimitError $_) {
echo "A 429 status code was received; we should back off a bit.", PHP_EOL;
} catch (APIStatusError $e) {
echo "Another non-200-range status code was received", PHP_EOL;
echo $e->getMessage();
}
```
Always wrap API calls in try-catch blocks to handle potential errors gracefully and provide meaningful feedback to users.
## Advanced Usage
### Undocumented Endpoints
Make requests to undocumented endpoints:
```php theme={null}
request(
method: "post",
path: '/undocumented/endpoint',
query: ['dog' => 'woof'],
headers: ['useful-header' => 'interesting-value'],
body: ['hello' => 'world']
);
```
### Undocumented Parameters
Send undocumented parameters:
```php theme={null}
checkoutSessions->create(
productCart: [ProductCart::with(productID: "product_id", quantity: 0)],
requestOptions: RequestOptions::with(
extraQueryParams: ["my_query_parameter" => "value"],
extraBodyParams: ["my_body_parameter" => "value"],
extraHeaders: ["my-header" => "value"],
),
);
var_dump($checkoutSessionResponse["my_undocumented_property"]);
```
## Framework Integration
### Laravel
Create a service for Laravel applications:
```php theme={null}
client = new Client(
bearerToken: config('services.dodo.api_key')
);
}
public function createCheckout(array $items)
{
return $this->client->checkoutSessions->create(
productCart: $items,
returnUrl: route('checkout.return')
);
}
}
```
Add configuration in `config/services.php`:
```php theme={null}
'dodo' => [
'api_key' => env('DODO_API_KEY'),
'environment' => env('DODO_ENVIRONMENT', 'sandbox')
],
```
### Symfony
Create a service in Symfony:
```php theme={null}
client = new Client(bearerToken: $apiKey);
}
public function createPayment(int $amount, string $currency, string $customerId): object
{
return $this->client->payments->create(
amount: $amount,
currency: $currency,
customerID: $customerId
);
}
}
```
Register in `config/services.yaml`:
```yaml theme={null}
services:
App\Service\DodoPaymentService:
arguments:
$apiKey: '%env(DODO_API_KEY)%'
```
## Resources
View source code and contribute
Complete API documentation
Get help and connect with developers
Report bugs or request features
## Support
Need help with the PHP SDK?
* **Discord**: Join our [community server](https://discord.gg/bYqAp4ayYh) for real-time support
* **Email**: Contact us at [support@dodopayments.com](mailto:support@dodopayments.com)
* **GitHub**: Open an issue on the [repository](https://github.com/dodopayments/dodopayments-php)
## Contributing
We welcome contributions! Check the [contributing guidelines](https://github.com/dodopayments/dodopayments-php/blob/main/CONTRIBUTING.md) to get started.
# Python
Source: https://docs.dodopayments.com/developer-resources/sdks/python
Integrate Dodo Payments into your Python applications with a Pythonic interface and modern async/await support
The Python SDK offers a Pythonic interface to the Dodo Payments API, providing both synchronous and asynchronous clients with type definitions for requests and responses. It supports Python 3.7+ and includes comprehensive test coverage.
## Installation
Install the SDK using pip:
```bash theme={null}
pip install dodopayments
```
For enhanced async performance with aiohttp:
```bash theme={null}
pip install dodopayments[aiohttp]
```
The SDK requires Python 3.7 or higher. We recommend using the latest stable version of Python for the best experience and security updates.
## Quick Start
### Synchronous Client
```python theme={null}
import os
from dodopayments import DodoPayments
client = DodoPayments(
bearer_token=os.environ.get("DODO_PAYMENTS_API_KEY"), # This is the default and can be omitted
environment="test_mode", # defaults to "live_mode"
)
checkout_session_response = client.checkout_sessions.create(
product_cart=[
{
"product_id": "product_id",
"quantity": 1
}
],
)
print(checkout_session_response.session_id)
```
### Asynchronous Client
```python theme={null}
import os
import asyncio
from dodopayments import AsyncDodoPayments
client = AsyncDodoPayments(
bearer_token=os.environ.get("DODO_PAYMENTS_API_KEY"),
environment="test_mode",
)
async def main() -> None:
checkout_session_response = await client.checkout_sessions.create(
product_cart=[
{
"product_id": "product_id",
"quantity": 1,
}
],
)
print(checkout_session_response.session_id)
asyncio.run(main())
```
Always store your API keys securely using environment variables. Never commit them to version control.
## Core Features
Clean, idiomatic Python code that follows PEP 8 guidelines and Python conventions
Full support for asynchronous operations with asyncio and optional aiohttp integration
Complete type hints for better IDE support and type checking with mypy
Automatic pagination for list responses with simple iteration
## Configuration
### Environment Variables
Configure using environment variables:
```bash .env theme={null}
DODO_PAYMENTS_API_KEY=your_api_key_here
```
### Timeouts
Configure request timeouts globally or per-request:
```python theme={null}
import httpx
from dodopayments import DodoPayments
# Configure default for all requests (default is 1 minute)
client = DodoPayments(
timeout=20.0, # 20 seconds
)
# More granular control
client = DodoPayments(
timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0),
)
# Override per-request
client.with_options(timeout=5.0).checkout_sessions.create(
product_cart=[
{
"product_id": "product_id",
"quantity": 0,
}
],
)
```
### Retries
Configure automatic retry behavior:
```python theme={null}
from dodopayments import DodoPayments
# Configure default for all requests (default is 2)
client = DodoPayments(
max_retries=0, # disable retries
)
# Override per-request
client.with_options(max_retries=5).checkout_sessions.create(
product_cart=[
{
"product_id": "product_id",
"quantity": 0,
}
],
)
```
## Common Operations
### Create a Checkout Session
Generate a checkout session:
```python theme={null}
session = client.checkout_sessions.create(
product_cart=[
{
"product_id": "prod_123",
"quantity": 1
}
],
return_url="https://yourdomain.com/return"
)
print(f"Checkout URL: {session.url}")
```
### Manage Customers
Create and retrieve customer information:
```python theme={null}
# Create a customer
customer = client.customers.create(
email="customer@example.com",
name="John Doe",
metadata={
"user_id": "12345"
}
)
# Retrieve customer
customer = client.customers.retrieve("cus_123")
print(f"Customer: {customer.name} ({customer.email})")
```
### Handle Subscriptions
Create and manage recurring subscriptions:
```python theme={null}
# Create a subscription
subscription = client.subscriptions.create(
customer_id="cus_123",
product_id="prod_456",
price_id="price_789"
)
# Retrieve usage history
usage_history = client.subscriptions.retrieve_usage_history(
subscription.id,
start_date="2024-01-01T00:00:00Z"
)
```
## Usage-Based Billing
### Ingest Usage Events
Track custom events for usage-based billing:
```python theme={null}
response = client.usage_events.ingest(
events=[
{
"event_id": "api_call_12345",
"customer_id": "cus_abc123",
"event_name": "api_request",
"timestamp": "2024-01-15T10:30:00Z",
"metadata": {
"endpoint": "/api/v1/users",
"method": "GET",
"tokens_used": "150"
}
}
]
)
```
### List and Retrieve Events
```python theme={null}
# Get a specific event
event = client.usage_events.retrieve("api_call_12345")
# List events with filtering
events = client.usage_events.list(
customer_id="cus_abc123",
event_name="api_request",
limit=20
)
for event in events.data:
print(f"Event: {event.event_id} at {event.timestamp}")
```
## Pagination
### Auto-Pagination
Iterate through all items automatically:
```python theme={null}
from dodopayments import DodoPayments
client = DodoPayments()
all_payments = []
# Automatically fetches more pages as needed
for payment in client.payments.list():
all_payments.append(payment)
print(all_payments)
```
### Async Pagination
```python theme={null}
import asyncio
from dodopayments import AsyncDodoPayments
client = AsyncDodoPayments()
async def main() -> None:
all_payments = []
# Iterate through items across all pages
async for payment in client.payments.list():
all_payments.append(payment)
print(all_payments)
asyncio.run(main())
```
### Manual Pagination
For more control over pagination:
```python theme={null}
# Access items from current page
first_page = client.payments.list()
for payment in first_page.items:
print(payment.brand_id)
# Check for more pages
if first_page.has_next_page():
next_page = first_page.get_next_page()
print(f"Fetched {len(next_page.items)} more items")
```
## HTTP Client Configuration
Customize the underlying `httpx` client:
```python theme={null}
import httpx
from dodopayments import DodoPayments, DefaultHttpxClient
client = DodoPayments(
base_url="http://my.test.server.example.com:8083",
http_client=DefaultHttpxClient(
proxy="http://my.test.proxy.example.com",
transport=httpx.HTTPTransport(local_address="0.0.0.0"),
),
)
```
## Async with aiohttp
Use aiohttp for enhanced async performance:
```python theme={null}
import asyncio
from dodopayments import DefaultAioHttpClient
from dodopayments import AsyncDodoPayments
async def main() -> None:
async with AsyncDodoPayments(
bearer_token="My Bearer Token",
http_client=DefaultAioHttpClient(),
) as client:
checkout_session_response = await client.checkout_sessions.create(
product_cart=[
{
"product_id": "product_id",
"quantity": 0,
}
],
)
print(checkout_session_response.session_id)
asyncio.run(main())
```
## Logging
Enable logging by setting the environment variable:
```bash theme={null}
export DODO_PAYMENTS_LOG=info
```
Or for debug-level logging:
```bash theme={null}
export DODO_PAYMENTS_LOG=debug
```
## Framework Integration
### FastAPI
```python theme={null}
from fastapi import FastAPI, HTTPException
from dodopayments import AsyncDodoPayments
from pydantic import BaseModel
import os
app = FastAPI()
dodo = AsyncDodoPayments(bearer_token=os.getenv("DODO_API_KEY"))
class CheckoutRequest(BaseModel):
product_id: str
quantity: int
@app.post("/create-checkout")
async def create_checkout(request: CheckoutRequest):
try:
session = await dodo.checkout_sessions.create(
product_cart=[{
"product_id": request.product_id,
"quantity": request.quantity
}],
return_url="https://yourdomain.com/return"
)
return {"checkout_url": session.url}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
```
### Django
```python theme={null}
from django.http import JsonResponse
from dodopayments import DodoPayments
import os
dodo = DodoPayments(bearer_token=os.getenv("DODO_API_KEY"))
def create_payment(request):
try:
payment = dodo.payments.create(
amount=5000,
currency="USD",
customer_id=request.user.customer_id
)
return JsonResponse({
"status": "success",
"payment_id": payment.id
})
except Exception as e:
return JsonResponse({
"status": "error",
"message": str(e)
}, status=400)
```
## Resources
View source code and contribute
Complete API documentation
Get help and connect with developers
Report bugs or request features
## Support
Need help with the Python SDK?
* **Discord**: Join our [community server](https://discord.gg/bYqAp4ayYh) for real-time support
* **Email**: Contact us at [support@dodopayments.com](mailto:support@dodopayments.com)
* **GitHub**: Open an issue on the [repository](https://github.com/dodopayments/dodopayments-python)
## Contributing
We welcome contributions! Check the [contributing guidelines](https://github.com/dodopayments/dodopayments-python/blob/main/CONTRIBUTING.md) to get started.
# Ruby
Source: https://docs.dodopayments.com/developer-resources/sdks/ruby
Integrate Dodo Payments into your Ruby applications with an elegant, Ruby-native SDK
The Ruby SDK provides a simple and intuitive way to integrate Dodo Payments into your Ruby applications. It follows Ruby conventions and best practices, offering comprehensive error handling, pagination, and middleware support.
## Installation
Add the gem to your Gemfile:
```ruby Gemfile theme={null}
gem "dodopayments", "~> 1.53.4"
```
Then run:
```bash theme={null}
bundle install
```
The SDK supports Ruby 3.2.0 and later versions, with comprehensive types, error handling, and retry mechanisms.
## Quick Start
Initialize the client and create a checkout session:
```ruby theme={null}
require "bundler/setup"
require "dodopayments"
dodo_payments = Dodopayments::Client.new(
bearer_token: ENV["DODO_PAYMENTS_API_KEY"], # This is the default and can be omitted
environment: "test_mode" # defaults to "live_mode"
)
checkout_session_response = dodo_payments.checkout_sessions.create(
product_cart: [{product_id: "product_id", quantity: 1}]
)
puts(checkout_session_response.session_id)
```
Store your API keys securely using environment variables. Never commit them to version control or expose them in your code.
## Core Features
Follows Ruby naming conventions and idiomatic patterns
Clean, readable API that feels natural to Ruby developers
Built-in auto-paging iterators for list responses
Optional Sorbet types for enhanced type safety
## Configuration
### Timeout Configuration
Configure request timeouts:
```ruby theme={null}
# Configure default for all requests (default is 60 seconds)
dodo_payments = Dodopayments::Client.new(
timeout: nil # disable timeout
)
# Or, configure per-request
dodo_payments.checkout_sessions.create(
product_cart: [{product_id: "product_id", quantity: 0}],
request_options: {timeout: 5}
)
```
### Retry Configuration
Configure automatic retry behavior:
```ruby theme={null}
# Configure default for all requests (default is 2)
dodo_payments = Dodopayments::Client.new(
max_retries: 0 # disable retries
)
# Or, configure per-request
dodo_payments.checkout_sessions.create(
product_cart: [{product_id: "product_id", quantity: 0}],
request_options: {max_retries: 5}
)
```
## Common Operations
### Create a Checkout Session
Generate a checkout session:
```ruby theme={null}
session = dodo_payments.checkout_sessions.create(
product_cart: [
{
product_id: "prod_123",
quantity: 1
}
],
return_url: "https://yourdomain.com/return"
)
# Redirect to checkout
redirect_to session.url
```
### Manage Customers
Create and retrieve customer information:
```ruby theme={null}
# Create a customer
customer = dodo_payments.customers.create(
email: "customer@example.com",
name: "John Doe",
metadata: {
user_id: "12345"
}
)
# Retrieve customer
customer = dodo_payments.customers.retrieve("cus_123")
puts "Customer: #{customer.name} (#{customer.email})"
```
### Handle Subscriptions
Create and manage recurring subscriptions:
```ruby theme={null}
# Create a subscription
subscription = dodo_payments.subscriptions.create(
customer_id: "cus_123",
product_id: "prod_456",
price_id: "price_789"
)
# Update subscription
updated = dodo_payments.subscriptions.update(
subscription.id,
metadata: { plan_type: "premium" }
)
# Cancel subscription
dodo_payments.subscriptions.cancel(subscription.id)
```
## Pagination
### Auto-Pagination
Automatically iterate through all pages:
```ruby theme={null}
page = dodo_payments.payments.list
# Fetch single item from page
payment = page.items[0]
puts(payment.brand_id)
# Automatically fetches more pages as needed
page.auto_paging_each do |payment|
puts(payment.brand_id)
end
```
### Manual Pagination
For more control over pagination:
```ruby theme={null}
page = dodo_payments.payments.list
if page.next_page?
new_page = page.next_page
puts(new_page.items[0].brand_id)
end
```
## Error Handling
Handle various Dodo Payments API errors:
```ruby theme={null}
begin
checkout_session = dodo_payments.checkout_sessions.create(
product_cart: [{product_id: "product_id", quantity: 0}]
)
rescue Dodopayments::Errors::APIConnectionError => e
puts("The server could not be reached")
puts(e.cause) # an underlying Exception, likely raised within `net/http`
rescue Dodopayments::Errors::RateLimitError => e
puts("A 429 status code was received; we should back off a bit.")
rescue Dodopayments::Errors::APIStatusError => e
puts("Another non-200-range status code was received")
puts(e.status)
end
```
Implement retry logic with exponential backoff for rate limit errors to ensure your application handles high-volume scenarios gracefully.
## Type Safety with Sorbet
Use Sorbet for type-safe request parameters:
```ruby theme={null}
# Type-safe using Sorbet RBI definitions
dodo_payments.checkout_sessions.create(
product_cart: [
Dodopayments::CheckoutSessionRequest::ProductCart.new(
product_id: "product_id",
quantity: 0
)
]
)
# Hashes work, but are not typesafe
dodo_payments.checkout_sessions.create(
product_cart: [{product_id: "product_id", quantity: 0}]
)
# You can also splat a full Params class
params = Dodopayments::CheckoutSessionCreateParams.new(
product_cart: [
Dodopayments::CheckoutSessionRequest::ProductCart.new(
product_id: "product_id",
quantity: 0
)
]
)
dodo_payments.checkout_sessions.create(**params)
```
## Advanced Usage
### Undocumented Endpoints
Make requests to undocumented endpoints:
```ruby theme={null}
response = dodo_payments.request(
method: :post,
path: '/undocumented/endpoint',
query: {"dog": "woof"},
headers: {"useful-header": "interesting-value"},
body: {"hello": "world"}
)
```
### Undocumented Parameters
Send undocumented parameters:
```ruby theme={null}
checkout_session_response = dodo_payments.checkout_sessions.create(
product_cart: [{product_id: "product_id", quantity: 0}],
request_options: {
extra_query: {my_query_parameter: value},
extra_body: {my_body_parameter: value},
extra_headers: {"my-header": value}
}
)
# Access undocumented response properties
puts(checkout_session_response[:my_undocumented_property])
```
## Rails Integration
### Create an Initializer
Create `config/initializers/dodo_payments.rb`:
```ruby theme={null}
require "dodopayments"
DODO_CLIENT = Dodopayments::Client.new(
bearer_token: Rails.application.credentials.dodo_api_key,
environment: Rails.env.production? ? "live_mode" : "test_mode"
)
```
### Service Object Pattern
Create a payment service:
```ruby theme={null}
# app/services/payment_service.rb
class PaymentService
def initialize
@client = DODO_CLIENT
end
def create_checkout(items)
@client.checkout_sessions.create(
product_cart: items,
return_url: Rails.application.routes.url_helpers.checkout_return_url
)
end
def process_payment(amount:, currency:, customer_id:)
@client.payments.create(
amount: amount,
currency: currency,
customer_id: customer_id
)
end
end
```
### Controller Integration
Use in your Rails controllers:
```ruby theme={null}
# app/controllers/checkouts_controller.rb
class CheckoutsController < ApplicationController
def create
service = PaymentService.new
session = service.create_checkout(checkout_params[:items])
redirect_to session.url, allow_other_host: true
rescue Dodopayments::Errors::APIError => e
flash[:error] = "Payment error: #{e.message}"
redirect_to cart_path
end
private
def checkout_params
params.require(:checkout).permit(items: [:product_id, :quantity])
end
end
```
## Sinatra Integration
Use with Sinatra applications:
```ruby theme={null}
require "sinatra"
require "dodopayments"
configure do
set :dodo_client, Dodopayments::Client.new(
bearer_token: ENV["DODO_API_KEY"]
)
end
post "/create-checkout" do
content_type :json
begin
session = settings.dodo_client.checkout_sessions.create(
product_cart: JSON.parse(request.body.read)["items"],
return_url: "#{request.base_url}/return"
)
{ checkout_url: session.url }.to_json
rescue Dodopayments::Errors::APIError => e
status 400
{ error: e.message }.to_json
end
end
```
## Resources
View source code and contribute
Complete API documentation
Get help and connect with developers
Report bugs or request features
## Support
Need help with the Ruby SDK?
* **Discord**: Join our [community server](https://discord.gg/bYqAp4ayYh) for real-time support
* **Email**: Contact us at [support@dodopayments.com](mailto:support@dodopayments.com)
* **GitHub**: Open an issue on the [repository](https://github.com/dodopayments/dodopayments-ruby)
## Contributing
We welcome contributions! Check the [contributing guidelines](https://github.com/dodopayments/dodopayments-ruby/blob/main/CONTRIBUTING.md) to get started.
# TypeScript
Source: https://docs.dodopayments.com/developer-resources/sdks/typescript
Integrate Dodo Payments into your TypeScript and Node.js applications with type safety and modern async/await support
The TypeScript SDK provides convenient server-side access to the Dodo Payments REST API for TypeScript and JavaScript applications. It features comprehensive type definitions, error handling, retries, timeouts, and auto-pagination for seamless payment processing.
## Installation
Install the SDK using your package manager of choice:
```bash npm theme={null}
npm install dodopayments
```
```bash yarn theme={null}
yarn add dodopayments
```
```bash pnpm theme={null}
pnpm add dodopayments
```
## Quick Start
Initialize the client with your API key and start processing payments:
```javascript theme={null}
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env['DODO_PAYMENTS_API_KEY'], // This is the default and can be omitted
environment: 'test_mode', // defaults to 'live_mode'
});
const checkoutSessionResponse = await client.checkoutSessions.create({
product_cart: [{ product_id: 'product_id', quantity: 1 }],
});
console.log(checkoutSessionResponse.session_id);
```
Always store your API keys securely using environment variables. Never commit them to version control or expose them in client-side code.
## Core Features
Full TypeScript support with comprehensive type definitions for all API endpoints
Automatic pagination for list responses makes working with large datasets effortless
Built-in error types with detailed messages for different failure scenarios
Configurable automatic retries with exponential backoff for transient errors
## Configuration
### Environment Variables
Set environment variables for secure configuration:
```bash .env theme={null}
DODO_PAYMENTS_API_KEY=your_api_key_here
```
### Timeout Configuration
Configure request timeouts globally or per-request:
```typescript theme={null}
// Configure default timeout for all requests (default is 1 minute)
const client = new DodoPayments({
timeout: 20 * 1000, // 20 seconds
});
// Override per-request
await client.checkoutSessions.create(
{ product_cart: [{ product_id: 'product_id', quantity: 0 }] },
{ timeout: 5 * 1000 }
);
```
### Retry Configuration
Configure automatic retry behavior:
```javascript theme={null}
// Configure default for all requests (default is 2 retries)
const client = new DodoPayments({
maxRetries: 0, // disable retries
});
// Override per-request
await client.checkoutSessions.create(
{ product_cart: [{ product_id: 'product_id', quantity: 0 }] },
{ maxRetries: 5 }
);
```
The SDK automatically retries requests that fail due to network errors or server issues (5xx responses) with exponential backoff.
## Common Operations
### Create a Checkout Session
Generate a checkout session for collecting payment information:
```typescript theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{
product_id: 'prod_123',
quantity: 1
}
],
return_url: 'https://yourdomain.com/return'
});
console.log('Redirect to:', session.url);
```
### Manage Customers
Create and retrieve customer information:
```typescript theme={null}
// Create a customer
const customer = await client.customers.create({
email: 'customer@example.com',
name: 'John Doe',
metadata: {
user_id: '12345'
}
});
// Retrieve customer
const retrieved = await client.customers.retrieve('cus_123');
console.log(`Customer: ${retrieved.name} (${retrieved.email})`);
```
### Handle Subscriptions
Create and manage recurring subscriptions:
```typescript theme={null}
// Create a subscription
const subscription = await client.subscriptions.create({
customer_id: 'cus_123',
product_id: 'prod_456',
price_id: 'price_789'
});
// Retrieve subscription usage history
const usageHistory = await client.subscriptions.retrieveUsageHistory('sub_123', {
start_date: '2024-01-01T00:00:00Z',
end_date: '2024-03-31T23:59:59Z'
});
```
## Usage-Based Billing
### Ingest Usage Events
Track custom events for usage-based billing:
```typescript theme={null}
await client.usageEvents.ingest({
events: [
{
event_id: 'api_call_12345',
customer_id: 'cus_abc123',
event_name: 'api_request',
timestamp: '2024-01-15T10:30:00Z',
metadata: {
endpoint: '/api/v1/users',
method: 'GET',
tokens_used: '150'
}
}
]
});
```
Events must have unique `event_id` values for idempotency. Duplicate IDs within the same request are rejected, and subsequent requests with existing IDs are ignored.
### Retrieve Usage Events
Fetch detailed information about usage events:
```typescript theme={null}
// Get a specific event
const event = await client.usageEvents.retrieve('api_call_12345');
// List events with filtering
const events = await client.usageEvents.list({
customer_id: 'cus_abc123',
event_name: 'api_request',
start: '2024-01-14T10:30:00Z',
end: '2024-01-15T10:30:00Z'
});
```
## Proxy Configuration
Configure proxy settings for different runtimes:
### Node.js (using undici)
```typescript theme={null}
import DodoPayments from 'dodopayments';
import * as undici from 'undici';
const proxyAgent = new undici.ProxyAgent('http://localhost:8888');
const client = new DodoPayments({
fetchOptions: {
dispatcher: proxyAgent,
},
});
```
### Bun
```typescript theme={null}
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
fetchOptions: {
proxy: 'http://localhost:8888',
},
});
```
### Deno
```typescript theme={null}
import DodoPayments from 'npm:dodopayments';
const httpClient = Deno.createHttpClient({ proxy: { url: 'http://localhost:8888' } });
const client = new DodoPayments({
fetchOptions: {
client: httpClient,
},
});
```
## Logging
Control log verbosity using environment variables or client options:
```typescript theme={null}
// Via client option
const client = new DodoPayments({
logLevel: 'debug', // Show all log messages
});
```
```bash theme={null}
# Via environment variable
export DODO_PAYMENTS_LOG=debug
```
**Available log levels:**
* `'debug'` - Show debug messages, info, warnings, and errors
* `'info'` - Show info messages, warnings, and errors
* `'warn'` - Show warnings and errors (default)
* `'error'` - Show only errors
* `'off'` - Disable all logging
At the debug level, all HTTP requests and responses are logged, including headers and bodies. Some authentication headers are redacted, but sensitive data in bodies may still be visible.
## Migration from Node.js SDK
If you're upgrading from the legacy Node.js SDK, the TypeScript SDK offers improved type safety and features:
Learn how to migrate from the Node.js SDK to the TypeScript SDK
## Resources
View source code and contribute
Complete API documentation
Get help and connect with developers
Report bugs or request features
## Support
Need help with the TypeScript SDK?
* **Discord**: Join our [community server](https://discord.gg/bYqAp4ayYh) for real-time support
* **Email**: Contact us at [support@dodopayments.com](mailto:support@dodopayments.com)
* **GitHub**: Open an issue on the [repository](https://github.com/dodopayments/dodopayments-typescript)
## Contributing
We welcome contributions! Check the [contributing guidelines](https://github.com/dodopayments/dodopayments-typescript/blob/main/CONTRIBUTING.md) to get started.
# Seat-Based Pricing with Add-ons and Payment Links
Source: https://docs.dodopayments.com/developer-resources/seat-based-pricing
Learn how to create add-ons for seat-based pricing and generate payment links with custom add-on quantities using Dodo Payments
In this tutorial, you'll learn how to implement seat-based pricing using Dodo Payments add-ons. We'll create a subscription product with add-ons for additional seats and show you how to generate payment links with custom add-on quantities.
This tutorial provides sample implementation code for a Node.js/Express application. You can modify this code for your specific framework (Next.js, React, Vue, etc.) and customize the user interface according to your application's needs.
By the end of this tutorial, you'll know how to:
* Create subscription products with seat-based pricing
* Set up add-ons for additional seats
* Generate payment links with custom add-on quantities
* Handle checkout sessions with dynamic seat counts
## What We're Building
Let's create a seat-based pricing model:
* **Base Plan**: \$49/month for up to 5 team members
* **Seat Add-on**: \$2/month per additional seat
* **Payment Links**: Dynamic checkout with custom seat quantities
Before we start, make sure you have:
* A Dodo Payments account
* Basic familiarity with TypeScript/Node.js
## Step 1: Create Your Seat Add-On
Now we need to create an add-on that represents additional seats. This add-on will be attached to our base subscription and allow customers to purchase additional seats.
**What we're building**: An add-on that costs \$2/month per seat and can be added to any base subscription.
1. In your Dodo Payments dashboard, stay in the **Products** section
2. Click on the **Add-Ons** tab
3. Click **Create Add-On**
This will open the add-on creation form.
Fill in these values for our seat add-on:
**Add-On Name**: `Additional Team Seat`
**Description**: `Add extra team members to your workspace with full access to all features`
**Price**: Enter → `2.00`
**Currency**: Must match your base subscription currency
**Tax Category**: Select appropriate category for your product.
1. Review all your settings:
* Name: Additional Team Seat
* Price: \$2.00/month
2. Click **Create Add-On**
**Add-on created!** Your seat add-on is now available to attach to subscriptions.
## Step 2: Create Your Base Subscription Product
We'll start by creating a base subscription product that includes 5 team members. This will be the foundation of our seat-based pricing model.
1. Log into your Dodo Payments dashboard
2. Click on **Products** in the left sidebar
3. Click the **Create Product** button
4. Select **Subscription** as the product type
You should see a form where we'll configure our base subscription.
Now we'll enter the specific details for our base plan:
**Product Name**: `Motion`
**Description**: `Where your team's documentation lives.`
**Recurring Price**: Enter → `49.00`
**Billing Cycle**: Select → `Monthly`
**Currency**: Select your preferred currency (e.g., `USD`)
## Step 3: Connect Add-On to Subscription
Now we need to associate our seat add-on with the base subscription so customers can purchase additional seats during checkout.
1. Scroll down to the **Add-Ons** section
2. Click **Add Add-Ons**
3. From the dropdown, select your seat add-on
4. Confirm that it appears in your subscription configuration
1. Review your complete subscription setup:
* Base plan: \$49/month for 5 seats
* Add-on: \$2/month per additional seat
* Free trial: 14 days
2. Click **Save Changes**
**Seat-based pricing configured!** Customers can now purchase your base plan and add extra seats as needed.
## Step 4: Generate Payment Links with Custom Add-on Quantities
Now let's create an Express.js application that generates payment links with custom add-on quantities. This is where the real power of seat-based pricing comes in - you can dynamically create checkout sessions with any number of additional seats.
Create a new Node.js project and install the required dependencies:
```bash theme={null}
mkdir seat-based-pricing
cd seat-based-pricing
npm init -y
npm install dodopayments express dotenv
npm install -D @types/node @types/express typescript ts-node
```
Create a `tsconfig.json` file:
```json theme={null}
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
```
Create a `.env` file with your Dodo Payments API key:
```bash theme={null}
DODO_PAYMENTS_API_KEY=your_actual_dodo_api_key_here
```
Never commit your API key to version control. Add `.env` to your `.gitignore` file.
Create a `src/server.ts` file with the following code:
```typescript server.ts expandable theme={null}
// Add this new endpoint for dynamic seat quantities
import 'dotenv/config';
import DodoPayments from 'dodopayments';
import express, { Request, Response } from 'express';
const app = express();
// Initialize the Dodo Payments client
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: 'test_mode'
});
async function createCheckoutSession(seatCount: number) {
try {
const session = await client.checkoutSessions.create({
// Products to sell - use IDs from your Dodo Payments dashboard
product_cart: [
{
product_id: 'pdt_7Rl9OWT2Mz4wwUTKz74iZ', // Replace with your actual product ID
quantity: 1,
addons: [
{
addon_id: 'adn_eKQbNakKrivDpaxmI8wKI', // Replace with your actual addon ID
quantity: seatCount
}
]
}
],
// Pre-fill customer information to reduce friction
customer: {
email: 'steve@example.com',
name: 'Steve Irwin',
},
// Where to redirect after successful payment
return_url: 'https://example.com/checkout/success',
});
// Redirect your customer to this URL to complete payment
console.log('Checkout URL:', session.checkout_url);
console.log('Session ID:', session.session_id);
return session;
} catch (error) {
console.error('Failed to create checkout session:', error);
throw error;
}
}
// Example usage in an Express.js route
app.post('/create-checkout/:seatCount', async (req: Request, res: Response) => {
try {
const seatCount = parseInt(req.params.seatCount);
const session = await createCheckoutSession(seatCount);
res.json({ checkout_url: session.checkout_url });
} catch (error) {
res.status(500).json({ error: 'Failed to create checkout session' });
}
});
// Add this line after your other middleware
app.use(express.static('public'));
// Add this route to serve the demo page
app.get('/', (req, res) => {
res.sendFile(__dirname + '/../public/index.html');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
```
Create a `public/index.html` file for easy testing:
```html index.html expandable theme={null}
Seat-Based Pricing Demo
Seat-Based Pricing Demo
Generate checkout links with custom seat quantities:
```
**Web interface created!** You now have a simple UI to test different seat quantities.
Add this to your `src/server.ts` to serve the HTML file:
```typescript theme={null}
// Add this line after your other middleware
app.use(express.static('public'));
// Add this route to serve the demo page
app.get('/', (req, res) => {
res.sendFile(__dirname + '/../public/index.html');
});
```
**Static files configured!** Visit `http://localhost:3000` to see your demo interface.
## Step 5: Test Your Implementation
Let's test our seat-based pricing implementation to make sure everything works correctly.
1. Make sure you have your `.env` file with the correct API key
2. Update the product and add-on IDs in your code with the actual values from your Dodo Payments dashboard
3. Start your server:
```bash theme={null}
npm run dev
```
Your server should start successfully and show "Server running on [http://localhost:3000](http://localhost:3000)"
1. Open your browser and go to `http://localhost:3000`
2. You should see the seat-based pricing demo interface
3. Try different seat quantities (0, 3, 10, etc.)
4. Click "Generate Checkout Link" for each quantity
5. Verify that the checkout URLs are generated correctly
1. Generate a checkout link with 3 additional seats
2. Click the checkout URL to open the Dodo Payments checkout
3. Verify that the checkout shows:
* Base plan: \$49/month
* Additional seats: 3 × 2 dollars = \$6/month
4. Complete the test purchase
The checkout should display the correct pricing breakdown and allow you to complete the purchase.
To keep your database in sync with subscription and seat changes, you need to listen for webhook events from Dodo Payments. Webhooks notify your backend when a customer completes checkout, updates their subscription, or changes seat counts.
Follow the official Dodo Payments webhooks guide for step-by-step instructions on setting up webhook endpoints and handling events:
Learn how to securely receive and process webhook events for subscription and seat management.
## Troubleshooting
Common issues and their solutions:
**Possible causes:**
* Invalid product ID or add-on ID
* API key doesn't have sufficient permissions
* Add-on not properly associated with subscription
* Network connectivity issues
**Solutions:**
1. Verify product and add-on IDs exist in your Dodo Payments dashboard
2. Check that add-on is properly attached to the subscription
3. Ensure API key has checkout session creation permissions
4. Test API connectivity with a simple GET request
## Congratulations! You've Implemented Seat-Based Pricing
You've successfully created a seat-based pricing system with Dodo Payments! Here's what you accomplished:
Created a subscription product with 5 included seats at \$49/month
Configured add-ons for additional seats at \$2/month per seat
Built an API that generates checkout sessions with custom seat quantities
Created a simple web interface for testing different seat quantities
This example demonstrates only a minimal implementation of seat-based pricing. For production use, you should add robust error handling, authentication, data validation, security measures, and adapt the logic to fit your application's requirements.
# Subscription Integration Guide
Source: https://docs.dodopayments.com/developer-resources/subscription-integration-guide
This guide will help you integrate the Dodo Payments Subscription Product into your website.
## Prerequisites
To integrate the Dodo Payments API, you'll need:
* A Dodo Payments merchant account
* API credentials (API key and webhook secret key) from the dashboard
For a more detailed guide on the prerequisites, check this [section](/developer-resources/integration-guide#dashboard-setup).
## API Integration
### Checkout Sessions
Use Checkout Sessions to sell subscription products with a secure, hosted checkout. Pass your subscription product in `product_cart` and redirect customers to the returned `checkout_url`.
You cannot mix subscription products with one-time products in the same checkout session.
```javascript theme={null}
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
});
async function main() {
const session = await client.checkoutSessions.create({
product_cart: [
{ product_id: 'prod_subscription_monthly', quantity: 1 }
],
// Optional: configure trials for subscription products
subscription_data: { trial_period_days: 14 },
customer: {
email: 'subscriber@example.com',
name: 'Jane Doe',
},
return_url: 'https://example.com/success',
});
console.log(session.checkout_url);
}
main();
```
```python theme={null}
import os
from dodopayments import DodoPayments
client = DodoPayments(
bearer_token=os.environ.get("DODO_PAYMENTS_API_KEY"),
)
session = client.checkout_sessions.create(
product_cart=[
{"product_id": "prod_subscription_monthly", "quantity": 1}
],
subscription_data={"trial_period_days": 14}, # optional
customer={
"email": "subscriber@example.com",
"name": "Jane Doe",
},
return_url="https://example.com/success",
)
print(session.checkout_url)
```
```javascript theme={null}
const response = await fetch('https://test.dodopayments.com/checkouts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.DODO_PAYMENTS_API_KEY}`
},
body: JSON.stringify({
product_cart: [
{ product_id: 'prod_subscription_monthly', quantity: 1 }
],
subscription_data: { trial_period_days: 14 }, // optional
customer: {
email: 'subscriber@example.com',
name: 'Jane Doe'
},
return_url: 'https://example.com/success'
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const session = await response.json();
console.log(session.checkout_url);
```
### API Response
The following is an example of the response:
```json theme={null}
{
"session_id": "cks_Gi6KGJ2zFJo9rq9Ukifwa",
"checkout_url": "https://test.checkout.dodopayments.com/session/cks_Gi6KGJ2zFJo9rq9Ukifwa"
}
```
Redirect the customer to `checkout_url`.
### Webhooks
When integrating subscriptions, you'll receive webhooks to track the subscription lifecycle. These webhooks help you manage subscription states and payment scenarios effectively.
To set up your webhook endpoint, please follow our [Detailed Integration Guide](/developer-resources/integration-guide#implementing-webhooks).
#### Subscription Event Types
The following webhook events track subscription status changes:
1. **`subscription.active`** - Subscription is successfully activated.
2. **`subscription.on_hold`** - Subscription is put on hold due to failed renewal.
3. **`subscription.failed`** - Subscription creation failed during mandate creation.
4. **`subscription.renewed`** - Subscription is renewed for the next billing period.
For reliable subscription lifecycle management, we recommend tracking these subscription events.
#### Payment Scenarios
**Successful Payment Flow**
When a payment succeeds, you'll receive the following webhooks:
1. `subscription.active` - Indicates subscription activation
2. `payment.succeeded` - Confirms the initial payment:
* For immediate billing (0 trial days): Expect within 2-10 minutes
* For trial days: whenever that ends
3. `subscription.renewed` - Indicates payment deduction and renewal for next cycle. (Basically, whenever payment gets deducted for subscription products, you will get `subscription.renewed` webhook along with `payment.succeeded`)
**Payment Failure Scenarios**
1. Subscription Failure
* `subscription.failed` - Subscription creation failed due to failure to create a mandate.
* `payment.failed` - Indicates failed payment.
**Best Practice**: To simplify implementation, we recommend primarily tracking subscription events for managing the subscription lifecycle.
### Sample Subscription event payload
***
| Property | Type | Required | Description |
| ------------- | ------ | -------- | -------------------------------------------------------------------------------------------- |
| `business_id` | string | Yes | The unique identifier for the business |
| `timestamp` | string | Yes | The timestamp of when the event occurred (not necessarily the same as when it was delivered) |
| `type` | string | Yes | The type of event. See [Event Types](#event-types) |
| `data` | object | Yes | The main data payload. See [Data Object](#data-object) |
## Changing Subscription Plans
You can upgrade or downgrade a subscription plan using the change plan API endpoint. This allows you to modify the subscription's product, quantity, and handle proration.
For detailed information about changing subscription plans, please refer to our Change Plan API documentation.
### Proration Options
When changing subscription plans, you have two options for handling the immediate charge:
#### 1. `prorated_immediately`
* Calculates the prorated amount based on the remaining time in the current billing cycle
* Charges the customer only for the difference between the old and new plan
* During a trial period, this will immediately switch the user to the new plan, charging the customer right away
#### 2. `full_immediately`
* Charges the customer the full subscription amount for the new plan
* Ignores any remaining time or credits from the previous plan
* Useful when you want to reset the billing cycle or charge the full amount regardless of proration
#### 3. `difference_immediately`
* When upgrading, the customer is immediately charged the difference between the two plan amounts.
* For example, if the current plan is 30 Dollars and the customer upgrades to an 80 Dollars, they are charged \$50 instantly.
* When downgrading, the unused amount from the current plan is added as internal credit and automatically applied to future subscription renewals.
* For example, if the current plan is 50 Dollars and the customer switches to a 20 Dollars plan, the remaining \$30 is credited and used toward the next billing cycle.
### Behavior
* When you invoke this API, Dodo Payments immediately initiates a charge based on your selected proration option
* If the plan change is a downgrade and you use `prorated_immediately`, credits will be automatically calculated and added to the subscription's credit balance. These credits are specific to that subscription and will only be used to offset future recurring payments of the same subscription
* The `full_immediately` option bypasses credit calculations and charges the complete new plan amount
**Choose your proration option carefully**: Use `prorated_immediately` for fair billing that accounts for unused time, or `full_immediately` when you want to charge the complete new plan amount regardless of the current billing cycle.
### Charge Processing
* The immediate charge initiated upon plan change usually completes processing in less than 2 minutes
* If this immediate charge fails for any reason, the subscription is automatically placed on hold until the issue is resolved
## On-Demand Subscriptions
On-demand subscriptions let you charge customers flexibly, not just on a fixed schedule. Contact support to enable this feature.
**To create an on-demand subscription:**
To create an on-demand subscription, use the [POST /subscriptions](/api-reference/subscriptions/post-subscriptions) API endpoint and include the `on_demand` field in your request body. This allows you to authorize a payment method without an immediate charge, or set a custom initial price.
**To charge an on-demand subscription:**
For subsequent charges, use the [POST /subscriptions/charge](/api-reference/subscriptions/create-charge) endpoint and specify the amount to charge the customer for that transaction.
For a complete, step-by-step guide—including request/response examples, safe retry policies, and webhook handling—see the On-Demand Subscriptions Guide.
# Subscription Upgrade & Downgrade Guide
Source: https://docs.dodopayments.com/developer-resources/subscription-upgrade-downgrade
Learn how to change a customer's subscription plan, handle proration, and process webhooks reliably.
Access the complete API documentation for changing subscription plans and test plan change requests interactively.
Learn how to create and manage subscriptions from scratch with our comprehensive integration guide.
## What is a subscription upgrade or downgrade?
Changing plans lets you move a customer between subscription tiers or quantities. Use it to:
* Align pricing with usage or features
* Move from monthly to annual (or vice versa)
* Adjust quantity for seat-based products
Plan changes can trigger an immediate charge depending on the proration mode you choose.
## When to use plan changes
* Upgrade when a customer needs more features, usage, or seats
* Downgrade when usage decreases
* Migrate users to a new product or price without cancelling their subscription
## Prerequisites
Before implementing subscription plan changes, ensure you have:
* A Dodo Payments merchant account with active subscription products
* API credentials (API key and webhook secret key) from the dashboard
* An existing active subscription to modify
* Webhook endpoint configured to handle subscription events
For detailed setup instructions, see our [Integration Guide](/developer-resources/integration-guide#dashboard-setup).
## Step-by-Step Implementation Guide
Follow this comprehensive guide to implement subscription plan changes in your application:
Before implementing, determine:
* Which subscription products can be changed to which others
* What proration mode fits your business model
* How to handle failed plan changes gracefully
* Which webhook events to track for state management
Test plan changes thoroughly in test mode before implementing in production.
Select the billing approach that aligns with your business needs:
Best for: SaaS applications wanting to charge fairly for unused time
* Calculates exact prorated amount based on remaining cycle time
* Charges a prorated amount based on unused time remaining in the cycle
* Provides transparent billing to customers
Best for: Clear upgrade/downgrade scenarios
* Upgrade: Charge immediate difference (e.g., $30→$80 = charge \$50)
* Downgrade: Credit remaining value for future renewals
* Simplifies billing logic and customer communication
Best for: When you want to reset the billing cycle
* Charges full amount of new plan immediately
* Ignores remaining time from old plan
* Useful for annual to monthly transitions
Use the Change Plan API to modify subscription details:
The ID of the active subscription to modify.
The new product ID to change the subscription to.
Number of units for the new plan (for seat-based products).
How to handle immediate billing: `prorated_immediately`, `full_immediately`, or `difference_immediately`.
Optional addons for the new plan. Leaving this empty removes any existing addons.
Set up webhook handling to track plan change outcomes:
* `subscription.active`: Plan change successful, subscription updated
* `subscription.plan_changed`: Subscription plan changed (upgrade/downgrade/addon update)
* `subscription.on_hold`: Plan change charge failed, subscription paused
* `payment.succeeded`: Immediate charge for plan change succeeded
* `payment.failed`: Immediate charge failed
Always verify webhook signatures and implement idempotent event processing.
Based on webhook events, update your application:
* Grant/revoke features based on new plan
* Update customer dashboard with new plan details
* Send confirmation emails about plan changes
* Log billing changes for audit purposes
Thoroughly test your implementation:
* Test all proration modes with different scenarios
* Verify webhook handling works correctly
* Monitor plan change success rates
* Set up alerts for failed plan changes
Your subscription plan change implementation is now ready for production use.
## Change Plan API
Use the Change Plan API to modify product, quantity, and proration behavior for an active subscription.
### Quick start examples
```javascript theme={null}
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
});
async function changePlan() {
const result = await client.subscriptions.changePlan('sub_123', {
product_id: 'prod_new',
quantity: 3,
proration_billing_mode: 'prorated_immediately',
});
console.log(result.status, result.invoice_id, result.payment_id);
}
changePlan();
```
```python theme={null}
import os
from dodopayments import DodoPayments
client = DodoPayments(bearer_token=os.environ.get("DODO_PAYMENTS_API_KEY"))
result = client.subscriptions.change_plan(
subscription_id="sub_123",
product_id="prod_new",
quantity=3,
proration_billing_mode="prorated_immediately",
)
print(result.status, result.get("invoice_id"), result.get("payment_id"))
```
```go theme={null}
package main
import (
"context"
"fmt"
"github.com/dodopayments/dodopayments-go"
"github.com/dodopayments/dodopayments-go/option"
)
func main() {
client := dodopayments.NewClient(option.WithBearerToken("YOUR_TOKEN"))
res, err := client.Subscriptions.ChangePlan(context.TODO(), dodopayments.SubscriptionChangePlanParams{
SubscriptionID: dodopayments.F("sub_123"),
ProductID: dodopayments.F("prod_new"),
Quantity: dodopayments.F(int64(3)),
ProrationBillingMode: dodopayments.F(dodopayments.SubscriptionChangePlanParamsProrationBillingModeProratedImmediately),
})
if err != nil { panic(err) }
fmt.Println(res.Status, res.InvoiceID, res.PaymentID)
}
```
```bash theme={null}
curl -X POST "$DODO_API_BASE/subscriptions/sub_123/change-plan" \
-H "Authorization: Bearer $DODO_PAYMENTS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"product_id": "prod_new",
"quantity": 3,
"proration_billing_mode": "prorated_immediately"
}'
```
```json Success theme={null}
{
"status": "processing",
"subscription_id": "sub_123",
"invoice_id": "inv_789",
"payment_id": "pay_456",
"proration_billing_mode": "prorated_immediately"
}
```
Fields like invoice\_id and payment\_id are returned only when an immediate charge and/or invoice is created during the plan change. Always rely on webhook events (e.g., payment.succeeded, subscription.plan\_changed) to confirm outcomes.
If the immediate charge fails, the subscription may move to `subscription.on_hold` until payment succeeds.
## Managing Addons
When changing subscription plans, you can also modify addons:
```javascript theme={null}
// Add addons to the new plan
await client.subscriptions.changePlan('sub_123', {
product_id: 'prod_new',
quantity: 1,
proration_billing_mode: 'difference_immediately',
addons: [
{ addon_id: 'addon_123', quantity: 2 }
]
});
// Remove all existing addons
await client.subscriptions.changePlan('sub_123', {
product_id: 'prod_new',
quantity: 1,
proration_billing_mode: 'difference_immediately',
addons: [] // Empty array removes all existing addons
});
```
Addons are included in the proration calculation and will be charged according to the selected proration mode.
## Proration modes
Choose how to bill the customer when changing plans:
#### `prorated_immediately`
* Charges for the partial difference in the current cycle
* If in trial, charges immediately and switches to the new plan now
* Downgrade: may generate a prorated credit applied to future renewals
#### `full_immediately`
* Charges the full amount of the new plan immediately
* Ignores remaining time from the old plan
Credits created by downgrades using difference\_immediately are subscription-scoped and distinct from Customer Credits. They automatically apply to future renewals of the same subscription and are not transferable between subscriptions.
#### `difference_immediately`
* Upgrade: immediately charge the price difference between old and new plans
* Downgrade: add remaining value as internal credit to the subscription and auto-apply on renewals
### Example scenarios
```javascript theme={null}
await client.subscriptions.changePlan('sub_123', {
product_id: 'prod_pro',
quantity: 1,
proration_billing_mode: 'difference_immediately'
})
// Immediate charge: $50
```
```javascript theme={null}
await client.subscriptions.changePlan('sub_123', {
product_id: 'prod_starter',
quantity: 1,
proration_billing_mode: 'difference_immediately'
})
// Credit added: $30 (auto-applied to future renewals for this subscription)
```
```javascript theme={null}
await client.subscriptions.changePlan('sub_123', {
product_id: 'prod_new',
quantity: 1,
proration_billing_mode: 'prorated_immediately'
})
// Immediate prorated charge based on remaining days in cycle
```
```javascript theme={null}
await client.subscriptions.changePlan('sub_123', {
product_id: 'prod_new',
quantity: 1,
proration_billing_mode: 'full_immediately'
})
// Immediate full charge for new plan; no credits calculated
```
Pick `prorated_immediately` for fair-time accounting; choose `full_immediately` to restart billing; use `difference_immediately` for simple upgrades and automatic credit on downgrades.
## Handling webhooks
Track subscription state through webhooks to confirm plan changes and payments.
### Event types to handle
* `subscription.active`: subscription activated
* `subscription.plan_changed`: subscription plan changed (upgrade/downgrade/addon changes)
* `subscription.on_hold`: charge failed, subscription paused
* `subscription.renewed`: renewal succeeded
* `payment.succeeded`: payment for plan change or renewal succeeded
* `payment.failed`: payment failed
We recommend driving business logic from subscription events and using payment events for confirmation and reconciliation.
### Verify signatures and handle intents
```javascript theme={null}
import { NextRequest, NextResponse } from 'next/server';
export async function POST(req) {
const webhookId = req.headers.get('webhook-id');
const webhookSignature = req.headers.get('webhook-signature');
const webhookTimestamp = req.headers.get('webhook-timestamp');
const secret = process.env.DODO_WEBHOOK_SECRET;
const payload = await req.text();
// verifySignature is a placeholder – in production, use a Standard Webhooks library
const { valid, event } = await verifySignature(
payload,
{ id: webhookId, signature: webhookSignature, timestamp: webhookTimestamp },
secret
);
if (!valid) return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
switch (event.type) {
case 'subscription.active':
// mark subscription active in your DB
break;
case 'subscription.plan_changed':
// refresh entitlements and reflect the new plan in your UI
break;
case 'subscription.on_hold':
// notify user to update payment method
break;
case 'subscription.renewed':
// extend access window
break;
case 'payment.succeeded':
// reconcile payment for plan change
break;
case 'payment.failed':
// log and alert
break;
default:
// ignore unknown events
break;
}
return NextResponse.json({ received: true });
}
```
```javascript theme={null}
import express from 'express';
const app = express();
app.post('/webhooks/dodo', express.raw({ type: 'application/json' }), async (req, res) => {
const webhookId = req.header('webhook-id');
const webhookSignature = req.header('webhook-signature');
const webhookTimestamp = req.header('webhook-timestamp');
const secret = process.env.DODO_WEBHOOK_SECRET;
const payload = req.body.toString('utf8');
const { valid, event } = await verifySignature(
payload,
{ id: webhookId, signature: webhookSignature, timestamp: webhookTimestamp },
secret
);
if (!valid) return res.status(400).send('Invalid signature');
// handle events like above
res.json({ received: true });
});
app.listen(3000);
```
For detailed payload schemas, see the Subscription webhook payloads and Payment webhook payloads.
## Best Practices
Follow these recommendations for reliable subscription plan changes:
### Plan Change Strategy
* **Test thoroughly**: Always test plan changes in test mode before production
* **Choose proration carefully**: Select the proration mode that aligns with your business model
* **Handle failures gracefully**: Implement proper error handling and retry logic
* **Monitor success rates**: Track plan change success/failure rates and investigate issues
### Webhook Implementation
* **Verify signatures**: Always validate webhook signatures to ensure authenticity
* **Implement idempotency**: Handle duplicate webhook events gracefully
* **Process asynchronously**: Don't block webhook responses with heavy operations
* **Log everything**: Maintain detailed logs for debugging and audit purposes
### User Experience
* **Communicate clearly**: Inform customers about billing changes and timing
* **Provide confirmations**: Send email confirmations for successful plan changes
* **Handle edge cases**: Consider trial periods, prorations, and failed payments
* **Update UI immediately**: Reflect plan changes in your application interface
## Common Issues and Solutions
Resolve typical problems encountered during subscription plan changes:
**Symptoms**: API call succeeds but subscription remains on old plan
**Common causes**:
* Webhook processing failed or was delayed
* Application state not updated after receiving webhooks
* Database transaction issues during state update
**Solutions**:
* Implement robust webhook handling with retry logic
* Use idempotent operations for state updates
* Add monitoring to detect and alert on missed webhook events
* Verify webhook endpoint is accessible and responding correctly
**Symptoms**: Customer downgrades but doesn't see credit balance
**Common causes**:
* Proration mode expectations: downgrades credit the full plan price difference with `difference_immediately`, while `prorated_immediately` creates a prorated credit based on remaining time in the cycle
* Credits are subscription-specific and don't transfer between subscriptions
* Credit balance not visible in customer dashboard
**Solutions**:
* Use `difference_immediately` for downgrades when you want automatic credits
* Explain to customers that credits apply to future renewals of the same subscription
* Implement customer portal to show credit balances
* Check next invoice preview to see applied credits
**Symptoms**: Webhook events rejected due to invalid signature
**Common causes**:
* Incorrect webhook secret key
* Raw request body modified before signature verification
* Wrong signature verification algorithm
**Solutions**:
* Verify you're using the correct `DODO_WEBHOOK_SECRET` from dashboard
* Read raw request body before any JSON parsing middleware
* Use the standard webhook verification library for your platform
* Test webhook signature verification in development environment
**Symptoms**: API returns 422 Unprocessable Entity error
**Common causes**:
* Invalid subscription ID or product ID
* Subscription not in active state
* Missing required parameters
* Product not available for plan changes
**Solutions**:
* Verify subscription exists and is active
* Check product ID is valid and available
* Ensure all required parameters are provided
* Review API documentation for parameter requirements
**Symptoms**: Plan change initiated but immediate charge fails
**Common causes**:
* Insufficient funds on customer's payment method
* Payment method expired or invalid
* Bank declined the transaction
* Fraud detection blocked the charge
**Solutions**:
* Handle `payment.failed` webhook events appropriately
* Notify customer to update payment method
* Implement retry logic for temporary failures
* Consider allowing plan changes with failed immediate charges
## Testing Your Implementation
Follow these steps to thoroughly test your subscription plan change implementation:
* Use test API keys and test products
* Create test subscriptions with different plan types
* Configure test webhook endpoint
* Set up monitoring and logging
* Test `prorated_immediately` with various billing cycle positions
* Test `difference_immediately` for upgrades and downgrades
* Test `full_immediately` to reset billing cycles
* Verify credit calculations are correct
* Verify all relevant webhook events are received
* Test webhook signature verification
* Handle duplicate webhook events gracefully
* Test webhook processing failure scenarios
* Test with invalid subscription IDs
* Test with expired payment methods
* Test network failures and timeouts
* Test with insufficient funds
* Set up alerts for failed plan changes
* Monitor webhook processing times
* Track plan change success rates
* Review customer support tickets for plan change issues
## Error Handling
Handle common API errors gracefully in your implementation:
### HTTP Status Codes
Plan change request processed successfully. The subscription is being updated and payment processing has begun.
Invalid request parameters. Check that all required fields are provided and properly formatted.
Invalid or missing API key. Verify your `DODO_PAYMENTS_API_KEY` is correct and has proper permissions.
Subscription ID not found or doesn't belong to your account.
The subscription cannot be changed (e.g., already cancelled, product not available, etc.).
Server error occurred. Retry the request after a brief delay.
### Error Response Format
```json theme={null}
{
"error": {
"code": "subscription_not_found",
"message": "The subscription with ID 'sub_123' was not found",
"details": {
"subscription_id": "sub_123"
}
}
}
```
## Next steps
* Review the Change Plan API
* Explore Customer Credits
* Implement alerts for `subscription.on_hold`
* Check out our Webhook Integration Guide
# Supabase Boilerplate
Source: https://docs.dodopayments.com/developer-resources/supabase-boilerplate
Use the official Next.js + Supabase + Dodo Payments subscription starter to ship subscriptions fast. Includes auth, database schema, webhook handler, and pricing UI.
Minimal Next.js + Supabase + Dodo Payments subscription boilerplate
Explore the deployed demo
## Overview
A production-ready boilerplate for subscriptions using Next.js 15, React 19, Supabase, Drizzle ORM, and Dodo Payments. It ships with Google OAuth, subscription checkout, webhook handling, database schema, and a basic dashboard.
If you need only route handlers for an existing app, see the dedicated adaptors: Next.js Adaptor and Express Adaptor.
## Prerequisites
* Node.js 18+ (or Bun 1.0+)
* Supabase project (URL, Anon key, Service role key, Database URL)
* Dodo Payments account (API key, Webhook secret)
* Google Cloud OAuth client (Client ID and Secret)
## Quickstart
```bash theme={null}
git clone https://github.com/dodopayments/dodo-supabase-subscription-starter.git
cd dodo-supabase-subscription-starter
# choose one
bun install
# or
npm install
# or
pnpm install
```
Create a Supabase project and copy:
* NEXT\_PUBLIC\_SUPABASE\_URL
* NEXT\_PUBLIC\_SUPABASE\_ANON\_KEY
* SUPABASE\_SERVICE\_ROLE\_KEY
* DATABASE\_URL (Connection string)
Set the redirect URI to: `https://[your-project-ref].supabase.co/auth/v1/callback` in Google Cloud, then enable Google provider in Supabase Auth using your Client ID and Secret.
Generate an API key and Webhook secret from the Dodo dashboard. Set environment to `test_mode` while developing.
```env theme={null}
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://your-project-ref.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role
# Database
DATABASE_URL=postgresql://postgres:[password]@db.[project-ref].supabase.co:5432/postgres
# Dodo Payments
DODO_PAYMENTS_API_KEY=your-dodo-api-key
DODO_WEBHOOK_SECRET=your-webhook-secret
DODO_PAYMENTS_ENVIRONMENT=test_mode
```
Never commit secrets. Use environment variables in deployment environments.
```bash theme={null}
bun run db:push
# or
npm run db:push
# or
pnpm run db:push
```
Tables created: `users`, `subscriptions`, `payments`.
```bash theme={null}
# login (one-time)
bunx supabase login
# or
npx supabase login
# deploy the edge function
bun run deploy:webhook --project-ref [your-project-ref]
# or
npm run deploy:webhook -- --project-ref [your-project-ref]
# or
pnpm run deploy:webhook --project-ref [your-project-ref]
```
```bash cURL theme={null}
curl -X POST \
'https://[your-project-ref].supabase.co/functions/v1/dodo-webhook' \
-H 'Content-Type: application/json' \
-H 'Dodo-Signature: ' \
-d '{"type":"payment.succeeded","data":{}}'
```
Set endpoint URL to:
```text theme={null}
https://[your-project-ref].supabase.co/functions/v1/dodo-webhook
```
Select payment and subscription events.
In Dodo dashboard → Products → Create Product. Optionally add metadata:
```json theme={null}
{
"features": ["Feature 1", "Feature 2", "Feature 3"]
}
```
The pricing UI reads this `features` array and renders it dynamically.
```bash theme={null}
bun run dev
# or
npm run dev
# or
pnpm run dev
```
Open [http://localhost:3000](http://localhost:3000).
## What’s included
* Authentication via Supabase (Google OAuth configured)
* Subscription checkout via Dodo Payments
* Supabase Edge Function for webhooks (`dodo-webhook`)
* Drizzle ORM schema and migrations
* Dashboard with invoices, subscription status, and plan features
Keep `DODO_PAYMENTS_ENVIRONMENT` as `test_mode` until you complete end-to-end tests.
## Key files and paths
```text theme={null}
supabase/functions/dodo-webhook/
index.ts # webhook handler verifying signatures
deno.json # permissions
```
```text theme={null}
app/(marketing)/* # landing + pricing UI
app/(dashboard)/* # protected pages
app/api/* # server actions & helpers
```
```text theme={null}
lib/db/schema.ts # users, subscriptions, payments
lib/db/index.ts # client
```
## Environment variables
```env theme={null}
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=
DATABASE_URL=
```
```env theme={null}
DODO_PAYMENTS_API_KEY=
DODO_WEBHOOK_SECRET=
DODO_PAYMENTS_ENVIRONMENT=test_mode|live_mode
```
```env theme={null}
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
```
## Verification and troubleshooting
* Ensure `DODO_WEBHOOK_SECRET` matches the value from Dodo dashboard
* Confirm you deployed the latest `dodo-webhook` function
* Verify the header name is correct in your function (Dodo-Signature)
* Check `DATABASE_URL` syntax and Supabase network egress
* Wait \~2–3 minutes after project creation before first push
* Redirect URI must be `https://[ref].supabase.co/auth/v1/callback`
* Ensure the same in Google Cloud and Supabase Auth provider
You now have a working subscription SaaS scaffolded with Supabase and Dodo Payments.
Original repository and detailed steps: dodo-supabase-subscription-starter.
# SveleteKit Adaptor
Source: https://docs.dodopayments.com/developer-resources/sveltekit-adaptor
Learn how to integrate Dodo Payments with your SveleteKit App Router project using our SveleteKit Adaptor. Covers checkout, customer portal, webhooks, and secure environment setup.
Integrate Dodo Payments checkout into your SveleteKit app.
Allow customers to manage subscriptions and details.
Receive and process Dodo Payments webhook events.
## Installation
Run the following command in your project root:
```bash theme={null}
npm install @dodopayments/svelteKit
```
Create a .env file in your project root:
```env expandable theme={null}
DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode"
DODO_PAYMENTS_RETURN_URL=https://yourdomain.com/checkout/success
```
Never commit your .env file or secrets to version control.
## Route Handler Examples
All examples assume you are using the SveleteKit App Router.
Use this handler to integrate Dodo Payments checkout into your SveleteKit app. Supports static (GET), dynamic (POST), and session (POST) payment flows.
```typescript SveleteKit Route Handler expandable theme={null}
// src/routes/api/checkout/+server.ts
import { Checkout } from "@dodopayments/sveltekit";
import { DODO_PAYMENTS_API_KEY, DODO_PAYMENTS_RETURN_URL, DODO_PAYMENTS_ENVIRONMENT } from '$env/static/private';
const checkoutGetHandler = Checkout({
bearerToken: DODO_PAYMENTS_API_KEY,
returnUrl: DODO_PAYMENTS_RETURN_URL,
environment: DODO_PAYMENTS_ENVIRONMENT,
type: "static",
});
const checkoutPostHandler = Checkout({
bearerToken: DODO_PAYMENTS_API_KEY,
returnUrl: DODO_PAYMENTS_RETURN_URL,
environment: DODO_PAYMENTS_ENVIRONMENT,
type: "session", // or "dynamic" for dynamic link
});
export const GET = checkoutGetHandler.GET;
export const POST = checkoutPostHandler.POST;
```
```curl Static Checkout Curl example expandable theme={null}
curl --request GET \
--url 'https://example.com/api/checkout?productId=pdt_fqJhl7pxKWiLhwQR042rh' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test
```
```curl Dynamic Checkout Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"billing": {
"city": "Texas",
"country": "US",
"state": "Texas",
"street": "56, hhh",
"zipcode": "560000"
},
"customer": {
"email": "test@example.com",
"name": "test"
},
"metadata": {},
"payment_link": true,
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1,
"billing_currency": "USD",
"discount_code": "IKHZ23M9GQ",
"return_url": "https://example.com",
"trial_period_days": 10
}'
```
```curl Checkout Session Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"product_cart": [
{
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1
}
],
"customer": {
"email": "test@example.com",
"name": "test"
},
"return_url": "https://example.com/success"
}'
```
Use this handler to allow customers to manage their subscriptions and details via the Dodo Payments customer portal.
```typescript SveleteKit Route Handler expandable theme={null}
// src/routes/api/checkout/+server.ts
import { CustomerPortal } from "@dodopayments/sveltekit";
import { DODO_PAYMENTS_API_KEY, DODO_PAYMENTS_RETURN_URL, DODO_PAYMENTS_ENVIRONMENT } from '$env/static/private';
const customerPortalHandler = CustomerPortal({
bearerToken: DODO_PAYMENTS_API_KEY,
environment: DODO_PAYMENTS_ENVIRONMENT,
});
export const GET = customerPortalHandler.GET;
```
Use this handler to receive and process Dodo Payments webhook events securely in your SveleteKit app.
```typescript SveleteKit Route Handler expandable theme={null}
// src/routes/api/webhook/+server.ts
import { Webhooks } from "@dodopayments/sveltekit";
import { DODO_PAYMENTS_WEBHOOK_KEY } from '$env/static/private';
export const POST = Webhooks({
webhookKey: DODO_PAYMENTS_WEBHOOK_KEY,
onPayload: async (payload) => {
console.log(payload);
}
});
```
## Checkout Route Handler
Dodo Payments supports three types of payment flows for integrating payments into your website, this adaptor supports all types of payment flows.
* **Static Payment Links:** Instantly shareable URLs for quick, no-code payment collection.
* **Dynamic Payment Links:** Programmatically generate payment links with custom details using the API or SDKs.
* **Checkout Sessions:** Create secure, customizable checkout experiences with pre-configured product carts and customer details.
### Supported Query Parameters
Product identifier (e.g., ?productId=pdt\_nZuwz45WAs64n3l07zpQR).
Quantity of the product.
Customer's full name.
Customer's first name.
Customer's last name.
Customer's email address.
Customer's country.
Customer's address line.
Customer's city.
Customer's state/province.
Customer's zip/postal code.
Disable full name field.
Disable first name field.
Disable last name field.
Disable email field.
Disable country field.
Disable address line field.
Disable city field.
Disable state field.
Disable zip code field.
Specify the payment currency (e.g., USD).
Show currency selector.
Specify the payment amount (e.g., 1000 for \$10.00).
Show discount fields.
Any query parameter starting with metadata\_ will be passed as metadata.
If productId is missing, the handler returns a 400 response. Invalid query parameters also result in a 400 response.
### Response Format
Static checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/"
}
```
* Send parameters as a JSON body in a POST request.
* Supports both one-time and recurring payments.
* For a complete list of supported POST body fields, refer to:
* [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)
### Response Format
Dynamic checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/"
}
```
Checkout sessions provide a more secure, hosted checkout experience that handles the complete payment flow for both one-time purchases and subscriptions with full customization control.
Refer to [Checkout Sessions Integration Guide](https://docs.dodopayments.com/developer-resources/checkout-session) for more details and a complete list of supported fields.
### Response Format
Checkout sessions return a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/session/..."
}
```
## Customer Portal Route Handler
The Customer Portal Route Handler enables you to seamlessly integrate the Dodo Payments customer portal into your SveleteKit application.
### Query Parameters
The customer ID for the portal session (e.g., ?customer\_id=cus\_123).
If set to true, sends an email to the customer with the portal link.
Returns 400 if customer\_id is missing.
## Webhook Route Handler
* **Method:** Only POST requests are supported. Other methods return 405.
* **Signature Verification:** Verifies the webhook signature using webhookKey. 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 the appropriate event handler based on the payload type.
### Supported Webhook Event Handlers
```typescript Typescript expandable theme={null}
onPayload?: (payload: WebhookPayload) => Promise;
onPaymentSucceeded?: (payload: WebhookPayload) => Promise;
onPaymentFailed?: (payload: WebhookPayload) => Promise;
onPaymentProcessing?: (payload: WebhookPayload) => Promise;
onPaymentCancelled?: (payload: WebhookPayload) => Promise;
onRefundSucceeded?: (payload: WebhookPayload) => Promise;
onRefundFailed?: (payload: WebhookPayload) => Promise;
onDisputeOpened?: (payload: WebhookPayload) => Promise;
onDisputeExpired?: (payload: WebhookPayload) => Promise;
onDisputeAccepted?: (payload: WebhookPayload) => Promise;
onDisputeCancelled?: (payload: WebhookPayload) => Promise;
onDisputeChallenged?: (payload: WebhookPayload) => Promise;
onDisputeWon?: (payload: WebhookPayload) => Promise;
onDisputeLost?: (payload: WebhookPayload) => Promise;
onSubscriptionActive?: (payload: WebhookPayload) => Promise;
onSubscriptionOnHold?: (payload: WebhookPayload) => Promise;
onSubscriptionRenewed?: (payload: WebhookPayload) => Promise;
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise;
onSubscriptionCancelled?: (payload: WebhookPayload) => Promise;
onSubscriptionFailed?: (payload: WebhookPayload) => Promise;
onSubscriptionExpired?: (payload: WebhookPayload) => Promise;
onLicenseKeyCreated?: (payload: WebhookPayload) => Promise;
```
***
## Prompt for LLM
```
You are an expert SvelteKit developer assistant. Your task is to guide a user through integrating the @dodopayments/sveltekit adapter into their existing SvelteKit project.
The @dodopayments/sveltekit adapter provides route handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, designed for the SvelteKit App Router.
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/sveltekit
Here's how you should structure your response:
Ask the user which functionalities they want to integrate.
"Which parts of the @dodopayments/sveltekit 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 app/checkout/route.ts in your SvelteKit project.
Code Snippet:
// src/routes/api/checkout/+server.ts
import { Checkout } from "@dodopayments/sveltekit";
import { DODO_PAYMENTS_API_KEY, DODO_PAYMENTS_RETURN_URL, DODO_PAYMENTS_ENVIRONMENT } from '$env/static/private';
const checkoutGetHandler = Checkout({
bearerToken: DODO_PAYMENTS_API_KEY,
returnUrl: DODO_PAYMENTS_RETURN_URL,
environment: DODO_PAYMENTS_ENVIRONMENT,
type: "static",
});
const checkoutPostHandler = Checkout({
bearerToken: DODO_PAYMENTS_API_KEY,
returnUrl: DODO_PAYMENTS_RETURN_URL,
environment: DODO_PAYMENTS_ENVIRONMENT,
type: "session", // or "dynamic" for dynamic link
});
export const GET = checkoutGetHandler.GET;
export const POST = checkoutPostHandler.POST;
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.
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. Defaults to "static".
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://checkout.dodopayments.com/..."}
Dynamic Checkout (POST) - Returns JSON with checkout_url: Parameters are sent as a JSON body. Supports both one-time and recurring payments. Returns: {"checkout_url": "https://checkout.dodopayments.com/..."}. 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. 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 parameters are invalid, the handler will return a 400 response.
If Customer Portal Route Handler is selected:
Purpose: This handler redirects authenticated users to their Dodo Payments customer portal.
File Creation: Create a new file at app/customer-portal/route.ts in your SvelteKit project.
Code Snippet:
// src/routes/api/checkout/+server.ts
import { CustomerPortal } from "@dodopayments/sveltekit";
import { DODO_PAYMENTS_API_KEY, DODO_PAYMENTS_RETURN_URL, DODO_PAYMENTS_ENVIRONMENT } from '$env/static/private';
const customerPortalHandler = CustomerPortal({
bearerToken: DODO_PAYMENTS_API_KEY,
environment: DODO_PAYMENTS_ENVIRONMENT,
});
export const GET = customerPortalHandler.GET;
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 app/api/webhook/dodo-payments/route.ts in your SvelteKit project.
Code Snippet:
// src/routes/api/webhook/+server.ts
import { Webhooks } from "@dodopayments/sveltekit";
import { DODO_PAYMENTS_WEBHOOK_KEY } from '$env/static/private';
export const POST = Webhooks({
webhookKey: DODO_PAYMENTS_WEBHOOK_KEY,
onPayload: async (payload) => {
console.log(payload);
}
});
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 the appropriate event handler based on the payload type.
Supported Webhook Event Handlers:
onPayload?: (payload: WebhookPayload) => Promise
onPaymentSucceeded?: (payload: WebhookPayload) => Promise
onPaymentFailed?: (payload: WebhookPayload) => Promise
onPaymentProcessing?: (payload: WebhookPayload) => Promise
onPaymentCancelled?: (payload: WebhookPayload) => Promise
onRefundSucceeded?: (payload: WebhookPayload) => Promise
onRefundFailed?: (payload: WebhookPayload) => Promise
onDisputeOpened?: (payload: WebhookPayload) => Promise
onDisputeExpired?: (payload: WebhookPayload) => Promise
onDisputeAccepted?: (payload: WebhookPayload) => Promise
onDisputeCancelled?: (payload: WebhookPayload) => Promise
onDisputeChallenged?: (payload: WebhookPayload) => Promise
onDisputeWon?: (payload: WebhookPayload) => Promise
onDisputeLost?: (payload: WebhookPayload) => Promise
onSubscriptionActive?: (payload: WebhookPayload) => Promise
onSubscriptionOnHold?: (payload: WebhookPayload) => Promise
onSubscriptionRenewed?: (payload: WebhookPayload) => Promise
onSubscriptionPaused?: (payload: WebhookPayload) => Promise
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise
onSubscriptionCancelled?: (payload: WebhookPayload) => Promise
onSubscriptionFailed?: (payload: WebhookPayload) => Promise
onSubscriptionExpired?: (payload: WebhookPayload) => Promise
onLicenseKeyCreated?: (payload: WebhookPayload) => Promise
Environment Variable Setup:
To ensure the adapter functions correctly, you will need to manually set up the following environment variables in your SvelteKit 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).
RETURN_URL: (Optional) The URL to redirect to after a successful checkout (for Checkout handler).
DODO_PAYMENTS_WEBHOOK_SECRET: Your Dodo Payments Webhook Secret (required for Webhook handler).
Example .env file:
DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
DODO_PAYMENTS_RETURN_URL=your-return-url
DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode"
Usage in your code:
bearerToken: process.env.DODO_PAYMENTS_API_KEY
webhookKey: process.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
```
# Tanstack Adaptor
Source: https://docs.dodopayments.com/developer-resources/tanstack-adaptor
Learn how to integrate Dodo Payments with your Tanstack App Router project using our Tanstack Adaptor. Covers checkout, customer portal, webhooks, and secure environment setup.
Integrate Dodo Payments checkout into your Tanstack app.
Allow customers to manage subscriptions and details.
Receive and process Dodo Payments webhook events.
## Installation
Run the following command in your project root:
```bash theme={null}
npm install @dodopayments/tanstack
```
Create a .env file in your project root:
```env expandable theme={null}
DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
DODO_PAYMENTS_RETURN_URL=your-return-url
DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode"
```
Never commit your .env file or secrets to version control.
## Route Handler Examples
All examples assume you are using the Tanstack App Router.
Use this handler to integrate Dodo Payments checkout into your Tanstack app. Supports static (GET), dynamic (POST), and session (POST) payment flows.
```typescript Tanstack Route Handler expandable theme={null}
// src/routes/api/checkout.ts
import { Checkout } from "@dodopayments/tanstack";
import { createServerFileRoute } from "@tanstack/react-start/server";
export const ServerRoute = createServerFileRoute("/api/checkout")
.methods({
GET: async ({ request }) => {
return Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "static", // optional, defaults to 'static'
})(request)
},
POST: async ({ request }) => {
return Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "session", // or "dynamic" for dynamic link
})(request)
}
})
```
```curl Static Checkout Curl example expandable theme={null}
curl --request GET \
--url 'https://example.com/api/checkout?productId=pdt_fqJhl7pxKWiLhwQR042rh' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test
```
```curl Dynamic Checkout Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"billing": {
"city": "Texas",
"country": "US",
"state": "Texas",
"street": "56, hhh",
"zipcode": "560000"
},
"customer": {
"email": "test@example.com",
"name": "test"
},
"metadata": {},
"payment_link": true,
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1,
"billing_currency": "USD",
"discount_code": "IKHZ23M9GQ",
"return_url": "https://example.com",
"trial_period_days": 10
}'
```
```curl Checkout Session Curl example expandable theme={null}
curl --request POST \
--url https://example.com/api/checkout \
--header 'Content-Type: application/json' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test \
--data '{
"product_cart": [
{
"product_id": "pdt_QMDuvLkbVzCRWRQjLNcs",
"quantity": 1
}
],
"customer": {
"email": "test@example.com",
"name": "test"
},
"return_url": "https://example.com/success"
}'
```
Use this handler to allow customers to manage their subscriptions and details via the Dodo Payments customer portal.
```typescript Tanstack Route Handler expandable theme={null}
// src/routes/api/customer-portal.ts
import { CustomerPortal } from "@dodopayments/tanstack";
import { createServerFileRoute } from "@tanstack/react-start/server";
export const ServerRoute = createServerFileRoute('/api/customer-portal')
.methods({
GET: async ({ request }) => {
return CustomerPortal({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
})(request)
}
})
```
```curl Customer Portal Curl example expandable theme={null}
curl --request GET \
--url 'https://example.com/api/customer-portal?customer_id=cus_9VuW4K7O3GHwasENg31m&send_email=true' \
--header 'User-Agent: insomnia/11.2.0' \
--cookie mode=test
```
Use this handler to receive and process Dodo Payments webhook events securely in your Tanstack app.
```typescript Tanstack Route Handler expandable theme={null}
// src/routes/api/webhook.ts
import { Webhooks } from "@dodopayments/tanstack";
import { createServerFileRoute } from "@tanstack/react-start/server";
export const ServerRoute = createServerFileRoute('/api/webhook')
.methods({
POST: async ({ request }) => {
return Webhooks({
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
onPayload: async (payload) => {
console.log(payload)
}
})(request)
}
})
```
## Checkout Route Handler
Dodo Payments supports three types of payment flows for integrating payments into your website, this adaptor supports all types of payment flows.
* **Static Payment Links:** Instantly shareable URLs for quick, no-code payment collection.
* **Dynamic Payment Links:** Programmatically generate payment links with custom details using the API or SDKs.
* **Checkout Sessions:** Create secure, customizable checkout experiences with pre-configured product carts and customer details.
### Supported Query Parameters
Product identifier (e.g., ?productId=pdt\_nZuwz45WAs64n3l07zpQR).
Quantity of the product.
Customer's full name.
Customer's first name.
Customer's last name.
Customer's email address.
Customer's country.
Customer's address line.
Customer's city.
Customer's state/province.
Customer's zip/postal code.
Disable full name field.
Disable first name field.
Disable last name field.
Disable email field.
Disable country field.
Disable address line field.
Disable city field.
Disable state field.
Disable zip code field.
Specify the payment currency (e.g., USD).
Show currency selector.
Specify the payment amount (e.g., 1000 for \$10.00).
Show discount fields.
Any query parameter starting with metadata\_ will be passed as metadata.
If productId is missing, the handler returns a 400 response. Invalid query parameters also result in a 400 response.
### Response Format
Static checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/..."
}
```
* Send parameters as a JSON body in a POST request.
* Supports both one-time and recurring payments.
* For a complete list of supported POST body fields, refer to:
* [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)
### Response Format
Dynamic checkout returns a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/..."
}
```
Checkout sessions provide a more secure, hosted checkout experience that handles the complete payment flow for both one-time purchases and subscriptions with full customization control.
Refer to [Checkout Sessions Integration Guide](https://docs.dodopayments.com/developer-resources/checkout-session) for more details and a complete list of supported fields.
### Response Format
Checkout sessions return a JSON response with the checkout URL:
```json theme={null}
{
"checkout_url": "https://checkout.dodopayments.com/session/..."
}
```
## Customer Portal Route Handler
The Customer Portal Route Handler enables you to seamlessly integrate the Dodo Payments customer portal into your Tanstack application.
### Query Parameters
The customer ID for the portal session (e.g., ?customer\_id=cus\_123).
If set to true, sends an email to the customer with the portal link.
Returns 400 if customer\_id is missing.
## Webhook Route Handler
* **Method:** Only POST requests are supported. Other methods return 405.
* **Signature Verification:** Verifies the webhook signature using webhookKey. 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 the appropriate event handler based on the payload type.
### Supported Webhook Event Handlers
```typescript Typescript expandable theme={null}
onPayload?: (payload: WebhookPayload) => Promise;
onPaymentSucceeded?: (payload: WebhookPayload) => Promise;
onPaymentFailed?: (payload: WebhookPayload) => Promise;
onPaymentProcessing?: (payload: WebhookPayload) => Promise;
onPaymentCancelled?: (payload: WebhookPayload) => Promise;
onRefundSucceeded?: (payload: WebhookPayload) => Promise;
onRefundFailed?: (payload: WebhookPayload) => Promise;
onDisputeOpened?: (payload: WebhookPayload) => Promise;
onDisputeExpired?: (payload: WebhookPayload) => Promise;
onDisputeAccepted?: (payload: WebhookPayload) => Promise;
onDisputeCancelled?: (payload: WebhookPayload) => Promise;
onDisputeChallenged?: (payload: WebhookPayload) => Promise;
onDisputeWon?: (payload: WebhookPayload) => Promise;
onDisputeLost?: (payload: WebhookPayload) => Promise;
onSubscriptionActive?: (payload: WebhookPayload) => Promise;
onSubscriptionOnHold?: (payload: WebhookPayload) => Promise;
onSubscriptionRenewed?: (payload: WebhookPayload) => Promise;
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise;
onSubscriptionCancelled?: (payload: WebhookPayload) => Promise;
onSubscriptionFailed?: (payload: WebhookPayload) => Promise;
onSubscriptionExpired?: (payload: WebhookPayload) => Promise;
onLicenseKeyCreated?: (payload: WebhookPayload) => Promise;
```
***
## Prompt for LLM
```
You are an expert Tanstack developer assistant. Your task is to guide a user through integrating the @dodopayments/tanstack adapter into their existing Tanstack project.
The @dodopayments/tanstack adapter provides route handlers for Dodo Payments' Checkout, Customer Portal, and Webhook functionalities, designed for the Tanstack App Router.
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/tanstack
Here's how you should structure your response:
Ask the user which functionalities they want to integrate.
"Which parts of the @dodopayments/tanstack 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 redirects users to the Dodo Payments checkout page.
File Creation: Create a new file at app/checkout/route.ts in your Tanstack project.
Code Snippet:
// src/routes/api/checkout.ts
import { Checkout } from "@dodopayments/tanstack";
import { createServerFileRoute } from "@tanstack/react-start/server";
export const ServerRoute = createServerFileRoute("/api/checkout")
.methods({
GET: async ({ request }) => {
return Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "static", // optional, defaults to 'static'
})(request)
},
POST: async ({ request }) => {
return Checkout({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
returnUrl: process.env.DODO_PAYMENTS_RETURN_URL,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
type: "session", // or "dynamic" for dynamic link
})(request)
}
})
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.
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://checkout.dodopayments.com/..."}
Dynamic Checkout (POST) - Returns JSON with checkout_url: Parameters are sent as a JSON body. Supports both one-time and recurring payments. Returns: {"checkout_url": "https://checkout.dodopayments.com/..."}. 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. 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 Route Handler is selected:
Purpose: This handler redirects authenticated users to their Dodo Payments customer portal.
File Creation: Create a new file at app/customer-portal/route.ts in your Tanstack project.
Code Snippet:
// src/routes/api/customer-portal.ts
import { CustomerPortal } from "@dodopayments/tanstack";
import { createServerFileRoute } from "@tanstack/react-start/server";
export const ServerRoute = createServerFileRoute('/api/customer-portal')
.methods({
GET: async ({ request }) => {
return CustomerPortal({
bearerToken: process.env.DODO_PAYMENTS_API_KEY,
environment: process.env.DODO_PAYMENTS_ENVIRONMENT,
})(request)
}
})
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 app/api/webhook/dodo-payments/route.ts in your Tanstack project.
Code Snippet:
// src/routes/api/webhook.ts
import { Webhooks } from "@dodopayments/tanstack";
import { createServerFileRoute } from "@tanstack/react-start/server";
export const ServerRoute = createServerFileRoute('/api/webhook')
.methods({
POST: async ({ request }) => {
return Webhooks({
webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
onPayload: async (payload) => {
console.log(payload)
}
})(request)
}
})
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 the appropriate event handler based on the payload type.
Supported Webhook Event Handlers:
onPayload?: (payload: WebhookPayload) => Promise
onPaymentSucceeded?: (payload: WebhookPayload) => Promise
onPaymentFailed?: (payload: WebhookPayload) => Promise
onPaymentProcessing?: (payload: WebhookPayload) => Promise
onPaymentCancelled?: (payload: WebhookPayload) => Promise
onRefundSucceeded?: (payload: WebhookPayload) => Promise
onRefundFailed?: (payload: WebhookPayload) => Promise
onDisputeOpened?: (payload: WebhookPayload) => Promise
onDisputeExpired?: (payload: WebhookPayload) => Promise
onDisputeAccepted?: (payload: WebhookPayload) => Promise
onDisputeCancelled?: (payload: WebhookPayload) => Promise
onDisputeChallenged?: (payload: WebhookPayload) => Promise
onDisputeWon?: (payload: WebhookPayload) => Promise
onDisputeLost?: (payload: WebhookPayload) => Promise
onSubscriptionActive?: (payload: WebhookPayload) => Promise
onSubscriptionOnHold?: (payload: WebhookPayload) => Promise
onSubscriptionRenewed?: (payload: WebhookPayload) => Promise
onSubscriptionPaused?: (payload: WebhookPayload) => Promise
onSubscriptionPlanChanged?: (payload: WebhookPayload) => Promise
onSubscriptionCancelled?: (payload: WebhookPayload) => Promise
onSubscriptionFailed?: (payload: WebhookPayload) => Promise
onSubscriptionExpired?: (payload: WebhookPayload) => Promise
onLicenseKeyCreated?: (payload: WebhookPayload) => Promise
Environment Variable Setup:
To ensure the adapter functions correctly, you will need to manually set up the following environment variables in your Tanstack 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).
RETURN_URL: (Optional) The URL to redirect to after a successful checkout (for Checkout handler).
DODO_PAYMENTS_WEBHOOK_SECRET: Your Dodo Payments Webhook Secret (required for Webhook handler).
Example .env file:
DODO_PAYMENTS_API_KEY=your-api-key
DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-secret
DODO_PAYMENTS_RETURN_URL=your-return-url
DODO_PAYMENTS_ENVIRONMENT="test_mode" or "live_mode"
Usage in your code:
bearerToken: process.env.DODO_PAYMENTS_API_KEY
webhookKey: process.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
```
# Build an AI Image Generator with Usage-Based Billing
Source: https://docs.dodopayments.com/developer-resources/usage-based-billing-build-ai-image-generator
Follow along as we build PixelGen AI - an AI image generation service with pay-per-use billing
In this tutorial, you'll build **PixelGen AI** - a sample AI image generation service that demonstrates usage-based billing. We'll create everything from scratch: the billing meter, product configuration, and sample application code that generates images and tracks usage in real-time.
This tutorial provides sample implementation code for a terminal-based application. You can modify this code for your specific framework (React, Vue, Angular, etc.) and customize the user input method according to your application's needs.
By the end of this tutorial, you'll have a working sample service that:
* Generates images using OpenAI's DALL-E API
* Tracks every image generation for billing
* Charges customers automatically based on usage
* Handles different quality tiers (standard vs HD)
## What We're Building
Let's start by understanding our PixelGen AI service:
* **Service**: AI image generation using OpenAI's DALL-E API
* **Pricing Model**: Pay-per-image (\$0.05 per image)
* **Free Tier**: 10 free images per customer per month
* **Quality Options**: Standard and HD images (same price for simplicity)
Before we start, make sure you have:
* A Dodo Payments account
* Access to OpenAI's API
* Basic familiarity with TypeScript/Node.js
## Step 1: Create Your Usage Meter
We'll start by creating a meter in your Dodo Payments dashboard that will track every image our service generates. Think of this as the "counter" that tracks billable events.
**What we're building**: A meter named "Image Generation Meter" that counts every time someone generates an image using our service.
1. Log into your Dodo Payments dashboard
2. Click on **Meters** in the left sidebar
3. Click the **Create Meter** button
You should see a form where we'll configure our image generation tracking.
Now we'll enter the specific details for our PixelGen AI service:
**Meter Name**: Copy and paste this exactly → `Image Generation Meter`
**Description**: Copy this → `Tracks each AI image generation request made by customers using our DALL-E powered service`
**Event Name**: This is crucial - copy exactly → `image.generated`
The event name `image.generated` must match exactly what we'll send from our application code later. Event names are case-sensitive!
Set up the aggregation (how the meter counts our events):
**Aggregation Type**: Select **Count** from the dropdown
**Measurement Unit**: Type → `images`
We're using "Count" because we want to bill per image generated, not by size or generation time. Each successful image = 1 billable unit.
We want to make sure we only count legitimate images (not test runs or failures):
1. **Enable Event Filtering**: Toggle this **ON**
2. **Filter Logic**: Select **OR** (this means "count if ANY of these conditions are true")
3. **Add the first condition**:
* Property Key: `quality`
* Comparator: `equals`
* Value: `standard`
4. **Click "Add Condition"** for the second one:
* Property Key: `quality`
* Comparator: `equals`
* Value: `hd`
This setup means we'll only count events where the quality is either "standard" OR "hd" - filtering out any test events or malformed requests.
1. Double-check all your settings match the values above
2. Click **Create Meter**
**Meter created!** Your "Image Generation Meter" is now ready to start counting image generations. Next, we'll connect it to a billing product.
## Step 2: Create Your Billing Product
Now we need to create a product that defines our pricing (\$0.05 per image with 10 free images). This connects our meter to actual billing.
**What we're building**: A product called "PixelGen AI - Image Generation" that charges \$0.05 per image after the first 10 free images each month.
1. In your Dodo Payments dashboard, click **Products** in the left sidebar
2. Click **Create Product**
3. Select **Usage-Based** as the product type
This tells Dodo Payments that billing will be based on meter usage, not a fixed subscription.
Fill in these exact values for our PixelGen AI service:
**Product Name**: Copy this → `PixelGen AI - Image Generation`
**Description**: Copy this → `AI-powered image generation service with pay-per-use billing`
**Product Image**: Upload a clear, relevant image.
These will appear on customer invoices, so make them clear and professional.
Before connecting your meter, make sure you have selected **Usage Based Billing** as the price type for your product.
Additionally, set the **Fixed Price** to `0` to ensure customers are only charged based on their usage, with no base fee.
Now, link the meter you just created:
1. Scroll down to the **Associated Meter** section
2. Click **Add Meters**
3. From the dropdown, select **"Image Generation Meter"** (the one you created earlier)
4. Confirm that it appears in your product configuration
Your meter is now successfully connected to this product.
Here's where we define our business model:
**Price Per Unit**: Enter → `0.05` (this is \$0.05 per image)
**Free Threshold**: Enter → `10` (customers get 10 free images per month)
**How billing works**: If a customer generates 25 images in a month, they'll be charged for 15 images (25 - 10 free) = 15 × $0.05 = $0.75
1. Review all your settings:
* Name: PixelGen AI - Image Generation
* Meter: Image Generation Meter
* Price: \$0.05 per image
* Free tier: 10 images
2. Click **Save Changes**
**Product created!** Your billing is now configured. Customers will automatically be charged based on their image generation usage.
## Step 3: Make a Test Purchase
Before we start ingesting usage events, we need to make a test purchase.
1. In your Dodo Payments dashboard, go to **Products**
2. Find your "PixelGen AI - Image Generation" product
3. Click the **Share** button next to your product
4. Copy the payment link that appears
The payment link will look something like: `https://test.checkout.dodopayments.com/buy/pdt_IgPWlRsfpbPd5jQKezzW1?quantity=1`
1. Open the payment link in a new browser tab
2. Enter test payment details and complete purchase.
After successful payment, you'll have a customer ID that we'll use in our application code.
1. Go back to your Dodo Payments dashboard
2. Navigate to **Customers** in the left sidebar
3. Find the customer you just created (with the test email)
4. Copy the customer ID - it will look like `cus_abc123def456`
Save this customer ID - we'll hardcode it in our sample application code to ensure events are properly tracked.
## Step 4: Build the Sample Application
Now we have our billing setup complete and a test customer created. Let's build the sample PixelGen AI application that generates images and automatically tracks usage for billing.
Create a new directory and initialize the project:
```bash theme={null}
mkdir pixelgen-ai
cd pixelgen-ai
npm init -y
```
Install the packages we need:
```bash theme={null}
npm install openai dotenv
npm install -D typescript @types/node ts-node
```
Create a file called `index.ts` and copy this complete application code:
Here's the complete PixelGen AI application with integrated billing:
```typescript index.ts expandable theme={null}
import 'dotenv/config';
import OpenAI from 'openai';
import * as readline from 'readline';
import { randomUUID } from 'crypto';
// Initialize OpenAI client
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
// Dodo Payments configuration
const DODO_PAYMENTS_CONFIG = {
apiKey: process.env.DODO_PAYMENTS_API_KEY,
baseUrl: 'https://test.dodopayments.com',
customerId: 'cus_FX5FAB43aShGyiHJGIqjB', // Replace with your actual customer ID from Step 3
};
// DALL-E 3 pricing (as of 2024-2025)
const PRICING = {
'standard': 0.040, // $0.040 per image (1024×1024)
'hd': 0.080, // $0.080 per image (1024×1024, HD quality)
};
interface ImageGenerationOptions {
prompt: string;
model?: 'dall-e-3' | 'dall-e-2';
quality?: 'standard' | 'hd';
size?: '1024x1024' | '1792x1024' | '1024x1792';
style?: 'vivid' | 'natural';
}
interface UsageEvent {
event_id: string;
customer_id: string;
event_name: string;
timestamp: string;
metadata: {
quality: string;
};
}
/**
* Send usage event to Dodo Payments for billing tracking
*/
async function sendUsageEvent(event: UsageEvent): Promise {
try {
console.log('Sending usage event to Dodo Payments...');
console.log(`URL: ${DODO_PAYMENTS_CONFIG.baseUrl}/events/ingest`);
console.log(`API Key present: ${!!DODO_PAYMENTS_CONFIG.apiKey}`);
console.log(`API Key length: ${DODO_PAYMENTS_CONFIG.apiKey?.length || 0}`);
console.log(`Customer ID: ${DODO_PAYMENTS_CONFIG.customerId}`);
const requestBody = {
events: [event]
};
console.log('Request body:', JSON.stringify(requestBody, null, 2));
const headers = {
'Authorization': `Bearer ${DODO_PAYMENTS_CONFIG.apiKey}`,
'Content-Type': 'application/json',
}
console.log('Headers:', headers);
const response = await fetch(`${DODO_PAYMENTS_CONFIG.baseUrl}/events/ingest`, {
method: 'POST',
headers: headers,
body: JSON.stringify(requestBody),
});
console.log(`Response status: ${response.status}`);
console.log(`Response headers:`, Object.fromEntries(response.headers.entries()));
if (!response.ok) {
const errorData = await response.text();
console.log(`Error response body: ${errorData}`);
throw new Error(`HTTP ${response.status}: ${errorData}`);
}
const result = await response.json();
console.log('Usage event sent successfully');
console.log(` • Event ID: ${event.event_id}`);
console.log(` • Customer: ${event.customer_id}`);
console.log(` • Quality: ${event.metadata.quality}`);
} catch (error) {
console.error('Failed to send usage event:', error);
// In production, you might want to queue failed events for retry
throw error;
}
}
async function generateImage(options: ImageGenerationOptions) {
const startTime = Date.now();
const eventId = randomUUID();
try {
console.log('Generating image...');
console.log(`Prompt: "${options.prompt}"`);
console.log(`Quality: ${options.quality || 'standard'}`);
console.log(`Size: ${options.size || '1024x1024'}`);
const response = await openai.images.generate({
model: options.model || 'dall-e-3',
prompt: options.prompt,
n: 1,
size: options.size || '1024x1024',
quality: options.quality || 'standard',
style: options.style || 'vivid',
});
const endTime = Date.now();
const duration = (endTime - startTime) / 1000;
const cost = PRICING[options.quality || 'standard'];
// Create usage event for Dodo Payments
const usageEvent: UsageEvent = {
event_id: eventId,
customer_id: DODO_PAYMENTS_CONFIG.customerId!,
event_name: 'image.generated',
timestamp: new Date().toISOString(),
metadata: {
quality: options.quality || 'standard',
}
};
// Send usage event to Dodo Payments for billing
await sendUsageEvent(usageEvent);
console.log('\nImage generated successfully!');
console.log(`Generation Stats:`);
console.log(` • Duration: ${duration.toFixed(2)} seconds`);
console.log(` • Quality: ${options.quality || 'standard'}`);
console.log(` • Cost: $${cost.toFixed(3)}`);
console.log(` • Image URL: ${response.data?.[0]?.url}`);
if (response.data?.[0]?.revised_prompt) {
console.log(` • Revised prompt: "${response.data[0].revised_prompt}"`);
}
return {
imageUrl: response.data?.[0].url,
revisedPrompt: response.data?.[0].revised_prompt,
cost: cost,
duration: duration,
eventId: eventId,
};
} catch (error) {
console.error('Error generating image:', error);
// Send failure event for monitoring (optional)
try {
const failureEvent: UsageEvent = {
event_id: eventId,
customer_id: DODO_PAYMENTS_CONFIG.customerId!,
event_name: 'image.generation.failed',
timestamp: new Date().toISOString(),
metadata: {
quality: options.quality || 'standard',
}
};
// Note: You might want to create a separate meter for failed attempts
// await sendUsageEvent(failureEvent);
} catch (eventError) {
console.error('Failed to send failure event:', eventError);
}
throw error;
}
}
async function getUserInput(): Promise {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question('Enter your image prompt: ', (answer) => {
rl.close();
resolve(answer);
});
});
}
async function main() {
console.log('PixelGen AI - Image Generator with Usage Billing\n');
// Validate environment variables
const requiredEnvVars = [
'OPENAI_API_KEY',
'DODO_PAYMENTS_API_KEY'
];
for (const envVar of requiredEnvVars) {
if (!process.env[envVar]) {
console.error(`Error: ${envVar} environment variable is not set.`);
console.log('Please set all required environment variables:');
console.log('export OPENAI_API_KEY="your-openai-key"');
console.log('export DODO_PAYMENTS_API_KEY="your-dodo-api-key"');
console.log('Note: Customer ID is hardcoded in the application');
process.exit(1);
}
}
try {
const prompt = await getUserInput();
if (!prompt.trim()) {
console.log('No prompt provided. Exiting...');
return;
}
const result = await generateImage({
prompt: prompt.trim(),
quality: 'standard', // Change to 'hd' for higher quality (costs more)
size: '1024x1024',
style: 'vivid'
});
console.log('\nProcess completed successfully!');
console.log(`Billing Information:`);
console.log(` • Total cost: $${result.cost.toFixed(3)}`);
console.log(` • Event ID: ${result.eventId}`);
console.log(` • Billing will be processed automatically via Dodo Payments`);
} catch (error) {
console.error('Application error:', error);
process.exit(1);
}
}
// Run the application
if (require.main === module) {
main().catch(console.error);
}
```
```bash .env theme={null}
# Create .env file with your API keys
OPENAI_API_KEY=your_openai_api_key_here
DODO_PAYMENTS_API_KEY=your_DODO_PAYMENTS_payments_api_key_here
# Note: Customer ID is hardcoded in the application code
```
```typescript package.json theme={null}
{
"name": "pixelgen-ai",
"version": "1.0.0",
"description": "AI Image Generation with Usage-Based Billing",
"main": "index.js",
"scripts": {
"start": "ts-node index.ts",
"build": "tsc",
"dev": "ts-node --watch index.ts"
},
"dependencies": {
"openai": "^4.0.0",
"dotenv": "^16.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0",
"ts-node": "^10.0.0"
}
}
```
## Step 5: Test Your Sample Application
Time to test our sample PixelGen AI service and see the billing in action! Let's make sure everything works end-to-end.
**What we're testing**: We'll generate some images, verify the events reach Dodo Payments, and confirm billing calculations are correct.
First, make sure you have everything configured:
1. Create a `.env` file in your `pixelgen-ai` directory
2. Add your actual API keys:
```bash theme={null}
OPENAI_API_KEY=sk-your-actual-openai-key
DODO_PAYMENTS_API_KEY=your-actual-dodo-api-key
# Customer ID is hardcoded in the application
```
3. Install dependencies and run the app:
```bash theme={null}
npm install
npm start
```
Make sure to use real API keys and update the hardcoded customer ID in the code with your actual customer ID from Step 3!
When the app starts, you'll see:
```
PixelGen AI - Image Generator with Usage Billing
Enter your image prompt:
```
Try this prompt: **"A cute robot painting a landscape"**
You should see output like this:
```
Generating image...
Prompt: "A cute robot painting a landscape"
Quality: standard
Size: 1024x1024
Sending usage event to Dodo Payments...
Usage event sent successfully
• Event ID: 550e8400-e29b-41d4-a716-446655440000
• Customer: cus_atXa1lklCRRzMicTqfiw2
• Quality: standard
Image generated successfully!
Generation Stats:
• Duration: 8.45 seconds
• Quality: standard
• Cost: $0.040
• Image URL: https://oaidalleapi...
```
If you see "Usage event sent successfully", your billing integration is working!
Let's generate 2-3 more images to test multiple events. Try these prompts:
1. **"A sunset over mountains with purple clouds"**
2. **"A steampunk coffee machine in a Victorian kitchen"**
3. **"A friendly dragon reading a book in a library"**
Each time, watch for the "Usage event sent successfully" message.
Now let's verify the events are being received:
1. Open your Dodo Payments dashboard
2. Go to *Usage Billing* → \***Meters** → **Image Generation Meter**
3. Click on the **Events** tab
4. You should see your image generation events listed
**What to look for**:
* Event names: `image.generated`
* Customer ID: Your test customer ID
You should see one event for each image you generated!
Let's check if the usage counting is working:
1. In your meter, go to the **Customers** tab
2. Find your test customer
3. Check the "Consumed Units" column
Let's exceed the free tier to see billing in action:
1. Generate 8 more images (to reach 12 total)
2. Check your meter dashboard again
3. You should now see:
* Consumed units: 12
* Billable units: 2 (12 - 10 free)
* Billing amount: \$0.10
**Success!** Your usage-based billing is working perfectly. Customers will be automatically charged based on their actual image generation usage.
## Troubleshooting
Common issues and their solutions:
**Possible causes:**
* Event name doesn't match meter configuration exactly
* Customer ID doesn't exist in your account
* API key is invalid or expired
* Network connectivity issues
**Solutions:**
1. Verify event name matches meter configuration exactly (case-sensitive)
2. Check that customer ID exists in Dodo Payments
3. Test API key with a simple API call
4. Check network connectivity and firewall settings
## Congratulations! You Built PixelGen AI
You've successfully created a snippet for AI image generation with usage-based billing! Here's what you accomplished:
Created "Image Generation Meter" that tracks every image generation event
Configured pricing at \$0.05 per image with 10 free images per month
Built a working TypeScript app that generates images using OpenAI's DALL-E
Integrated real-time event tracking that automatically bills customers
# Usage-Based Billing Integration guide
Source: https://docs.dodopayments.com/developer-resources/usage-based-billing-guide
Learn how to set up meters and send usage events to enable accurate usage-based billing with Dodo Payments.
Access the complete API documentation for ingesting usage events and test event ingestion requests and responses interactively.
Explore the full API documentation for creating meters and interactively test meter creation requests and responses.
## Creating a Meter
Meters define how your usage events are aggregated and measured for billing purposes.
Before creating a meter, plan your usage tracking strategy:
* Identify what usage events you want to track
* Determine how events should be aggregated (count, sum, etc.)
* Define any filtering requirements for specific use cases
### Step-by-Step Meter Creation
Follow this comprehensive guide to set up your usage meter:
Set up the fundamental details for your meter.
Choose a clear, descriptive name that identifies what this meter tracks.
Examples: "Tokens", "API Calls", "Storage Usage", "Compute Hours"
Provide a detailed explanation of what this meter measures.
Example: "Counts each POST /v1/orders request made by the customer"
Specify the event identifier that will trigger this meter.
Examples: "token", "api.call", "storage.usage", "compute.session"
The event name must match exactly what you send in your usage events. Event names are case-sensitive.
Define how the meter calculates usage from your events.
Select how events should be aggregated:
Simply counts the number of events received.
Use case: API calls, page views, file uploads
Calculation: Total number of events
Adds up values from a specific property in your events.
Use case: Data transfer, storage consumption, processing time
Calculation: Sum of all property values
Records the highest value of a specific property during the billing period.
Use case: Peak concurrent users, maximum storage used, highest bandwidth
Calculation: Maximum property value observed
Uses the most recent value of a specific property.
Use case: Current plan tier, latest configuration setting
Calculation: Last recorded property value
The property name from event metadata to aggregate over.
This field is required when using Sum, Max, or Last aggregation types.
Define the unit label for display purposes in reports and billing.
Examples: "calls", "GB", "hours", "tokens"
Set up criteria to control which events are included in the meter.
Event filtering allows you to create sophisticated rules that determine which events contribute to your usage calculations. This is useful for excluding test events, filtering by user tiers, or focusing on specific actions.
**Enable Event Filtering**
Toggle **Enable Event Filtering** to activate conditional event processing.
**Choose Filter Logic**
Select how multiple conditions are evaluated:
All conditions must be true for an event to be counted. Use this when you need events to meet multiple strict criteria simultaneously.
**Example:** Count API calls where `user_tier = "premium"` AND `endpoint = "/api/v2/users"`
At least one condition must be true for an event to be counted. Use this when you want to include events that meet any of several criteria.
**Example:** Count events where `method = "POST"` OR `method = "PUT"` OR `method = "DELETE"`
**Setting Up Filter Conditions**
Click **Add condition** to create a new filter rule.
Specify the property name from your event metadata.
Choose from available operators:
* `equals` - Exact match
* `not equals` - Exclusion filter
* `greater than` - Numeric comparison
* `greater than or equals` - Numeric comparison (inclusive)
* `less than` - Numeric comparison
* `less than or equals` - Numeric comparison (inclusive)
* `contains` - String contains substring
* `does not contain` - String exclusion filter
Set the target value for comparison.
Use **Add Group** to create additional condition groups for complex logic.
Filtered properties must be included in your event metadata for the conditions to work properly. Events missing required properties will be excluded from counting.
Review your meter configuration and click on **Create Meter**.
Your meter is now ready to receive and aggregate usage events.
## Linking Meter in a Product
Once you have created your meter, you need to link it to a product to enable usage-based billing. This process connects your meter's usage data to pricing rules for customer billing.
Linking meters to products establishes the connection between usage tracking and billing:
* Products define pricing rules and billing behavior
* Meters provide usage data for billing calculations
* Multiple meters can be linked to a single product for complex billing scenarios
### Product Configuration Process
Transform your usage data into billable charges by properly configuring your product settings:
Navigate to your product creation or editing page and select **Usage-Based** as the product type.
Click on **Associated Meter** to open the meter selection panel from the side.
This panel allows you to configure which meters will track usage for this product.
In the meter selection panel:
1. Click **Add Meters** to view available meters
2. Select the meter you created from the dropdown list
3. The selected meter will appear in your product configuration
Set the pricing for each unit of usage tracked by your meter.
Define how much to charge for each unit measured by your meter.
**Example**: Setting `$0.50` per unit means:
* 1,000 units consumed = 1,000 × \$0.50 = 500.00 charged
* 500 units consumed = 500 × \$0.50 = 250.00 charged
* 100 units consumed = 100 × \$0.50 = 50.00 charged
Configure a free usage allowance before billing begins.
Number of units customers can consume at no charge before paid usage calculation starts.
**How it works**:
* Free threshold: 100 units
* Price per unit: \$0.50
* Customer usage: 250 units
* **Calculation**: (250 - 100) × $0.50 = **$75.00\*\* charged
Free thresholds are ideal for freemium models, trial periods, or providing customers with a base allowance included in their plan.
The free threshold applies to each billing cycle, giving customers fresh allowances monthly or according to your billing schedule.
Review your meter and pricing configuration, then click **Save Changes** to finalize the setup.
Your product is now configured for usage-based billing and will automatically charge customers based on their measured consumption.
**What happens next**:
* Usage events sent to your meter will be tracked and aggregated
* Billing calculations will apply your pricing rules automatically
* Customers will be charged based on actual consumption during each billing cycle
Remember that you can add up to 10 meters per product, enabling sophisticated usage tracking across multiple dimensions like API calls, storage, compute time, and custom metrics.
## Sending Usage Events
Once your meter is configured, you can start sending usage events from your application to track customer usage.
### Event Structure
Each usage event must include these required fields:
Unique identifier for this specific event. Must be unique across all events.
The Dodo Payments customer ID this usage should be attributed to.
The event name that matches your meter configuration. Event names trigger the appropriate meter.
ISO 8601 timestamp when the event occurred. Defaults to current time if not provided.
Additional properties for filtering and aggregation. Include any values referenced in your meter's "Over Property" or filtering conditions.
### Usage Events API Examples
Send usage events to your configured meters using the Events API:
```javascript Node.js theme={null}
const response = await fetch('https://test.dodopayments.com/events/ingest', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.DODO_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
events: [
{
event_id: "api_call_1234",
customer_id: "cus_atXa1lklCRRzMicTqfiw2",
event_name: "api.call",
timestamp: new Date().toISOString(),
metadata: {
endpoint: "/v1/orders",
method: "POST",
response_size: 1024
}
}
]
})
});
```
```python Python theme={null}
import requests
from datetime import datetime
response = requests.post(
'https://test.dodopayments.com/events/ingest',
headers={
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
},
json={
'events': [
{
'event_id': 'api_call_1234',
'customer_id': 'cus_atXa1lklCRRzMicTqfiw2',
'event_name': 'api.call',
'timestamp': datetime.now().isoformat(),
'metadata': {
'endpoint': '/v1/orders',
'method': 'POST',
'response_size': 1024
}
}
]
}
)
```
```curl cURL theme={null}
curl -X POST 'https://test.dodopayments.com/events/ingest' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"events": [
{
"event_id": "api_call_1234",
"customer_id": "cus_atXa1lklCRRzMicTqfiw2",
"event_name": "api.call",
"timestamp": "2024-01-15T10:30:00Z",
"metadata": {
"endpoint": "/v1/orders",
"method": "POST",
"response_size": 1024
}
}
]
}'
```
## Usage-Based Billing Analytics
Monitor and analyze your usage-based billing data with comprehensive analytics dashboard. Track customer consumption patterns, meter performance, and billing trends to optimize your pricing strategy and understand usage behaviors.
### Overview Analytics
The Overview tab provides a comprehensive view of your usage-based billing performance:
#### Activity Metrics
Track key usage statistics across different time periods:
Shows usage activity for the current billing period, helping you understand monthly consumption patterns.
Displays cumulative usage statistics since you started tracking, providing long-term growth insights.
Use the time period selector to compare usage across different months and identify seasonal trends or growth patterns.
#### Meter Quantities Chart
The meter quantities chart visualizes usage trends over time with the following features:
* **Time-series visualization**: Track usage patterns across days, weeks, or months
* **Multiple meter support**: View data from different meters simultaneously
* **Trend analysis**: Identify usage spikes, patterns, and growth trajectories
The chart automatically scales based on your usage volume and selected time range, providing clear visibility into both small fluctuations and major usage changes.
### Events Analytics
The Events tab provides granular visibility into individual usage events:
#### Event Information Display
The events table provides a clear view of individual usage events with the following columns:
* **Event Name**: The specific action or trigger that generated the usage event
* **Event ID**: Unique identifier for each event instance
* **Customer ID**: The customer associated with the event
* **Timestamp**: When the event occurred
This view allows you to track and monitor individual usage events across your customer base, providing transparency into billing calculations and usage patterns.
### Customer Analytics
The Customers tab provides a detailed table view of customer usage data with the following information:
#### Available Data Columns
Email address of the customer for identification.
Unique identifier for the customer's subscription.
Number of free units included in the customer's plan before charges apply.
The cost per unit for usage beyond the free threshold.
Timestamp of the customer's most recent usage event.
Total amount charged to the customer for usage-based billing.
Total number of units the customer has consumed.
Number of units that exceed the free threshold and are being charged.
#### Table Features
* **Column Filtering**: Use the "Edit Columns" feature to show/hide specific data columns
* **Real-time Updates**: Usage data reflects the most current consumption metrics
## Aggregation Examples
Here are practical examples of how different aggregation types work:
### Understanding Aggregation Types
Different aggregation types serve different billing scenarios. Choose the right type based on how you want to measure and charge for usage.
### Practical Implementation Examples
These examples demonstrate real-world applications of each aggregation type with sample events and expected results.
Scenario: Track the total number of API requests
Meter Configuration:
* Event Name: `api.call`
* Aggregation Type: Count
* Measurement Unit: `calls`
**Sample Events**:
```json theme={null}
{
"events": [
{"event_id": "call_1", "customer_id": "cus_123", "event_name": "api.call"},
{"event_id": "call_2", "customer_id": "cus_123", "event_name": "api.call"},
{"event_id": "call_3", "customer_id": "cus_123", "event_name": "api.call"}
]
}
```
Result: 3 calls billed to customer
Scenario: Bill based on total bytes transferred
Meter Configuration:
* Event Name: `data.transfer`
* Aggregation Type: Sum
* Over Property: `bytes`
* Measurement Unit: `GB`
**Sample Events**:
```json theme={null}
{
"events": [
{
"event_id": "transfer_1",
"customer_id": "cus_123",
"event_name": "data.transfer",
"metadata": {"bytes": 1073741824}
},
{
"event_id": "transfer_2",
"customer_id": "cus_123",
"event_name": "data.transfer",
"metadata": {"bytes": 536870912}
}
]
}
```
Result: 1.5 GB total transfer billed to customer
Scenario: Bill based on highest concurrent user count
Meter Configuration:
* Event Name: `concurrent.users`
* Aggregation Type: Max
* Over Property: `count`
* Measurement Unit: `users`
**Sample Events**:
```json theme={null}
{
"events": [
{
"event_id": "peak_1",
"customer_id": "cus_123",
"event_name": "concurrent.users",
"metadata": {"count": 15}
},
{
"event_id": "peak_2",
"customer_id": "cus_123",
"event_name": "concurrent.users",
"metadata": {"count": 23}
},
{
"event_id": "peak_3",
"customer_id": "cus_123",
"event_name": "concurrent.users",
"metadata": {"count": 18}
}
]
}
```
Result: 23 peak concurrent users billed to customer
### Event Filtering Examples
Only count API calls to specific endpoints:
Filter Configuration:
* Property: `endpoint`
* Comparator: `equals`
* Value: `/v1/orders`
Sample Event:
```json theme={null}
{
"event_id": "call_1",
"customer_id": "cus_123",
"event_name": "api.call",
"metadata": {
"endpoint": "/v1/orders",
"method": "POST"
}
}
```
Result: Events matching the filter criteria would be counted. Events with different endpoints would be ignored.
Only count large file uploads:
Filter Configuration:
* Property: `file_size`
* Comparator: `greater_than`
* Value: `1048576` (1MB in bytes)
Sample Event:
```json theme={null}
{
"event_id": "upload_1",
"customer_id": "cus_123",
"event_name": "file.upload",
"metadata": {
"file_size": 5242880,
"file_type": "image"
}
}
```
Result: Files larger than 1MB would be counted. Smaller files would be ignored.
Count premium API calls during business hours:
Filter Configuration (using AND logic):
* Property: `plan_type`, Comparator: `equals`, Value: `premium`
* Property: `hour`, Comparator: `greater_than_or_equal`, Value: `9`
* Property: `hour`, Comparator: `less_than`, Value: `17`
Sample Event:
```json theme={null}
{
"event_id": "call_1",
"customer_id": "cus_123",
"event_name": "api.call",
"metadata": {
"plan_type": "premium",
"hour": 14,
"endpoint": "/v1/analytics"
}
}
```
Result: Premium API calls during business hours (9 AM - 5 PM) would be counted.
## Troubleshooting
Resolve common issues with usage-based billing implementation and ensure accurate tracking and billing.
### Common Issues
Most usage-based billing problems fall into these categories:
* Event delivery and processing issues
* Meter configuration problems
* Data type and formatting errors
* Customer ID and authentication issues
### Debugging Steps
When troubleshooting usage-based billing:
1. Verify event delivery in the Events analytics tab
2. Check meter configuration matches your event structure
3. Validate customer IDs and API authentication
4. Review filtering conditions and aggregation settings
### Solutions and Fixes
Common causes:
* Event name doesn't exactly match meter configuration
* Event filtering conditions are excluding your events
* Customer ID doesn't exist in your Dodo Payments account
* Event timestamp is outside the current billing period
Solutions:
* Verify event name spelling and case sensitivity
* Review and test your filtering conditions
* Confirm customer ID is valid and active
* Check event timestamps are recent and properly formatted
Common causes:
* Over Property name doesn't match event metadata keys
* Metadata values are wrong data type (string vs number)
* Missing required metadata properties
Solutions:
* Ensure metadata keys exactly match your Over Property setting
* Convert string numbers to actual numbers in your events
* Include all required properties in every event
Common causes:
* Filter property names don't match event metadata
* Wrong comparator for data type (string vs number)
* Case sensitivity in string comparisons
Solutions:
* Double-check property names match exactly
* Use appropriate comparators for your data types
* Consider case sensitivity when filtering strings
# Webhooks
Source: https://docs.dodopayments.com/developer-resources/webhooks
Receive real-time notifications when events occur in Dodo Payments. Automate workflows and keep your systems synchronized with instant event delivery.
Webhooks provide real-time notifications when specific events occur in your Dodo Payments account. Use webhooks to automate workflows, update your database, send notifications, and keep your systems synchronized.
Our webhook implementation follows the [Standard Webhooks](https://standardwebhooks.com/) specification, ensuring compatibility with industry best practices and existing webhook libraries.
## Key Features
Receive instant notifications when events occur
HMAC SHA256 signature verification included
Built-in retry logic with exponential backoff
Subscribe only to events you need
## Getting Started
Navigate to the DodoPayments Dashboard and go to `Settings > Webhooks`.
Click on `Add Webhook` to create a new webhook endpoint.
Enter the URL where you want to receive webhook events.
Choose the specific events your webhook endpoint should listen for by selecting them from the event list.
Only selected events will trigger webhooks to your endpoint, helping you avoid unnecessary traffic and processing.
Obtain your webhook `Secret Key` from the settings page. You'll use this to verify the authenticity of received webhooks.
Keep your webhook secret key secure and never expose it in client-side code or public repositories.
If needed, you can rotate your webhook secret for enhanced security. Click the **Rotate Secret** button in your webhook settings.
Rotating the secret will **expire it** and **replace it** with a new one. The old secret will only be valid for the next 24 hours. Afterward, trying to verify with the old secret will fail.
Use secret rotation periodically or immediately if you suspect your current secret has been compromised.
## Configuring Subscribed Events
You can configure which specific events they want to receive for each webhook endpoint.
### Accessing Event Configuration
Go to your Dodo Payments Dashboard and navigate to `Settings > Webhooks`.
Click on the webhook endpoint you want to configure.
In the webhook details page, you'll see a "Subscribed events" section. Click the **Edit** button to modify your event subscriptions.
### Managing Event Subscriptions
The interface displays all available webhook events organized in a hierarchical structure. Events are grouped by category (e.g., `dispute`, `payment`, `subscription`).
Use the search bar to quickly find specific events by typing event names or keywords.
Check the boxes next to the events you want to receive. You can:
* Select individual sub-events (e.g., `dispute.accepted`, `dispute.challenged`)
* Select parent events to receive all related sub-events
* Mix and match specific events based on your needs
Hover over the information icon (ⓘ) next to each event to see a description of when that event is triggered.
Click **Save** to apply your changes, or **Cancel** to discard modifications.
If you deselect all events, your webhook endpoint will not receive any notifications. Make sure to select at least the events your application needs to function properly.
## Webhook Delivery
### Timeouts
Webhooks have a **15-second timeout window** for both connection and read operations. Ensure your endpoint responds quickly to avoid timeouts.
Process webhooks asynchronously by acknowledging receipt immediately with a `200` status code, then handling the actual processing in the background.
### Automatic Retries
If a webhook delivery fails, Dodo Payments automatically retries with exponential backoff to prevent overwhelming your system.
| Attempt | Delay | Description |
| ------- | ----------- | ------------------------------------------------------------ |
| 1 | Immediately | First retry happens right away |
| 2 | 5 seconds | Second attempt after short delay |
| 3 | 5 minutes | Third attempt with increased backoff |
| 4 | 30 minutes | Fourth attempt continuing backoff |
| 5 | 2 hours | Fifth attempt with extended delay |
| 6 | 5 hours | Sixth attempt with longer delay |
| 7 | 10 hours | Seventh attempt with maximum delay |
| 8 | 10 hours | **Final attempt** - webhook marked as failed if unsuccessful |
**Maximum of 8 retry attempts** per webhook event. For example, if a webhook fails three times before succeeding, the total delivery time is approximately 35 minutes and 5 seconds from the first attempt.
Use the Dodo Payments dashboard to manually retry individual messages or bulk recover all failed messages at any time.
### Idempotency
Each webhook event includes a unique `webhook-id` header. Use this identifier to implement idempotency and prevent duplicate processing.
```javascript theme={null}
// Example: Storing webhook IDs to prevent duplicate processing
const processedWebhooks = new Set();
app.post('/webhook', (req, res) => {
const webhookId = req.headers['webhook-id'];
if (processedWebhooks.has(webhookId)) {
return res.status(200).json({ received: true });
}
processedWebhooks.add(webhookId);
// Process the webhook...
});
```
Always implement idempotency checks. Due to retries, you may receive the same event multiple times.
### Event Ordering
Webhook events may arrive out of order due to retries or network conditions. Design your system to handle events in any sequence.
You will receive the **latest payload at the time of delivery**, regardless of when the webhook event was originally emitted.
## Securing Webhooks
To ensure the security of your webhooks, always validate the payloads and use HTTPS.
### Verifying Signatures
Each webhook request includes a `webhook-signature` header, an HMAC SHA256 signature of the webhook payload and timestamp, signed with your secret key.
#### SDK verification (recommended)
All official SDKs include built‑in helpers to securely validate and parse incoming webhooks. Two methods are available:
* `unwrap()`: Verifies signatures using your webhook secret key
* `unsafe_unwrap()`: Parses payloads without verification
Provide your webhook secret via `DODO_PAYMENTS_WEBHOOK_KEY` when initializing the Dodo Payments client.
#### Manual verification (alternative)
If you are not using an SDK, you can verify signatures yourself following the Standard Webhooks spec:
1. Build the signed message by concatenating `webhook-id`, `webhook-timestamp`, and the exact raw stringified `payload`, separated by periods (`.`).
2. Compute the HMAC SHA256 of that string using your webhook secret key from the Dashboard.
3. Compare the computed signature to the `webhook-signature` header. If they match, the webhook is authentic.
We follow the Standard Webhooks specification. You can use their libraries to verify signatures: [https://github.com/standard-webhooks/standard-webhooks/tree/main/libraries](https://github.com/standard-webhooks/standard-webhooks/tree/main/libraries). For event payload formats, see the [Webhook Payload](/developer-resources/webhooks/intents/payment).
### Responding to Webhooks
* Your webhook handler must return a `2xx status code` to acknowledge receipt of the event.
* Any other response will be treated as a failure, and the webhook will be retried.
### Best Practices
Always use HTTPS URLs for webhook endpoints. HTTP endpoints are vulnerable to man-in-the-middle attacks and expose your webhook data.
Return a `200` status code immediately upon receiving the webhook. Process the event asynchronously to avoid timeouts.
```javascript theme={null}
app.post('/webhook', async (req, res) => {
// Acknowledge receipt immediately
res.status(200).json({ received: true });
// Process asynchronously
processWebhookAsync(req.body).catch(console.error);
});
```
Implement idempotency using the `webhook-id` header to safely process the same event multiple times without side effects.
Store your webhook secret securely using environment variables or a secrets manager. Never commit secrets to version control.
## Webhook Payload Structure
Understanding the webhook payload structure helps you parse and process events correctly.
### Request Format
```http theme={null}
POST /your-webhook-url
Content-Type: application/json
```
### Headers
Unique identifier for this webhook event. Use this for idempotency checks.
HMAC SHA256 signature for verifying the webhook authenticity.
Unix timestamp (in seconds) when the webhook was sent.
### Request Body
Your Dodo Payments business identifier.
Event type that triggered this webhook (e.g., `payment.succeeded`, `subscription.created`).
ISO 8601 formatted timestamp of when the event occurred.
Event-specific payload containing detailed information about the event.
Type of resource: `Payment`, `Subscription`, `Refund`, `Dispute`, or `LicenseKey`.
Additional fields vary by event type. See the event-specific documentation for complete schemas.
### Example Payload
```json theme={null}
{
"business_id": "string",
"type": "payment.succeeded | payment.failed |...",
"timestamp": "2024-01-01T12:00:00Z",
"data": {
"payload_type": "Payment | Subscription | Refund | Dispute | LicenseKey",
// ... event-specific fields (see below)
}
}
```
Browse all available webhook event types
View detailed payload schemas for each event
## Testing Webhooks
You can test your webhook integration directly from the Dodo Payments dashboard to ensure your endpoint is working correctly before going live.
### Accessing the Testing Interface
Go to your Dodo Payments Dashboard and navigate to `Settings > Webhooks`.
Click on your webhook endpoint to access its details page.
Click on the **Testing** tab to access the webhook testing interface.
### Testing Your Webhook
The testing interface provides a comprehensive way to test your webhook endpoint:
Use the dropdown menu to select the specific event type you want to test (e.g., `payment.succeeded`, `payment.failed`, etc.).
The dropdown contains all available webhook event types that your endpoint can receive.
The interface displays both the **Schema** (data structure) and **Example** (sample payload) for the selected event type.
Click the **Send Example** button to send a test webhook to your endpoint.
**Important**: Failed messages sent through the testing interface will not be retried. This is for testing purposes only.
### Verifying Your Test
Monitor your webhook endpoint logs to confirm the test event was received.
Ensure your signature verification is working correctly with the test payload.
Confirm your endpoint returns a `2xx` status code to acknowledge receipt.
### Implementation Example
Here's a complete Express.js implementation showing webhook verification and handling:
```typescript Express.js Webhook Handler theme={null}
import { Webhook } from "standardwebhooks";
import express from "express";
const app = express();
app.use(express.json());
const webhook = new Webhook(process.env.DODO_WEBHOOK_SECRET);
app.post('/webhook/dodo-payments', async (req, res) => {
try {
// Extract webhook headers
const webhookHeaders = {
"webhook-id": req.headers["webhook-id"] as string,
"webhook-signature": req.headers["webhook-signature"] as string,
"webhook-timestamp": req.headers["webhook-timestamp"] as string,
};
// Verify the webhook signature
const payload = JSON.stringify(req.body);
await webhook.verify(payload, webhookHeaders);
// Acknowledge receipt immediately
res.status(200).json({ received: true });
// Process webhook asynchronously
processWebhookAsync(req.body).catch(console.error);
} catch (error) {
console.error('Webhook verification failed:', error);
res.status(400).json({ error: 'Invalid signature' });
}
});
async function processWebhookAsync(data: any) {
// Handle the webhook event based on type
switch (data.type) {
case 'payment.succeeded':
await handlePaymentSucceeded(data);
break;
case 'subscription.created':
await handleSubscriptionCreated(data);
break;
// Add more event handlers...
}
}
```
```javascript Testing Webhooks Locally theme={null}
const { Webhook } = require("standardwebhooks");
const axios = require("axios");
const crypto = require("crypto");
// Generate test webhook
const timestamp = new Date();
const webhookId = crypto.randomUUID();
const payload = {
business_id: "biz_test123",
type: "payment.succeeded",
timestamp: timestamp.toISOString(),
data: {
payload_type: "Payment",
payment_id: "pay_test456",
amount: 2999
}
};
// Sign the payload
const webhookSecret = process.env.DODO_WEBHOOK_SECRET;
const webhookInstance = new Webhook(webhookSecret);
const payloadString = JSON.stringify(payload);
const signature = webhookInstance.sign(webhookId, timestamp, payloadString);
// Send test webhook
const response = await axios.post('http://localhost:3000/webhook/dodo-payments', payload, {
headers: {
"webhook-id": webhookId,
"webhook-timestamp": Math.floor(timestamp.getTime() / 1000),
"webhook-signature": signature,
"Content-Type": "application/json"
}
});
console.log("Test webhook sent successfully:", response.data);
```
Test your webhook handler thoroughly using the dashboard testing interface before processing production events. This helps identify and fix issues early.
## Advanced Settings
The Advanced Settings tab provides additional configuration options for fine-tuning your webhook endpoint behavior.
### Rate Limiting (Throttling)
Control the rate at which webhook events are delivered to your endpoint to prevent overwhelming your system.
In the **Advanced** tab, locate the "Rate Limit (throttling)" section.
Click the **Edit** button to modify the rate limit settings.
By default, webhooks have "No rate limit" applied, meaning events are delivered as soon as they occur.
Configure your desired rate limit to control webhook delivery frequency and prevent system overload.
Use rate limiting when your webhook handler needs time to process events or when you want to batch multiple events together.
### Custom Headers
Add custom HTTP headers to all webhook requests sent to your endpoint. This is useful for authentication, routing, or adding metadata to your webhook requests.
In the "Custom Headers" section, enter a **Key** and **Value** for your custom header.
Click the **+** button to add additional custom headers as needed.
Your custom headers will be included in all webhook requests to this endpoint.
### Transformations
Transformations allow you to modify a webhook's payload and redirect it to a different URL. This powerful feature enables you to:
* Modify the payload structure before processing
* Route webhooks to different endpoints based on content
* Add or remove fields from the payload
* Transform data formats
Toggle the **Enabled** switch to activate the transformation feature.
Click **Edit transformation** to define your transformation rules.
You can use JavaScript to transform the webhook payload and specify a different target URL.
Use the testing interface to verify your transformation works correctly before going live.
Transformations can significantly impact webhook delivery performance. Test thoroughly and keep transformation logic simple and efficient.
Transformations are particularly useful for:
* Converting between different data formats
* Filtering events based on specific criteria
* Adding computed fields to the payload
* Routing events to different microservices
## Monitoring Webhook Logs
The Logs tab provides comprehensive visibility into your webhook delivery status, allowing you to monitor, debug, and manage webhook events effectively.
## Activity Monitoring
The Activity tab provides real-time insights into your webhook delivery performance with visual analytics.
## Deploy to Cloud Platforms
Ready to deploy your webhook handler to production? We provide platform-specific guides to help you deploy webhooks to popular cloud providers with best practices for each platform.
Deploy webhooks to Vercel with serverless functions
Run webhooks on Cloudflare's edge network
Integrate webhooks with Supabase
Deploy webhooks as Netlify serverless functions
Each platform guide includes environment setup, signature verification, and deployment steps specific to that provider.
# Cloudflare Workers
Source: https://docs.dodopayments.com/developer-resources/webhooks/examples/cloudflare-example
Deploy DodoPayments webhooks to Cloudflare Workers
Complete source code and setup guide
## Quick Setup
### 1. Prerequisites
* [Cloudflare account](https://dash.cloudflare.com/sign-up)
* [Neon database](https://neon.tech) account
* DodoPayments API key and webhook key from [dashboard](https://app.dodopayments.com/)
### 2. Install & Deploy
```bash theme={null}
npm install -g wrangler
wrangler login
git clone https://github.com/dodopayments/cloud-functions.git
cd cloud-functions/cloudflare
npm install
```
### 3. Database Setup
1. Sign up for [Neon](https://neon.tech)
2. Create a new project
3. Open the SQL Editor
4. Copy and paste the contents of [`schema.sql`](#database-schema)
5. Run the query
6. Get your connection string from Neon → Connection Details
### 4. Configure Secrets
```bash theme={null}
# Set your Neon database URL
wrangler secret put DATABASE_URL
# Set your API key
wrangler secret put DODO_PAYMENTS_API_KEY
# Set your webhook signing key
wrangler secret put DODO_PAYMENTS_WEBHOOK_KEY
```
### 5. Update wrangler.toml
Edit `wrangler.toml` and set your worker name:
```toml theme={null}
name = "my-dodo-webhook"
```
### 6. Deploy
```bash theme={null}
npm run deploy
```
**Webhook URL:** `https://[worker-name].[subdomain].workers.dev`
## What It Does
Processes subscription events and stores them in PostgreSQL:
* **subscription.active** - Creates/updates customer and subscription records
* **subscription.cancelled** - Marks subscription as cancelled
* **subscription.renewed** - Updates next billing date
## Key Features
✅ **Signature verification** - Using the dodopayments library\
✅ **Idempotency** - Prevents duplicate processing with webhook IDs\
✅ **Event logging** - Complete audit trail in `webhook_events` table\
✅ **Error handling** - Logged and retryable
## Configuration Files
```json package.json theme={null}
{
"name": "dodo-webhook-cloudflare",
"version": "1.0.0",
"type": "module",
"description": "DodoPayments Webhook Handler for Cloudflare Workers",
"main": "worker.ts",
"scripts": {
"dev": "wrangler dev",
"deploy": "wrangler deploy",
"tail": "wrangler tail"
},
"dependencies": {
"@neondatabase/serverless": "^1.0.2",
"dodopayments": "^2.4.1"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.0.0",
"typescript": "^5.9.3",
"wrangler": "^4.43.0"
}
}
```
```toml wrangler.toml expandable theme={null}
# Cloudflare Workers Configuration
name = "dodo-webhook"
main = "worker.ts"
compatibility_date = "2025-10-20"
[observability]
[observability.logs]
enabled = true
head_sampling_rate = 1
invocation_logs = true
persist = true
```
```json tsconfig.json theme={null}
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"lib": ["ES2022"],
"moduleResolution": "bundler",
"types": ["@cloudflare/workers-types"],
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true
},
"include": ["*.ts"],
"exclude": ["node_modules"]
}
```
## Database Schema
```sql schema.sql expandable theme={null}
-- DodoPayments Webhook Database Schema
-- Compatible with PostgreSQL (Supabase, Neon, etc.)
-- Enable UUID extension (if not already enabled)
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Customers table
CREATE TABLE IF NOT EXISTS customers (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email TEXT NOT NULL,
name TEXT,
dodo_customer_id TEXT UNIQUE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Subscriptions table
CREATE TABLE IF NOT EXISTS subscriptions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
dodo_subscription_id TEXT UNIQUE NOT NULL,
product_id TEXT NOT NULL,
status TEXT NOT NULL CHECK (status IN ('pending', 'active', 'cancelled', 'expired', 'paused')),
billing_interval TEXT CHECK (billing_interval IN ('day', 'week', 'month', 'year')),
amount NUMERIC(10, 2),
currency TEXT DEFAULT 'USD',
next_billing_date TIMESTAMP WITH TIME ZONE,
cancelled_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Webhook events log
CREATE TABLE IF NOT EXISTS webhook_events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
webhook_id TEXT UNIQUE,
event_type TEXT NOT NULL,
data JSONB NOT NULL,
processed BOOLEAN DEFAULT FALSE,
error_message TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
processed_at TIMESTAMP WITH TIME ZONE,
attempts INTEGER DEFAULT 0
);
-- Indexes for better query performance
CREATE INDEX IF NOT EXISTS idx_customers_email ON customers(email);
CREATE INDEX IF NOT EXISTS idx_customers_dodo_id ON customers(dodo_customer_id);
CREATE INDEX IF NOT EXISTS idx_subscriptions_dodo_id ON subscriptions(dodo_subscription_id);
CREATE INDEX IF NOT EXISTS idx_subscriptions_customer_id ON subscriptions(customer_id);
CREATE INDEX IF NOT EXISTS idx_subscriptions_status ON subscriptions(status);
CREATE INDEX IF NOT EXISTS idx_webhook_events_processed ON webhook_events(processed, created_at);
CREATE INDEX IF NOT EXISTS idx_webhook_events_type ON webhook_events(event_type);
CREATE INDEX IF NOT EXISTS idx_webhook_events_created_at ON webhook_events(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_webhook_events_webhook_id ON webhook_events(webhook_id);
-- Function to automatically update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Triggers to automatically update updated_at
CREATE TRIGGER update_customers_updated_at
BEFORE UPDATE ON customers
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_subscriptions_updated_at
BEFORE UPDATE ON subscriptions
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- Comments for documentation
COMMENT ON TABLE customers IS 'Stores customer information from DodoPayments';
COMMENT ON TABLE subscriptions IS 'Stores subscription data from DodoPayments';
COMMENT ON TABLE webhook_events IS 'Logs all incoming webhook events for audit and retry purposes';
COMMENT ON COLUMN customers.dodo_customer_id IS 'Unique customer ID from DodoPayments';
COMMENT ON COLUMN subscriptions.dodo_subscription_id IS 'Unique subscription ID from DodoPayments';
COMMENT ON COLUMN webhook_events.attempts IS 'Number of processing attempts for failed webhooks';
COMMENT ON COLUMN webhook_events.data IS 'Full webhook payload as JSON';
```
**Tables created:**
* **customers** - Email, name, dodo\_customer\_id
* **subscriptions** - Status, amount, next\_billing\_date, linked to customers
* **webhook\_events** - Event log with webhook\_id for idempotency
## Implementation Code
```typescript worker.ts expandable theme={null}
import { DodoPayments } from 'dodopayments';
import { neon, NeonQueryFunction } from '@neondatabase/serverless';
interface Env {
DATABASE_URL: string;
DODO_PAYMENTS_API_KEY: string;
DODO_PAYMENTS_WEBHOOK_KEY: string;
}
interface WebhookPayload {
business_id: string;
type: string;
timestamp: string;
data: {
payload_type: "Subscription" | "Refund" | "Dispute" | "LicenseKey";
subscription_id?: string;
customer: {
customer_id: string;
email: string;
name: string;
};
product_id?: string;
status?: string;
recurring_pre_tax_amount?: number;
payment_frequency_interval?: string;
next_billing_date?: string;
cancelled_at?: string;
currency?: string;
};
}
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, webhook-id, webhook-signature, webhook-timestamp',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
};
async function handleSubscriptionEvent(sql: NeonQueryFunction, payload: WebhookPayload, status: string) {
if (!payload.data.customer.customer_id || !payload.data.subscription_id) {
throw new Error('Missing required fields: customer_id or subscription_id');
}
console.log('🔄 Processing subscription event:', JSON.stringify(payload, null, 2));
const customer = payload.data.customer;
// Upsert customer (create if doesn't exist, otherwise use existing)
const customerResult = await sql`
INSERT INTO customers (email, name, dodo_customer_id, created_at)
VALUES (${customer.email}, ${customer.name || customer.email}, ${customer.customer_id}, ${new Date().toISOString()})
ON CONFLICT (dodo_customer_id)
DO UPDATE SET
email = EXCLUDED.email,
name = EXCLUDED.name,
updated_at = ${new Date().toISOString()}
RETURNING id
`;
const customerId = customerResult[0].id;
console.log(`✅ Customer upserted with ID: ${customerId}`);
// Upsert subscription
await sql`
INSERT INTO subscriptions (
customer_id, dodo_subscription_id, product_id, status,
billing_interval, amount, currency, next_billing_date, cancelled_at, updated_at
)
VALUES (
${customerId}, ${payload.data.subscription_id},
${payload.data.product_id || 'unknown'}, ${status},
${payload.data.payment_frequency_interval?.toLowerCase() || 'month'}, ${payload.data.recurring_pre_tax_amount || 0},
${payload.data.currency || 'USD'}, ${payload.data.next_billing_date || null},
${payload.data.cancelled_at || null}, ${new Date().toISOString()}
)
ON CONFLICT (dodo_subscription_id)
DO UPDATE SET
customer_id = EXCLUDED.customer_id,
product_id = EXCLUDED.product_id,
status = EXCLUDED.status,
billing_interval = EXCLUDED.billing_interval,
amount = EXCLUDED.amount,
currency = EXCLUDED.currency,
next_billing_date = EXCLUDED.next_billing_date,
cancelled_at = EXCLUDED.cancelled_at,
updated_at = EXCLUDED.updated_at
`;
console.log(`✅ Subscription upserted with ${status} status`)
}
export default {
async fetch(request: Request, env: Env): Promise {
// Handle CORS preflight
if (request.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders });
}
if (request.method !== 'POST') {
return new Response(JSON.stringify({ error: 'Method not allowed' }), {
status: 405,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
});
}
try {
const rawBody = await request.text();
console.log('📨 Webhook received');
// Verify required environment variables
if (!env.DODO_PAYMENTS_API_KEY) {
console.error('❌ DODO_PAYMENTS_API_KEY is not configured');
return new Response(
JSON.stringify({ error: 'API key not configured' }),
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
// Verify webhook signature (required for security)
if (!env.DODO_PAYMENTS_WEBHOOK_KEY) {
console.error('❌ DODO_PAYMENTS_WEBHOOK_KEY is not configured');
return new Response(
JSON.stringify({ error: 'Webhook verification key not configured' }),
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
if (!env.DATABASE_URL) {
console.error('❌ DATABASE_URL is not configured');
return new Response(
JSON.stringify({ error: 'Database URL not configured' }),
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
const webhookHeaders = {
'webhook-id': request.headers.get('webhook-id') || '',
'webhook-signature': request.headers.get('webhook-signature') || '',
'webhook-timestamp': request.headers.get('webhook-timestamp') || '',
};
try {
const dodoPaymentsClient = new DodoPayments({
bearerToken: env.DODO_PAYMENTS_API_KEY,
webhookKey: env.DODO_PAYMENTS_WEBHOOK_KEY,
});
const unwrappedWebhook = dodoPaymentsClient.webhooks.unwrap(rawBody, {headers: webhookHeaders});
console.log('Unwrapped webhook:', unwrappedWebhook);
console.log('✅ Webhook signature verified');
} catch (error) {
console.error('❌ Webhook verification failed:', error);
return new Response(
JSON.stringify({ error: 'Webhook verification failed' }),
{ status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
// Initialize Neon client
const sql = neon(env.DATABASE_URL);
const payload: WebhookPayload = JSON.parse(rawBody);
const eventType = payload.type;
const eventData = payload.data;
const webhookId = request.headers.get('webhook-id') || '';
console.log(`📋 Webhook payload:`, JSON.stringify(payload, null, 2));
// Check for duplicate webhook-id (idempotency)
if (webhookId) {
const existingEvent = await sql`
SELECT id FROM webhook_events WHERE webhook_id = ${webhookId}
`;
if (existingEvent.length > 0) {
console.log(`⚠️ Webhook ${webhookId} already processed, skipping (idempotency)`);
return new Response(
JSON.stringify({ success: true, message: 'Webhook already processed' }),
{ status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
}
// Log webhook event with webhook_id for idempotency
const logResult = await sql`
INSERT INTO webhook_events (webhook_id, event_type, data, processed, created_at)
VALUES (${webhookId || null}, ${eventType}, ${JSON.stringify(eventData)}, ${false}, ${new Date().toISOString()})
RETURNING id
`;
const loggedEventId = logResult[0].id;
console.log('📝 Webhook event logged with ID:', loggedEventId);
console.log(`🔄 Processing: ${eventType} (${eventData.payload_type || 'unknown payload type'})`);
try {
switch (eventType) {
case 'subscription.active':
await handleSubscriptionEvent(sql, payload, 'active');
break;
case 'subscription.cancelled':
await handleSubscriptionEvent(sql, payload, 'cancelled');
break;
case 'subscription.renewed':
console.log('🔄 Subscription renewed - keeping active status and updating billing date');
await handleSubscriptionEvent(sql, payload, 'active');
break;
default:
console.log(`ℹ️ Event ${eventType} logged but not processed (no handler available)`);
}
await sql`
UPDATE webhook_events
SET processed = ${true}, processed_at = ${new Date().toISOString()}
WHERE id = ${loggedEventId}
`;
console.log('✅ Webhook marked as processed');
} catch (processingError) {
console.error('❌ Error processing webhook event:', processingError);
await sql`
UPDATE webhook_events
SET processed = ${false},
error_message = ${processingError instanceof Error ? processingError.message : 'Unknown error'},
processed_at = ${new Date().toISOString()}
WHERE id = ${loggedEventId}
`;
throw processingError;
}
console.log('✅ Webhook processed successfully');
return new Response(
JSON.stringify({
success: true,
event_type: eventType,
event_id: loggedEventId
}),
{ status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
} catch (error) {
console.error('❌ Webhook processing failed:', error);
return new Response(
JSON.stringify({
error: 'Webhook processing failed',
details: error instanceof Error ? error.message : 'Unknown error'
}),
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
}
};
```
## How It Works
The webhook handler:
1. **Verifies the signature** - Ensures the request is from DodoPayments using HMAC-SHA256
2. **Checks for duplicates** - Uses webhook ID to prevent processing the same event twice
3. **Logs the event** - Stores raw webhook in `webhook_events` table for audit trail
4. **Processes the event** - Creates or updates customers and subscriptions
5. **Handles errors** - Logs failures and marks event as unprocessed for retry
## Testing
**Local development:**
```bash theme={null}
npm run dev # Available at http://localhost:8787
```
**Configure in DodoPayments Dashboard:**
1. Go to Developers → Webhooks
2. Add endpoint with your Worker URL
3. Enable: subscription.active, subscription.cancelled, subscription.renewed
## Common Issues
| Issue | Solution |
| ------------------------- | -------------------------------------------------------- |
| Verification failed | Check webhook key is correct from DodoPayments dashboard |
| Database connection error | Verify Neon connection string |
| Worker not deploying | Run `npm install`, check `wrangler.toml` syntax |
## Resources
* [Cloudflare Workers Docs](https://developers.cloudflare.com/workers/)
* [Neon Docs](https://neon.tech/docs/)
* [Webhook Events Guide](/developer-resources/webhooks/intents/webhook-events-guide)
* [GitHub Repo](https://github.com/dodopayments/cloud-functions/tree/main/cloudflare)
# Netlify Functions
Source: https://docs.dodopayments.com/developer-resources/webhooks/examples/netlify-example
Deploy DodoPayments webhooks to Netlify Functions
Complete source code and setup guide
## Quick Setup
### 1. Prerequisites
* [Netlify account](https://app.netlify.com/signup)
* [Neon database](https://neon.tech) account
* DodoPayments API key and webhook key from [dashboard](https://app.dodopayments.com/)
### 2. Install & Deploy
```bash theme={null}
npm install -g netlify-cli
netlify login
git clone https://github.com/dodopayments/cloud-functions.git
cd cloud-functions/netlify
npm install
```
### 3. Database Setup
1. Sign up for [Neon](https://neon.tech)
2. Create a new project
3. Open the SQL Editor
4. Copy and paste the contents of [`schema.sql`](#database-schema)
5. Run the query
6. Get your connection string from Neon → Connection Details
### 4. Set Environment Variables
```bash theme={null}
netlify env:set DATABASE_URL "your-neon-connection-string"
netlify env:set DODO_PAYMENTS_API_KEY "your-api-key"
netlify env:set DODO_PAYMENTS_WEBHOOK_KEY "your-webhook-key"
```
Or set them in [Netlify Dashboard](https://app.netlify.com) → Site Settings → Environment Variables
### 5. Initialize & Deploy
```bash theme={null}
netlify init # Link to your site (first time only)
npm run deploy
```
**Webhook URL:** `https://[your-site].netlify.app/.netlify/functions/webhook`
## What It Does
Processes subscription events and stores them in PostgreSQL:
* **subscription.active** - Creates/updates customer and subscription records
* **subscription.cancelled** - Marks subscription as cancelled
* **subscription.renewed** - Updates next billing date
## Key Features
✅ **Signature verification** - Using the dodopayments library\
✅ **Idempotency** - Prevents duplicate processing with webhook IDs\
✅ **Event logging** - Complete audit trail in `webhook_events` table\
✅ **Error handling** - Logged and retryable
## Configuration Files
```json package.json theme={null}
{
"name": "dodo-webhook-netlify",
"version": "1.0.0",
"type": "module",
"description": "DodoPayments Webhook Handler for Netlify",
"scripts": {
"dev": "netlify dev",
"deploy": "netlify deploy --prod"
},
"dependencies": {
"@neondatabase/serverless": "^1.0.2",
"dodopayments": "^2.4.1"
},
"devDependencies": {
"@netlify/functions": "^5.0.0",
"netlify-cli": "^23.9.1",
"typescript": "^5.9.3"
}
}
```
```json tsconfig.json theme={null}
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"lib": ["ES2022"],
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"types": ["node"]
},
"include": ["functions/*.ts"],
"exclude": ["node_modules"]
}
```
```toml netlify.toml theme={null}
[build]
functions = "functions"
[build.environment]
NODE_VERSION = "18"
[functions]
# Functions will be available at /.netlify/functions/[function-name]
directory = "functions"
```
## Database Schema
```sql schema.sql expandable theme={null}
-- DodoPayments Webhook Database Schema
-- Compatible with PostgreSQL (Supabase, Neon, etc.)
-- Enable UUID extension (if not already enabled)
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Customers table
CREATE TABLE IF NOT EXISTS customers (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email TEXT NOT NULL,
name TEXT,
dodo_customer_id TEXT UNIQUE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Subscriptions table
CREATE TABLE IF NOT EXISTS subscriptions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
dodo_subscription_id TEXT UNIQUE NOT NULL,
product_id TEXT NOT NULL,
status TEXT NOT NULL CHECK (status IN ('pending', 'active', 'cancelled', 'expired', 'paused')),
billing_interval TEXT CHECK (billing_interval IN ('day', 'week', 'month', 'year')),
amount NUMERIC(10, 2),
currency TEXT DEFAULT 'USD',
next_billing_date TIMESTAMP WITH TIME ZONE,
cancelled_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Webhook events log
CREATE TABLE IF NOT EXISTS webhook_events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
webhook_id TEXT UNIQUE,
event_type TEXT NOT NULL,
data JSONB NOT NULL,
processed BOOLEAN DEFAULT FALSE,
error_message TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
processed_at TIMESTAMP WITH TIME ZONE,
attempts INTEGER DEFAULT 0
);
-- Indexes for better query performance
CREATE INDEX IF NOT EXISTS idx_customers_email ON customers(email);
CREATE INDEX IF NOT EXISTS idx_customers_dodo_id ON customers(dodo_customer_id);
CREATE INDEX IF NOT EXISTS idx_subscriptions_dodo_id ON subscriptions(dodo_subscription_id);
CREATE INDEX IF NOT EXISTS idx_subscriptions_customer_id ON subscriptions(customer_id);
CREATE INDEX IF NOT EXISTS idx_subscriptions_status ON subscriptions(status);
CREATE INDEX IF NOT EXISTS idx_webhook_events_processed ON webhook_events(processed, created_at);
CREATE INDEX IF NOT EXISTS idx_webhook_events_type ON webhook_events(event_type);
CREATE INDEX IF NOT EXISTS idx_webhook_events_created_at ON webhook_events(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_webhook_events_webhook_id ON webhook_events(webhook_id);
-- Function to automatically update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Triggers to automatically update updated_at
CREATE TRIGGER update_customers_updated_at
BEFORE UPDATE ON customers
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_subscriptions_updated_at
BEFORE UPDATE ON subscriptions
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- Comments for documentation
COMMENT ON TABLE customers IS 'Stores customer information from DodoPayments';
COMMENT ON TABLE subscriptions IS 'Stores subscription data from DodoPayments';
COMMENT ON TABLE webhook_events IS 'Logs all incoming webhook events for audit and retry purposes';
COMMENT ON COLUMN customers.dodo_customer_id IS 'Unique customer ID from DodoPayments';
COMMENT ON COLUMN subscriptions.dodo_subscription_id IS 'Unique subscription ID from DodoPayments';
COMMENT ON COLUMN webhook_events.attempts IS 'Number of processing attempts for failed webhooks';
COMMENT ON COLUMN webhook_events.data IS 'Full webhook payload as JSON';
```
**Tables created:**
* **customers** - Email, name, dodo\_customer\_id
* **subscriptions** - Status, amount, next\_billing\_date, linked to customers
* **webhook\_events** - Event log with webhook\_id for idempotency
## Implementation Code
```typescript functions/webhook.ts expandable theme={null}
import { Handler, HandlerEvent, HandlerContext } from '@netlify/functions';
import { neon, NeonQueryFunction } from '@neondatabase/serverless';
import { DodoPayments } from 'dodopayments';
interface WebhookPayload {
business_id: string;
type: string;
timestamp: string;
data: {
payload_type: "Subscription" | "Refund" | "Dispute" | "LicenseKey";
subscription_id?: string;
customer: {
customer_id: string;
email: string;
name: string;
};
product_id?: string;
status?: string;
recurring_pre_tax_amount?: number;
payment_frequency_interval?: string;
next_billing_date?: string;
cancelled_at?: string;
currency?: string;
};
}
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, webhook-id, webhook-signature, webhook-timestamp',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
};
async function handleSubscriptionEvent(sql: NeonQueryFunction, payload: WebhookPayload, status: string) {
if (!payload.data.customer.customer_id || !payload.data.subscription_id) {
throw new Error('Missing required fields: customer_id or subscription_id');
}
console.log('🔄 Processing subscription event:', JSON.stringify(payload, null, 2));
const customer = payload.data.customer;
// Upsert customer (create if doesn't exist, otherwise use existing)
const customerResult = await sql`
INSERT INTO customers (email, name, dodo_customer_id, created_at)
VALUES (${customer.email}, ${customer.name || customer.email}, ${customer.customer_id}, ${new Date().toISOString()})
ON CONFLICT (dodo_customer_id)
DO UPDATE SET
email = EXCLUDED.email,
name = EXCLUDED.name,
updated_at = ${new Date().toISOString()}
RETURNING id
`;
const customerId = customerResult[0].id;
console.log(`✅ Customer upserted with ID: ${customerId}`);
// Upsert subscription
await sql`
INSERT INTO subscriptions (
customer_id, dodo_subscription_id, product_id, status,
billing_interval, amount, currency, next_billing_date, cancelled_at, updated_at
)
VALUES (
${customerId}, ${payload.data.subscription_id},
${payload.data.product_id || 'unknown'}, ${status},
${payload.data.payment_frequency_interval?.toLowerCase() || 'month'}, ${payload.data.recurring_pre_tax_amount || 0},
${payload.data.currency || 'USD'}, ${payload.data.next_billing_date || null},
${payload.data.cancelled_at || null}, ${new Date().toISOString()}
)
ON CONFLICT (dodo_subscription_id)
DO UPDATE SET
customer_id = EXCLUDED.customer_id,
product_id = EXCLUDED.product_id,
status = EXCLUDED.status,
billing_interval = EXCLUDED.billing_interval,
amount = EXCLUDED.amount,
currency = EXCLUDED.currency,
next_billing_date = EXCLUDED.next_billing_date,
cancelled_at = EXCLUDED.cancelled_at,
updated_at = EXCLUDED.updated_at
`;
console.log(`✅ Subscription upserted with ${status} status`)
}
export const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
// Handle CORS preflight
if (event.httpMethod === 'OPTIONS') {
return {
statusCode: 200,
headers: corsHeaders,
body: 'ok'
};
}
if (event.httpMethod !== 'POST') {
return {
statusCode: 405,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Method not allowed' })
};
}
try {
const rawBody = event.body || '';
console.log('📨 Webhook received');
const DATABASE_URL = process.env.DATABASE_URL;
const API_KEY = process.env.DODO_PAYMENTS_API_KEY;
const WEBHOOK_KEY = process.env.DODO_PAYMENTS_WEBHOOK_KEY;
if (!DATABASE_URL) {
console.error('❌ Missing DATABASE_URL environment variable');
return {
statusCode: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Server configuration error' })
};
}
// Verify required environment variables
if (!API_KEY) {
console.error('❌ DODO_PAYMENTS_API_KEY is not configured');
return {
statusCode: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'API key not configured' })
};
}
// Verify webhook signature (required for security)
if (!WEBHOOK_KEY) {
console.error('❌ DODO_PAYMENTS_WEBHOOK_KEY is not configured');
return {
statusCode: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Webhook verification key not configured' })
};
}
const webhookHeaders = {
'webhook-id': event.headers['webhook-id'] || '',
'webhook-signature': event.headers['webhook-signature'] || '',
'webhook-timestamp': event.headers['webhook-timestamp'] || '',
};
try {
const dodoPaymentsClient = new DodoPayments({
bearerToken: API_KEY,
webhookKey: WEBHOOK_KEY,
});
const unwrappedWebhook = dodoPaymentsClient.webhooks.unwrap(rawBody, {headers: webhookHeaders});
console.log('Unwrapped webhook:', unwrappedWebhook);
console.log('✅ Webhook signature verified');
} catch (error) {
console.error('❌ Webhook verification failed:', error);
return {
statusCode: 401,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Webhook verification failed' })
};
}
// Initialize Neon client
const sql = neon(DATABASE_URL);
const payload: WebhookPayload = JSON.parse(rawBody);
const eventType = payload.type;
const eventData = payload.data;
const webhookId = event.headers['webhook-id'] || '';
console.log(`📋 Webhook payload:`, JSON.stringify(payload, null, 2));
// Check for duplicate webhook-id (idempotency)
if (webhookId) {
const existingEvent = await sql`
SELECT id FROM webhook_events WHERE webhook_id = ${webhookId}
`;
if (existingEvent.length > 0) {
console.log(`⚠️ Webhook ${webhookId} already processed, skipping (idempotency)`);
return {
statusCode: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({ success: true, message: 'Webhook already processed' })
};
}
}
// Log webhook event with webhook_id for idempotency
const logResult = await sql`
INSERT INTO webhook_events (webhook_id, event_type, data, processed, created_at)
VALUES (${webhookId || null}, ${eventType}, ${JSON.stringify(eventData)}, ${false}, ${new Date().toISOString()})
RETURNING id
`;
const loggedEventId = logResult[0].id;
console.log('📝 Webhook event logged with ID:', loggedEventId);
console.log(`🔄 Processing: ${eventType} (${eventData.payload_type || 'unknown payload type'})`);
try {
switch (eventType) {
case 'subscription.active':
await handleSubscriptionEvent(sql, payload, 'active');
break;
case 'subscription.cancelled':
await handleSubscriptionEvent(sql, payload, 'cancelled');
break;
case 'subscription.renewed':
console.log('🔄 Subscription renewed - keeping active status and updating billing date');
await handleSubscriptionEvent(sql, payload, 'active');
break;
default:
console.log(`ℹ️ Event ${eventType} logged but not processed (no handler available)`);
}
await sql`
UPDATE webhook_events
SET processed = ${true}, processed_at = ${new Date().toISOString()}
WHERE id = ${loggedEventId}
`;
console.log('✅ Webhook marked as processed');
} catch (processingError) {
console.error('❌ Error processing webhook event:', processingError);
await sql`
UPDATE webhook_events
SET processed = ${false},
error_message = ${processingError instanceof Error ? processingError.message : 'Unknown error'},
processed_at = ${new Date().toISOString()}
WHERE id = ${loggedEventId}
`;
throw processingError;
}
console.log('✅ Webhook processed successfully');
return {
statusCode: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({
success: true,
event_type: eventType,
event_id: loggedEventId
})
};
} catch (error) {
console.error('❌ Webhook processing failed:', error);
return {
statusCode: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({
error: 'Webhook processing failed',
details: error instanceof Error ? error.message : 'Unknown error'
})
};
}
};
```
## How It Works
The webhook handler:
1. **Verifies the signature** - Ensures the request is from DodoPayments using HMAC-SHA256
2. **Checks for duplicates** - Uses webhook ID to prevent processing the same event twice
3. **Logs the event** - Stores raw webhook in `webhook_events` table for audit trail
4. **Processes the event** - Creates or updates customers and subscriptions in Neon
5. **Handles errors** - Logs failures and marks event as unprocessed for retry
## Testing
**Local development:**
```bash theme={null}
npm run dev # Available at http://localhost:8888/.netlify/functions/webhook
```
**View logs in Netlify Dashboard:**
1. Select your site → **Functions** tab
2. Click on the `webhook` function
3. View real-time logs and invocation history
**Configure in DodoPayments Dashboard:**
1. Go to Developers → Webhooks
2. Add endpoint with your Netlify Functions URL
3. Enable: subscription.active, subscription.cancelled, subscription.renewed
## Common Issues
| Issue | Solution |
| ------------------------- | -------------------------------------------------------- |
| Verification failed | Check webhook key is correct from DodoPayments dashboard |
| Database connection error | Verify Neon connection string and use pooled connection |
| Function not found (404) | Run `netlify init` and `npm run deploy` again |
| Env vars not available | Set variables in CLI or dashboard, then redeploy |
## Resources
* [Netlify Functions Docs](https://docs.netlify.com/functions/overview/)
* [Netlify CLI](https://docs.netlify.com/cli/get-started/)
* [Neon Docs](https://neon.tech/docs/)
* [Webhook Events Guide](/developer-resources/webhooks/intents/webhook-events-guide)
* [GitHub Repo](https://github.com/dodopayments/cloud-functions/tree/main/netlify)
# Supabase Edge Functions
Source: https://docs.dodopayments.com/developer-resources/webhooks/examples/supabase-example
Deploy DodoPayments webhooks to Supabase Edge Functions
Complete source code and setup guide
Checkout the complete starter kit for supabase
A minimal subscription starter kit built with Next.js, Supabase, and Dodo Payments. This boilerplate helps you quickly set up a subscription-based SaaS with authentication, payments, and webhooks.
Dodo Payments Supabase Subscription Starter
## Quick Setup
### 1. Prerequisites
* [Supabase account](https://supabase.com/dashboard)
* A Supabase project created
* DodoPayments API key and webhook key from [dashboard](https://app.dodopayments.com/)
### 2. Authenticate & Link
```bash theme={null}
npx supabase login
git clone https://github.com/dodopayments/cloud-functions.git
cd cloud-functions/supabase
npx supabase link --project-ref your-project-ref
```
Get your project ref from [Supabase Dashboard](https://supabase.com/dashboard) → Project Settings
### 3. Database Setup
1. Go to your [Supabase Dashboard](https://supabase.com/dashboard)
2. Open the SQL Editor
3. Create a new query
4. Copy and paste the entire contents of [`schema.sql`](#database-schema)
5. Run the query
### 4. Set Environment Variables
Supabase automatically provides `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE_KEY` at runtime.
Set your webhook key and API key:
```bash theme={null}
npx supabase secrets set DODO_PAYMENTS_API_KEY=your-api-key
npx supabase secrets set DODO_PAYMENTS_WEBHOOK_KEY=your-webhook-key
```
### 5. Deploy
The function is already set up in `functions/webhook/index.ts` - just deploy it:
```bash theme={null}
npm run deploy
```
**Webhook URL:** `https://[project-ref].supabase.co/functions/v1/webhook`
## What It Does
Processes subscription events and stores them in Supabase PostgreSQL:
* **subscription.active** - Creates/updates customer and subscription records
* **subscription.cancelled** - Marks subscription as cancelled
* **subscription.renewed** - Updates next billing date
## Key Features
✅ **Signature verification** - Using the dodopayments library\
✅ **Idempotency** - Prevents duplicate processing with webhook IDs\
✅ **Event logging** - Complete audit trail in `webhook_events` table\
✅ **Error handling** - Logged and retryable
## Configuration Files
```json package.json theme={null}
{
"name": "dodo-webhook-supabase",
"version": "1.0.0",
"type": "module",
"description": "DodoPayments Webhook Handler for Supabase Edge Functions",
"scripts": {
"dev": "npx supabase functions serve webhook --no-verify-jwt --workdir ..",
"deploy": "npx supabase functions deploy webhook --no-verify-jwt --workdir .."
}
}
```
```json tsconfig.json theme={null}
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"lib": ["ES2022", "DOM"],
"moduleResolution": "bundler",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true
},
"include": ["functions/webhook/*.ts"],
"exclude": ["node_modules"]
}
```
## Database Schema
```sql schema.sql expandable theme={null}
-- DodoPayments Webhook Database Schema
-- Compatible with PostgreSQL (Supabase, Neon, etc.)
-- Enable UUID extension (if not already enabled)
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Customers table
CREATE TABLE IF NOT EXISTS customers (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email TEXT NOT NULL,
name TEXT,
dodo_customer_id TEXT UNIQUE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Subscriptions table
CREATE TABLE IF NOT EXISTS subscriptions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
dodo_subscription_id TEXT UNIQUE NOT NULL,
product_id TEXT NOT NULL,
status TEXT NOT NULL CHECK (status IN ('pending', 'active', 'cancelled', 'expired', 'paused')),
billing_interval TEXT CHECK (billing_interval IN ('day', 'week', 'month', 'year')),
amount NUMERIC(10, 2),
currency TEXT DEFAULT 'USD',
next_billing_date TIMESTAMP WITH TIME ZONE,
cancelled_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Webhook events log
CREATE TABLE IF NOT EXISTS webhook_events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
webhook_id TEXT UNIQUE,
event_type TEXT NOT NULL,
data JSONB NOT NULL,
processed BOOLEAN DEFAULT FALSE,
error_message TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
processed_at TIMESTAMP WITH TIME ZONE,
attempts INTEGER DEFAULT 0
);
-- Indexes for better query performance
CREATE INDEX IF NOT EXISTS idx_customers_email ON customers(email);
CREATE INDEX IF NOT EXISTS idx_customers_dodo_id ON customers(dodo_customer_id);
CREATE INDEX IF NOT EXISTS idx_subscriptions_dodo_id ON subscriptions(dodo_subscription_id);
CREATE INDEX IF NOT EXISTS idx_subscriptions_customer_id ON subscriptions(customer_id);
CREATE INDEX IF NOT EXISTS idx_subscriptions_status ON subscriptions(status);
CREATE INDEX IF NOT EXISTS idx_webhook_events_processed ON webhook_events(processed, created_at);
CREATE INDEX IF NOT EXISTS idx_webhook_events_type ON webhook_events(event_type);
CREATE INDEX IF NOT EXISTS idx_webhook_events_created_at ON webhook_events(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_webhook_events_webhook_id ON webhook_events(webhook_id);
-- Function to automatically update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Triggers to automatically update updated_at
CREATE TRIGGER update_customers_updated_at
BEFORE UPDATE ON customers
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_subscriptions_updated_at
BEFORE UPDATE ON subscriptions
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- Comments for documentation
COMMENT ON TABLE customers IS 'Stores customer information from DodoPayments';
COMMENT ON TABLE subscriptions IS 'Stores subscription data from DodoPayments';
COMMENT ON TABLE webhook_events IS 'Logs all incoming webhook events for audit and retry purposes';
COMMENT ON COLUMN customers.dodo_customer_id IS 'Unique customer ID from DodoPayments';
COMMENT ON COLUMN subscriptions.dodo_subscription_id IS 'Unique subscription ID from DodoPayments';
COMMENT ON COLUMN webhook_events.attempts IS 'Number of processing attempts for failed webhooks';
COMMENT ON COLUMN webhook_events.data IS 'Full webhook payload as JSON';
```
**Tables created:**
* **customers** - Email, name, dodo\_customer\_id
* **subscriptions** - Status, amount, next\_billing\_date, linked to customers
* **webhook\_events** - Event log with webhook\_id for idempotency
## Implementation Code
```typescript functions/webhook/index.ts expandable theme={null}
import { serve } from 'https://deno.land/std@0.208.0/http/server.ts';
import { createClient, SupabaseClient } from 'https://esm.sh/@supabase/supabase-js@2.38.4';
import { DodoPayments } from 'https://esm.sh/dodopayments@2.4.1';
export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, webhook-id, webhook-signature, webhook-timestamp',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
};
interface WebhookPayload {
business_id: string;
type: string;
timestamp: string;
data: {
payload_type: "Subscription" | "Refund" | "Dispute" | "LicenseKey";
subscription_id?: string;
customer: {
customer_id: string;
email: string;
name: string;
};
product_id?: string;
status?: string;
recurring_pre_tax_amount?: number;
payment_frequency_interval?: string;
next_billing_date?: string;
cancelled_at?: string;
currency?: string;
};
}
// Handle subscription events
async function handleSubscriptionEvent(supabase: SupabaseClient, payload: WebhookPayload, status: string) {
if (!payload.data.customer.customer_id || !payload.data.subscription_id) {
throw new Error('Missing required fields: customer_id or subscription_id');
}
try {
console.log('🔄 Processing subscription event:', JSON.stringify(payload, null, 2));
const customer = payload.data.customer;
// Upsert customer (create if doesn't exist, otherwise update)
const customerResult = await supabase
.from('customers')
.upsert({
email: customer.email,
name: customer.name || customer.email,
dodo_customer_id: customer.customer_id
}, {
onConflict: 'dodo_customer_id',
ignoreDuplicates: false
})
.select('id')
.single();
if (customerResult.error) {
console.error('❌ Failed to upsert customer:', customerResult.error);
throw new Error(`Failed to upsert customer: ${customerResult.error.message}`);
}
const customerId = customerResult.data.id;
console.log(`✅ Customer upserted with ID: ${customerId}`);
// Upsert subscription
const subscriptionResult = await supabase
.from('subscriptions')
.upsert({
customer_id: customerId,
dodo_subscription_id: payload.data.subscription_id,
product_id: payload.data.product_id || 'unknown',
status,
billing_interval: payload.data.payment_frequency_interval?.toLowerCase() || 'month',
amount: payload.data.recurring_pre_tax_amount || 0,
currency: payload.data.currency || 'USD',
next_billing_date: payload.data.next_billing_date || null,
cancelled_at: payload.data.cancelled_at || null,
updated_at: new Date().toISOString()
}, {
onConflict: 'dodo_subscription_id',
ignoreDuplicates: false
})
.select();
if (subscriptionResult.error) {
console.error('❌ Failed to upsert subscription:', subscriptionResult.error);
throw new Error(`Failed to upsert subscription: ${subscriptionResult.error.message}`);
}
console.log(`✅ Subscription upserted with ${status} status`);
} catch (error) {
console.error('❌ Error in handleSubscriptionEvent:', error);
console.error('❌ Raw webhook data:', JSON.stringify(payload, null, 2));
throw error;
}
}
serve(async (req: Request) => {
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders });
}
// Validate required environment variables
try {
const supabaseUrl = Deno.env.get('SUPABASE_URL');
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY');
if (!supabaseUrl || !supabaseServiceKey) {
console.error('❌ Missing required environment variables');
return new Response(
JSON.stringify({ error: 'Server configuration error' }),
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
const rawBody = await req.text();
console.log('📨 Webhook received');
// Verify webhook signature (required for security)
const apiKey = Deno.env.get('DODO_PAYMENTS_API_KEY');
const webhookKey = Deno.env.get('DODO_PAYMENTS_WEBHOOK_KEY');
if (!apiKey) {
console.error('❌ DODO_PAYMENTS_API_KEY is not configured');
return new Response(
JSON.stringify({ error: 'API key not configured' }),
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
if (!webhookKey) {
console.error('❌ DODO_PAYMENTS_WEBHOOK_KEY is not configured');
return new Response(
JSON.stringify({ error: 'Webhook verification key not configured' }),
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
const webhookHeaders = {
'webhook-id': req.headers.get('webhook-id') || '',
'webhook-signature': req.headers.get('webhook-signature') || '',
'webhook-timestamp': req.headers.get('webhook-timestamp') || '',
};
try {
const dodoPaymentsClient = new DodoPayments({
bearerToken: apiKey,
webhookKey: webhookKey,
});
const unwrappedWebhook = dodoPaymentsClient.webhooks.unwrap(rawBody, {headers: webhookHeaders});
console.log('Unwrapped webhook:', unwrappedWebhook);
console.log('✅ Webhook signature verified');
} catch (error) {
console.error('❌ Webhook verification failed:', error);
return new Response(
JSON.stringify({
error: 'Webhook verification failed',
details: error instanceof Error ? error.message : 'Invalid signature'
}),
{ status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
// Initialize Supabase client
const supabase = createClient(supabaseUrl, supabaseServiceKey);
let payload: WebhookPayload;
try {
payload = JSON.parse(rawBody) as WebhookPayload;
} catch (parseError) {
console.error('❌ Failed to parse webhook payload:', parseError);
return new Response(
JSON.stringify({ error: 'Invalid JSON payload' }),
{ status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
const eventType = payload.type;
const eventData = payload.data;
const webhookId = req.headers.get('webhook-id') || '';
console.log(`📋 Webhook payload:`, JSON.stringify(payload, null, 2));
// Check for duplicate webhook-id (idempotency)
if (webhookId) {
const { data: existingEvent } = await supabase
.from('webhook_events')
.select('id')
.eq('webhook_id', webhookId)
.single();
if (existingEvent) {
console.log(`⚠️ Webhook ${webhookId} already processed, skipping (idempotency)`);
return new Response(
JSON.stringify({ success: true, message: 'Webhook already processed' }),
{ status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
}
// Log webhook event with webhook_id for idempotency
const logResult = await supabase.from('webhook_events').insert([{
webhook_id: webhookId || null,
event_type: eventType,
data: eventData,
processed: false,
created_at: new Date().toISOString()
}]).select('id').single();
if (logResult.error) {
console.error('❌ Failed to log webhook event:', logResult.error);
return new Response(
JSON.stringify({ error: 'Failed to log webhook event' }),
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
const loggedEventId = logResult.data.id;
console.log('📝 Webhook event logged with ID:', loggedEventId);
console.log(`🔄 Processing: ${eventType} (${eventData.payload_type || 'unknown payload type'})`);
try {
switch (eventType) {
case 'subscription.active':
await handleSubscriptionEvent(supabase, payload, 'active');
break;
case 'subscription.cancelled':
await handleSubscriptionEvent(supabase, payload, 'cancelled');
break;
case 'subscription.renewed':
console.log('🔄 Subscription renewed - keeping active status and updating billing date');
await handleSubscriptionEvent(supabase, payload, 'active');
break;
default:
console.log(`ℹ️ Event ${eventType} logged but not processed (no handler available)`);
}
const updateResult = await supabase
.from('webhook_events')
.update({
processed: true,
processed_at: new Date().toISOString()
})
.eq('id', loggedEventId);
if (updateResult.error) {
console.error('❌ Failed to mark webhook as processed:', updateResult.error);
} else {
console.log('✅ Webhook marked as processed');
}
} catch (processingError) {
console.error('❌ Error processing webhook event:', processingError);
await supabase
.from('webhook_events')
.update({
processed: false,
error_message: processingError instanceof Error ? processingError.message : 'Unknown error',
processed_at: new Date().toISOString()
})
.eq('id', loggedEventId);
throw processingError;
}
console.log('✅ Webhook processed successfully');
return new Response(
JSON.stringify({
success: true,
event_type: eventType,
event_id: loggedEventId
}),
{ status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
} catch (error) {
console.error('❌ Webhook processing failed:', error);
return new Response(
JSON.stringify({
error: 'Webhook processing failed',
details: error instanceof Error ? error.message : 'Unknown error'
}),
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
});
```
## How It Works
The Deno-based Edge Function:
1. **Verifies signature** - Uses dodopayments library for HMAC-SHA256 verification
2. **Checks idempotency** - Looks up webhook ID to prevent duplicate processing
3. **Logs the event** - Stores raw webhook data in `webhook_events` table
4. **Processes updates** - Creates or updates customers and subscriptions via Supabase client
5. **Handles errors** - Logs failures and marks event for retry
## Testing
**Local development:**
```bash theme={null}
cd supabase
npm run dev
# Available at http://localhost:54321/functions/v1/webhook
```
The `--no-verify-jwt` flag is required because webhooks don't include JWT tokens. Security is provided by webhook signature verification.
**View logs:**
```bash theme={null}
npx supabase functions logs webhook
```
Or in [Supabase Dashboard](https://supabase.com/dashboard) → Edge Functions → webhook → Logs tab
**Configure in DodoPayments Dashboard:**
1. Go to Developers → Webhooks
2. Add endpoint with your Supabase URL
3. Enable: subscription.active, subscription.cancelled, subscription.renewed
## Common Issues
| Issue | Solution |
| ------------------------- | -------------------------------------------------------- |
| Verification failed | Check webhook key is correct from DodoPayments dashboard |
| Database permission error | Ensure using Service Role Key |
| JWT verification error | Deploy with `--no-verify-jwt` flag |
| Function not found | Verify project ref is correct and function is deployed |
## Resources
* [Supabase Edge Functions Docs](https://supabase.com/docs/guides/functions)
* [Supabase CLI](https://supabase.com/docs/reference/cli)
* [Deno Runtime](https://deno.land/)
* [Webhook Events Guide](/developer-resources/webhooks/intents/webhook-events-guide)
* [GitHub Repo](https://github.com/dodopayments/cloud-functions/tree/main/supabase)
# Vercel Functions
Source: https://docs.dodopayments.com/developer-resources/webhooks/examples/vercel-example
Deploy DodoPayments webhooks to Vercel
Complete source code and setup guide
## Quick Setup
### 1. Prerequisites
* [Vercel account](https://vercel.com/signup)
* [Neon database](https://neon.tech) account
* DodoPayments API key and webhook key from [dashboard](https://app.dodopayments.com/)
### 2. Install & Deploy
```bash theme={null}
npm install -g vercel
vercel login
git clone https://github.com/dodopayments/cloud-functions.git
cd cloud-functions/vercel
npm install
```
### 3. Database Setup
1. Sign up for [Neon](https://neon.tech)
2. Create a new project
3. Open the SQL Editor
4. Copy and paste the contents of [`schema.sql`](#database-schema)
5. Run the query
6. Get your connection string from Neon → Connection Details
### 4. Set Environment Variables
Via Vercel CLI:
```bash theme={null}
vercel env add DATABASE_URL
vercel env add DODO_PAYMENTS_API_KEY
vercel env add DODO_PAYMENTS_WEBHOOK_KEY
```
Or via [Vercel Dashboard](https://vercel.com/dashboard) → Project Settings → Environment Variables
### 5. Deploy
```bash theme={null}
npm run deploy
```
**Webhook URL:** `https://[your-project].vercel.app/api/webhook`
## What It Does
Processes subscription events and stores them in PostgreSQL:
* **subscription.active** - Creates/updates customer and subscription records
* **subscription.cancelled** - Marks subscription as cancelled
* **subscription.renewed** - Updates next billing date
## Key Features
✅ **Signature verification** - Using the dodopayments library\
✅ **Idempotency** - Prevents duplicate processing with webhook IDs\
✅ **Event logging** - Complete audit trail in `webhook_events` table\
✅ **Error handling** - Logged and retryable
## Configuration Files
```json package.json theme={null}
{
"name": "dodo-webhook-vercel",
"version": "1.0.0",
"type": "module",
"description": "DodoPayments Webhook Handler for Vercel",
"scripts": {
"start": "vercel dev",
"deploy": "vercel --prod"
},
"dependencies": {
"@neondatabase/serverless": "^1.0.2",
"dodopayments": "^2.4.1"
},
"devDependencies": {
"typescript": "^5.9.3",
"vercel": "^48.4.1"
}
}
```
```json tsconfig.json theme={null}
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"lib": ["ES2022"],
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"types": ["node"]
},
"include": ["api/**/*.ts"],
"exclude": ["node_modules"]
}
```
## Database Schema
```sql schema.sql expandable theme={null}
-- DodoPayments Webhook Database Schema
-- Compatible with PostgreSQL (Supabase, Neon, etc.)
-- Enable UUID extension (if not already enabled)
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Customers table
CREATE TABLE IF NOT EXISTS customers (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email TEXT NOT NULL,
name TEXT,
dodo_customer_id TEXT UNIQUE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Subscriptions table
CREATE TABLE IF NOT EXISTS subscriptions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
dodo_subscription_id TEXT UNIQUE NOT NULL,
product_id TEXT NOT NULL,
status TEXT NOT NULL CHECK (status IN ('pending', 'active', 'cancelled', 'expired', 'paused')),
billing_interval TEXT CHECK (billing_interval IN ('day', 'week', 'month', 'year')),
amount NUMERIC(10, 2),
currency TEXT DEFAULT 'USD',
next_billing_date TIMESTAMP WITH TIME ZONE,
cancelled_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Webhook events log
CREATE TABLE IF NOT EXISTS webhook_events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
webhook_id TEXT UNIQUE,
event_type TEXT NOT NULL,
data JSONB NOT NULL,
processed BOOLEAN DEFAULT FALSE,
error_message TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
processed_at TIMESTAMP WITH TIME ZONE,
attempts INTEGER DEFAULT 0
);
-- Indexes for better query performance
CREATE INDEX IF NOT EXISTS idx_customers_email ON customers(email);
CREATE INDEX IF NOT EXISTS idx_customers_dodo_id ON customers(dodo_customer_id);
CREATE INDEX IF NOT EXISTS idx_subscriptions_dodo_id ON subscriptions(dodo_subscription_id);
CREATE INDEX IF NOT EXISTS idx_subscriptions_customer_id ON subscriptions(customer_id);
CREATE INDEX IF NOT EXISTS idx_subscriptions_status ON subscriptions(status);
CREATE INDEX IF NOT EXISTS idx_webhook_events_processed ON webhook_events(processed, created_at);
CREATE INDEX IF NOT EXISTS idx_webhook_events_type ON webhook_events(event_type);
CREATE INDEX IF NOT EXISTS idx_webhook_events_created_at ON webhook_events(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_webhook_events_webhook_id ON webhook_events(webhook_id);
-- Function to automatically update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Triggers to automatically update updated_at
CREATE TRIGGER update_customers_updated_at
BEFORE UPDATE ON customers
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_subscriptions_updated_at
BEFORE UPDATE ON subscriptions
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- Comments for documentation
COMMENT ON TABLE customers IS 'Stores customer information from DodoPayments';
COMMENT ON TABLE subscriptions IS 'Stores subscription data from DodoPayments';
COMMENT ON TABLE webhook_events IS 'Logs all incoming webhook events for audit and retry purposes';
COMMENT ON COLUMN customers.dodo_customer_id IS 'Unique customer ID from DodoPayments';
COMMENT ON COLUMN subscriptions.dodo_subscription_id IS 'Unique subscription ID from DodoPayments';
COMMENT ON COLUMN webhook_events.attempts IS 'Number of processing attempts for failed webhooks';
COMMENT ON COLUMN webhook_events.data IS 'Full webhook payload as JSON';
```
**Tables created:**
* **customers** - Email, name, dodo\_customer\_id
* **subscriptions** - Status, amount, next\_billing\_date, linked to customers
* **webhook\_events** - Event log with webhook\_id for idempotency
## Implementation Code
```typescript api/webhook/index.ts expandable theme={null}
import { neon, NeonQueryFunction } from '@neondatabase/serverless';
import { DodoPayments } from 'dodopayments';
interface WebhookPayload {
business_id: string;
type: string;
timestamp: string;
data: {
payload_type: "Subscription" | "Refund" | "Dispute" | "LicenseKey";
subscription_id?: string;
customer: {
customer_id: string;
email: string;
name: string;
};
product_id?: string;
status?: string;
recurring_pre_tax_amount?: number;
payment_frequency_interval?: string;
next_billing_date?: string;
cancelled_at?: string;
currency?: string;
};
}
// Disable body parsing to access raw body for webhook verification
export const config = {
api: {
bodyParser: false,
},
};
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, webhook-id, webhook-signature, webhook-timestamp',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
};
// Helper function for JSON responses
function jsonResponse(data: unknown, status: number = 200) {
return new Response(JSON.stringify(data), {
status,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
});
}
// Handle subscription events
async function handleSubscriptionEvent(sql: NeonQueryFunction, payload: WebhookPayload, status: string) {
if (!payload.data.customer.customer_id || !payload.data.subscription_id) {
throw new Error('Missing required fields: customer_id or subscription_id');
}
console.log('🔄 Processing subscription event:', JSON.stringify(payload, null, 2));
const customer = payload.data.customer;
// Upsert customer (create if doesn't exist, otherwise use existing)
const customerResult = await sql`
INSERT INTO customers (email, name, dodo_customer_id, created_at)
VALUES (${customer.email}, ${customer.name || customer.email}, ${customer.customer_id}, ${new Date().toISOString()})
ON CONFLICT (dodo_customer_id)
DO UPDATE SET
email = EXCLUDED.email,
name = EXCLUDED.name,
updated_at = ${new Date().toISOString()}
RETURNING id
`;
const customerId = customerResult[0].id;
console.log(`✅ Customer upserted with ID: ${customerId}`);
// Upsert subscription
await sql`
INSERT INTO subscriptions (
customer_id, dodo_subscription_id, product_id, status,
billing_interval, amount, currency, next_billing_date, cancelled_at, updated_at
)
VALUES (
${customerId}, ${payload.data.subscription_id},
${payload.data.product_id || 'unknown'}, ${status},
${payload.data.payment_frequency_interval?.toLowerCase() || 'month'}, ${payload.data.recurring_pre_tax_amount || 0},
${payload.data.currency || 'USD'}, ${payload.data.next_billing_date || null},
${payload.data.cancelled_at || null}, ${new Date().toISOString()}
)
ON CONFLICT (dodo_subscription_id)
DO UPDATE SET
customer_id = EXCLUDED.customer_id,
product_id = EXCLUDED.product_id,
status = EXCLUDED.status,
billing_interval = EXCLUDED.billing_interval,
amount = EXCLUDED.amount,
currency = EXCLUDED.currency,
next_billing_date = EXCLUDED.next_billing_date,
cancelled_at = EXCLUDED.cancelled_at,
updated_at = EXCLUDED.updated_at
`;
console.log(`✅ Subscription upserted with ${status} status`)
}
// Handle CORS preflight
export async function OPTIONS() {
return new Response('ok', {
status: 200,
headers: corsHeaders
});
}
// Handle webhook POST request
export async function POST(req: Request) {
try {
// Get raw body for webhook signature verification
const rawBody = await req.text();
console.log('📨 Webhook received');
const DATABASE_URL = process.env.DATABASE_URL;
const API_KEY = process.env.DODO_PAYMENTS_API_KEY;
const WEBHOOK_KEY = process.env.DODO_PAYMENTS_WEBHOOK_KEY;
if (!DATABASE_URL) {
console.error('❌ Missing DATABASE_URL environment variable');
return jsonResponse({ error: 'Server configuration error' }, 500);
}
// Verify required environment variables
if (!API_KEY) {
console.error('❌ DODO_PAYMENTS_API_KEY is not configured');
return jsonResponse({ error: 'API key not configured' }, 500);
}
// Verify webhook signature (required for security)
if (!WEBHOOK_KEY) {
console.error('❌ DODO_PAYMENTS_WEBHOOK_KEY is not configured');
return jsonResponse({ error: 'Webhook verification key not configured' }, 500);
}
const webhookHeaders = {
'webhook-id': req.headers.get('webhook-id') || '',
'webhook-signature': req.headers.get('webhook-signature') || '',
'webhook-timestamp': req.headers.get('webhook-timestamp') || '',
};
try {
const dodoPaymentsClient = new DodoPayments({
bearerToken: API_KEY,
webhookKey: WEBHOOK_KEY,
});
const unwrappedWebhook = dodoPaymentsClient.webhooks.unwrap(rawBody, {headers: webhookHeaders});
console.log('Unwrapped webhook:', unwrappedWebhook);
console.log('✅ Webhook signature verified');
} catch (error) {
console.error('❌ Webhook verification failed:', error);
return jsonResponse({ error: 'Webhook verification failed' }, 401);
}
// Initialize Neon client
const sql = neon(DATABASE_URL);
const payload: WebhookPayload = JSON.parse(rawBody);
const eventType = payload.type;
const eventData = payload.data;
const webhookId = req.headers.get('webhook-id') || '';
console.log(`📋 Webhook payload:`, JSON.stringify(payload, null, 2));
// Check for duplicate webhook-id (idempotency)
if (webhookId) {
const existingEvent = await sql`
SELECT id FROM webhook_events WHERE webhook_id = ${webhookId}
`;
if (existingEvent.length > 0) {
console.log(`⚠️ Webhook ${webhookId} already processed, skipping (idempotency)`);
return jsonResponse({ success: true, message: 'Webhook already processed' });
}
}
// Log webhook event with webhook_id for idempotency
const logResult = await sql`
INSERT INTO webhook_events (webhook_id, event_type, data, processed, created_at)
VALUES (${webhookId || null}, ${eventType}, ${JSON.stringify(eventData)}, ${false}, ${new Date().toISOString()})
RETURNING id
`;
const loggedEventId = logResult[0].id;
console.log('📝 Webhook event logged with ID:', loggedEventId);
console.log(`🔄 Processing: ${eventType} (${eventData.payload_type || 'unknown payload type'})`);
try {
switch (eventType) {
case 'subscription.active':
await handleSubscriptionEvent(sql, payload, 'active');
break;
case 'subscription.cancelled':
await handleSubscriptionEvent(sql, payload, 'cancelled');
break;
case 'subscription.renewed':
console.log('🔄 Subscription renewed - keeping active status and updating billing date');
await handleSubscriptionEvent(sql, payload, 'active');
break;
default:
console.log(`ℹ️ Event ${eventType} logged but not processed (no handler available)`);
}
await sql`
UPDATE webhook_events
SET processed = ${true}, processed_at = ${new Date().toISOString()}
WHERE id = ${loggedEventId}
`;
console.log('✅ Webhook marked as processed');
} catch (processingError) {
console.error('❌ Error processing webhook event:', processingError);
await sql`
UPDATE webhook_events
SET processed = ${false},
error_message = ${processingError instanceof Error ? processingError.message : 'Unknown error'},
processed_at = ${new Date().toISOString()}
WHERE id = ${loggedEventId}
`;
throw processingError;
}
console.log('✅ Webhook processed successfully');
return jsonResponse({
success: true,
event_type: eventType,
event_id: loggedEventId
});
} catch (error) {
console.error('❌ Webhook processing failed:', error);
return jsonResponse({
error: 'Webhook processing failed',
details: error instanceof Error ? error.message : 'Unknown error'
}, 500);
}
}
```
## How It Works
The webhook handler:
1. **Disables body parsing** - To access raw body for signature verification
2. **Verifies the signature** - Ensures the request is from DodoPayments using HMAC-SHA256
3. **Checks for duplicates** - Uses webhook ID to prevent processing the same event twice
4. **Logs the event** - Stores raw webhook in `webhook_events` table for audit trail
5. **Processes the event** - Creates or updates customers and subscriptions in Neon
6. **Handles errors** - Logs failures and marks event as unprocessed for retry
## Testing
**Local development:**
```bash theme={null}
npm start
```
**View logs in Vercel Dashboard:**
1. Select your project
2. Go to **Deployments** → latest deployment
3. Click **Functions** → **Logs**
**Configure in DodoPayments Dashboard:**
1. Go to Developers → Webhooks
2. Add endpoint with your Vercel Functions URL
3. Enable: subscription.active, subscription.cancelled, subscription.renewed
## Common Issues
| Issue | Solution |
| ------------------------- | ------------------------------------------------------------------- |
| Verification failed | Check webhook key is correct from DodoPayments dashboard |
| Database connection error | Verify Neon connection string and use pooled connection |
| Function timeout | Optimize queries; Pro plan has longer timeout (60s) |
| Env vars not available | Set in dashboard or CLI, ensure all environments selected, redeploy |
## Resources
* [Vercel Functions Docs](https://vercel.com/docs/functions)
* [Vercel CLI](https://vercel.com/docs/cli)
* [Neon Docs](https://neon.tech/docs/)
* [Webhook Events Guide](/developer-resources/webhooks/intents/webhook-events-guide)
* [GitHub Repo](https://github.com/dodopayments/cloud-functions/tree/main/vercel)
# Dispute
Source: https://docs.dodopayments.com/developer-resources/webhooks/intents/dispute
The payload sent to your webhook endpoint when a dispute is created or updated.
# License Key
Source: https://docs.dodopayments.com/developer-resources/webhooks/intents/license-key
The payload sent to your webhook endpoint when a license key is created or updated.
# Payment
Source: https://docs.dodopayments.com/developer-resources/webhooks/intents/payment
The payload sent to your webhook endpoint when a payment is created or updated.
# Refund
Source: https://docs.dodopayments.com/developer-resources/webhooks/intents/refund
The payload sent to your webhook endpoint when a refund is created or updated.
# Subscription
Source: https://docs.dodopayments.com/developer-resources/webhooks/intents/subscription
The payload sent to your webhook endpoint when a subscription is created or updated.
# Webhook Event Guide
Source: https://docs.dodopayments.com/developer-resources/webhooks/intents/webhook-events-guide
Understand the different webhook events triggered by Dodo Payments during payment, refund, subscription, dispute, and license key activities
Dodo Payments provides real-time webhook notifications to keep you informed about critical events related to transactions, subscriptions, refunds, disputes, and license key management. Below is a detailed guide to these events, categorized for clarity, along with implementation tips and considerations.
## **Payment Events**
| **Event Name** | **Event Type** | **Description** |
| ---------------------- | -------------------- | ----------------------------------------------------------------------------------- |
| **Payment Succeeded** | `payment.succeeded` | Triggered when a payment is successfully processed. |
| **Payment Failed** | `payment.failed` | Occurs when a payment attempt fails due to errors, declined cards, or other issues. |
| **Payment Processing** | `payment.processing` | Indicates that a payment is currently being processed. |
| **Payment Cancelled** | `payment.cancelled` | Triggered when a payment is cancelled before completion. |
## **Refund Events**
| **Event Name** | **Event Type** | **Description** |
| -------------------- | ------------------ | ---------------------------------------------------------------------------- |
| **Refund Succeeded** | `refund.succeeded` | Triggered when a refund is successfully processed. |
| **Refund Failed** | `refund.failed` | Occurs when a refund attempt fails due to processing errors or other issues. |
## **Dispute Events**
| **Event Name** | **Event Type** | **Description** |
| ---------------------- | -------------------- | ------------------------------------------------------------- |
| **Dispute Opened** | `dispute.opened` | Triggered when a customer initiates a dispute. |
| **Dispute Expired** | `dispute.expired` | Indicates that a dispute expired without resolution. |
| **Dispute Accepted** | `dispute.accepted` | Triggered when a merchant accepts the dispute. |
| **Dispute Cancelled** | `dispute.cancelled` | Occurs when a dispute is cancelled by the customer or system. |
| **Dispute Challenged** | `dispute.challenged` | Triggered when a merchant challenges a dispute. |
| **Dispute Won** | `dispute.won` | Indicates that the merchant has successfully won a dispute. |
| **Dispute Lost** | `dispute.lost` | Occurs when the merchant loses a dispute. |
## **Subscription Events**
| **Event Name** | **Event Type** | **Description** | |
| ----------------------------- | --------------------------- | ----------------------------------------------------------------------------------------- | - |
| **Subscription Active** | `subscription.active` | Indicates that a subscription is now active and recurring charges are scheduled. | |
| **Subscription On Hold** | `subscription.on_hold` | Triggered when a subscription is temporarily put on hold due to failed renewal. | |
| **Subscription Renewed** | `subscription.renewed` | Occurs when a subscription is successfully renewed. | |
| **Subscription Plan Changed** | `subscription.plan_changed` | Triggered when a subscription is upgraded, downgraded, or modified with different addons. | |
| **Subscription Cancelled** | `subscription.cancelled` | Triggered when a subscription is cancelled by the merchant or customer. | |
| **Subscription Failed** | `subscription.failed` | Indicates a failed subscription. This means that we were unable to create a mandate. | |
| **Subscription Expired** | `subscription.expired` | Triggered when a subscription reaches the end of its term and expires. | |
## **License Key Events**
| **Event Name** | **Event Type** | **Description** |
| ----------------------- | --------------------- | ---------------------------------------------------------- |
| **License Key Created** | `license_key.created` | Triggered when a new license key is created for a product. |
These webhook events are designed to provide merchants with critical updates to manage their operations effectively. Implement robust handlers for each relevant event in your webhook integration to ensure smooth and reliable functionality.
# Balances
Source: https://docs.dodopayments.com/features/account-summary-payout-wallet
Track your financial activity across multi-currency wallets with comprehensive transaction ledgers, including payments, refunds, fees, and payouts in USD, GBP, EUR, and INR.
## Introduction
The **Balances** section provides merchants with a comprehensive overview of their financial activity across all currencies. It includes every transaction type - payments, refunds, fees, taxes, and payouts - ensuring full visibility into both earnings and deductions.
## Key Details
## Wallet Structure
Each business account maintains **four native wallets** based on supported payout currencies:
| Wallet | Currency Code |
| ---------- | ------------- |
| USD Wallet | USD |
| GBP Wallet | GBP |
| EUR Wallet | EUR |
| INR Wallet | INR |
### Wallet Logic
* Merchant sales are **credited to the appropriate wallet** based on the **customer's payment currency** and **geographic location**.
* This system optimizes for **payment processor efficiency**, **reduced transaction fees** and **lower currency conversion charges**.
## Balance Ledger
Each wallet has an **independent ledger** recording all related financial activity - payments, refunds, fees, and payouts - in that specific wallet's currency.
A **Consolidated Ledger** combines all wallet data, offering a unified view of the merchant's entire financial history.
### Filters and Navigation
* **Currency Filter:** Allows merchants to view transactions by specific wallet (USD, INR, GBP, EUR).
* **All Transactions View:** Provides an aggregated view across all wallets with unified sorting, filtering, and search capabilities.
### Transaction Types
The following transaction types are recorded in the Balance ledger:
| Transaction Type | Symbol | Description |
| -------------------- | ---------- | ------------------------------------------------- |
| **Payment** | Credit (+) | Successful customer payment |
| **Refund** | Debit (-) | Returned amount to the customer |
| **Transaction Fees** | Debit (-) | Processing fees applied to payments |
| **Refund Fees** | Debit (-) | Fees deducted for processing refunds |
| **Sales Tax** | Debit (-) | Tax amount charged on payments |
| **Sales Tax Refund** | Credit (+) | Refunded portion of sales tax on refunds |
| **Payout** | Debit (-) | Amount transferred to the merchant's bank account |
| **Payout Fees** | Debit (-) | Transaction fees deducted from payouts |
#### Additional Fields
* **Transaction Amount:** Total value associated with the transaction.
* **Transaction Timestamp:** Date and time of transaction completion.
* **Transaction ID:** Unique identifier for tracking and support purposes.
## Total Balance
The **Total Balance** section shows a **consolidated indicative amount** of all wallet balances - **USD, GBP, and EUR** - after conversion to USD.
**Indicative Display:** The displayed amount is **for reference only**. Final payout amounts may vary based on the actual exchange rate at the time of transfer.
## Conclusion
The **Balances** aims to deliver **transparency, accuracy, and flexibility** in managing multi-currency funds.
By maintaining separate native wallets, providing currency-based filters, and enabling indicative consolidated views, Dodo Payments ensures merchants always have a clear understanding of their earnings and payout readiness.
For any discrepancies or clarifications, please reach out to **[support@dodopayments.com](mailto:support@dodopayments.com)** with your **Transaction ID** and issue details.
# Adaptive Currency
Source: https://docs.dodopayments.com/features/adaptive-currency
Display prices in each customer's local currency to reduce friction and improve conversion. Automatic detection, conversion, and same‑currency refunds.
Adaptive Currency lets you show localized prices and accept payments in customers' local currencies. This reduces friction at checkout and builds trust.
## What Is Adaptive Currency?
Adaptive Currency displays product prices in the customer's local currency instead of a fixed global currency (for example, USD). When enabled, checkout defaults to the detected local currency for supported countries, with the option to switch to USD.
## Key Benefits
* **Localized payment experience**: Prices appear in local currency by default.
* **More payment methods**: Unlocks payment methods available for the local currency.
* **Same‑currency refunds**: Refund the customer in the currency they paid with.
## Enable Adaptive Currency
Log in to your Merchant Dashboard and go to Business → Business Settings.
Enable the Adaptive Pricing toggle. You can disable it at any time.
Changes apply only to future transactions.
Save your settings.
Verify a test checkout shows prices in your local currency when supported.
## Customer Experience
1. **Detection**: The system detects the customer's country based on the billing address.
2. **Currency selection**: If the country is supported, prices show in the local currency by default. Customers can switch to USD.
3. **Payment methods**: Localized payment methods appear where applicable.
4. **Checkout**: Payment is completed in the selected currency.
## Conversion and Fees
Adaptive Currency charges the customer in their local currency using the latest exchange rates.
* **You pay**: 0%
* **Your customers pay**: 2–4% based on order value
Tiered fees applied at checkout:
* 4% for orders under \$500
* 3% for 500 to \$1,500
* 2% for over \$1,500
Adaptive Currency fees are borne by the customer, not the merchant.
## Supported Currencies
| Currency Code | Currency Name | Countries | Minimum Amount |
| ------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- |
| AED | UAE Dirham | United Arab Emirates | 2.00 AED |
| ALL | Albanian Lek | Albania | 50.00 ALL |
| AMD | Armenian Dram | Armenia | 500.00 AMD |
| ANG | Netherlands Antillean Guilder | Curaçao, Sint Maarten | 2.00 ANG |
| ARS | Argentine Peso | Argentina | 1000.00 ARS |
| AUD | Australian Dollar | Australia, Nauru | 0.50 AUD |
| AWG | Aruban Florin | Aruba | 2.00 AWG |
| AZN | Azerbaijani Manat | Azerbaijan | 2.00 AZN |
| BAM | Bosnia-Herzegovina Convertible Mark | Bosnia and Herzegovina | 2.00 BAM |
| BDT | Bangladeshi Taka | Bangladesh | 100.00 BDT |
| BMD | Bermudian Dollar | Bermuda | 1.00 BMD |
| BND | Brunei Dollar | Brunei | 1.00 BND |
| BOB | Bolivian Boliviano | Bolivia | 5.00 BOB |
| BRL | Brazilian Real | Brazil | 0.50 BRL |
| BSD | Bahamian Dollar | Bahamas | 1.00 BSD |
| BWP | Botswanan Pula | Botswana | 15.00 BWP |
| BZD | Belize Dollar | Belize | 2.00 BZD |
| CAD | Canadian Dollar | Canada | 0.50 CAD |
| CHF | Swiss Franc | Switzerland, Liechtenstein | 0.50 CHF |
| CLP | Chilean Peso | Chile | 1000.00 CLP |
| CNY | Chinese Yuan | China | 4.00 CNY |
| CRC | Costa Rican Colón | Costa Rica | 500.00 CRC |
| CZK | Czech Koruna | Czech Republic | 15.00 CZK |
| DKK | Danish Krone | Denmark, Greenland | 2.50 DKK |
| DOP | Dominican Peso | Dominican Republic | 100.00 DOP |
| EGP | Egyptian Pound | Egypt | 50.00 EGP |
| ETB | Ethiopian Birr | Ethiopia | 100.00 ETB |
| EUR | Euro | Austria, Belgium, Cyprus, Estonia, Finland, France, Germany, Greece, Ireland, Italy, Latvia, Lithuania, Luxembourg, Malta, Netherlands, Portugal, Slovakia, Slovenia, Spain, Andorra, San Marino, Montenegro | 0.50 EUR |
| FJD | Fijian Dollar | Fiji | 2.00 FJD |
| GBP | British Pound | United Kingdom | 0.30 GBP |
| GEL | Georgian Lari | Georgia | 3.00 GEL |
| GMD | Gambian Dalasi | Gambia | 100.00 GMD |
| GTQ | Guatemalan Quetzal | Guatemala | 10.00 GTQ |
| GYD | Guyanese Dollar | Guyana | 200.00 GYD |
| HKD | Hong Kong Dollar | Hong Kong | 4.00 HKD |
| HNL | Honduran Lempira | Honduras | 25.00 HNL |
| HUF | Hungarian Forint | Hungary | 175.00 HUF |
| IDR | Indonesian Rupiah | Indonesia | 8500.00 IDR |
| ILS | Israeli New Shekel | Israel | 3.00 ILS |
| INR | Indian Rupee | India | 5.00 INR |
| KZT | Kazakhstani Tenge | Kazakhstan | 500.00 KZT |
| LKR | Sri Lankan Rupee | Sri Lanka | 300.00 LKR |
| LRD | Liberian Dollar | Liberia | 200.00 LRD |
| LSL | Lesotho Loti | Lesotho | 20.00 LSL |
| MAD | Moroccan Dirham | Morocco | 10.00 MAD |
| MKD | Macedonian Denar | North Macedonia | 50.00 MKD |
| MOP | Macanese Pataca | Macau | 10.00 MOP |
| MUR | Mauritian Rupee | Mauritius | 50.00 MUR |
| MVR | Maldivian Rufiyaa | Maldives | 15.00 MVR |
| MWK | Malawian Kwacha | Malawi | 2000.00 MWK |
| MXN | Mexican Peso | Mexico | 10.00 MXN |
| MYR | Malaysian Ringgit | Malaysia | 4.00 MYR |
| NGN | Nigerian Naira | Nigeria | 2000.00 NGN |
| NOK | Norwegian Krone | Norway | 3.00 NOK |
| NPR | Nepalese Rupee | Nepal | 150.00 NPR |
| NZD | New Zealand Dollar | New Zealand | 1.00 NZD |
| PEN | Peruvian Sol | Peru | 3.00 PEN |
| PGK | Papua New Guinean Kina | Papua New Guinea | 4.00 PGK |
| PHP | Philippine Peso | Philippines | 50.00 PHP |
| PLN | Polish Zloty | Poland | 2.00 PLN |
| QAR | Qatari Rial | Qatar | 3.00 QAR |
| RON | Romanian Leu | Romania | 2.00 RON |
| RSD | Serbian Dinar | Serbia | 60.00 RSD |
| SAR | Saudi Riyal | Saudi Arabia | 2.00 SAR |
| SBD | Solomon Islands Dollar | Solomon Islands | 10.00 SBD |
| SCR | Seychellois Rupee | Seychelles | 15.00 SCR |
| SEK | Swedish Krona | Sweden | 3.00 SEK |
| SGD | Singapore Dollar | Singapore | 0.50 SGD |
| SRD | Surinamese Dollar | Suriname | 50.00 SRD |
| SZL | Swazi Lilangeni | Eswatini | 20.00 SZL |
| THB | Thai Baht | Thailand | 25.00 THB |
| TOP | Tongan Paʻanga | Tonga | 2.00 TOP |
| TRY | Turkish Lira | Turkey | 20.00 TRY |
| TWD | New Taiwan Dollar | Taiwan | 20.00 TWD |
| TZS | Tanzanian Shilling | Tanzania | 3000.00 TZS |
| UYU | Uruguayan Peso | Uruguay | 50.00 UYU |
| WST | Samoan Tala | Samoa | 2.00 WST |
| ZAR | South African Rand | South Africa | 20.00 ZAR |
| ZMW | Zambian Kwacha | Zambia | 30.00 ZMW |
## Refunds and Adjustments
Dodo Payments issues refunds in the currency the customer originally paid, using the latest exchange rate. The global USD amount remains fixed on your dashboard, invoices, and in the refund. This means the customer may receive more or less than the original local‑currency amount depending on FX changes.
Adaptive Currency fees which are generally the FX fees are not refunded.
**Example refund**
1. You sell a product for 100 USD with Adaptive Currency enabled.
2. A Canadian customer sees 137 CAD at an exchange rate of 1.37 CAD per 1 USD and completes the purchase.
3. We process the payment, converting 137 CAD to 100 USD for your settlement.
4. Later, the exchange rate changes to 1.40 CAD per 1 USD and you issue a full refund.
5. We deduct 100 USD and refund the customer 140 CAD.
## Invoices and Taxation
* Invoices show only the settlement currency amount.
* Taxes and platform fees are calculated on the settlement currency amount.
* Example: a \$10 sale converted to 36 AED still reflects as \$10 in the Dashboard and invoices.
All amounts are rounded according to Dodo Payments' internal rounding logic.
## Integration Examples
**Checkout Sessions with billing currency**
Pass `billing_currency` to explicitly set the billing currency for the session.
When Adaptive Pricing is disabled, `billing_currency` is ignored.
```typescript theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{
product_id: 'prod_one_time_or_subscription',
quantity: 1
}
],
billing_currency: 'AED',
return_url: 'https://example.com/return'
});
```
Configure Billing Currency using our Checkout Session API.
# Add-ons for Subscriptions
Source: https://docs.dodopayments.com/features/addons
Enhance your subscription products with flexible add-ons for seat-based billing, feature upgrades, and creative pricing models
Add-ons are additional products that can be attached to your main subscription products, enabling flexible pricing models and enhanced customer experiences. Whether you need seat-based billing, feature upgrades, or custom pricing structures, add-ons give you the power to create sophisticated subscription offerings.
Create add-ons for additional team seats, user licenses, or capacity upgrades with per-seat pricing.
Extend usage limits, API calls, or data allowances with flexible add-on pricing.
## What Are Add-ons?
Add-ons are supplementary products that customers can purchase alongside their main subscription. They're perfect for:
* **Seat-based billing**: Additional team members, user licenses, or concurrent users
* **Feature upgrades**: Premium features, advanced analytics, or priority support
* **Usage extensions**: Extra storage, API calls, or bandwidth allowances
* **Service add-ons**: Professional services, training, or consultation hours
## Key Benefits
* **Flexible Pricing Models**: Offer base plans with optional add-ons to create sophisticated pricing structures. You can address diverse customer segments with upgrades that grow as your customers' needs change.
* **Revenue Optimization**: Boost your average revenue per user (ARPU) by presenting relevant add-ons. This enables natural upsell opportunities as customers add features over time.
* **Simplified Management**: Manage all pricing components from one dashboard. Add-ons are automatically included in both checkout sessions and subscription management.
* **Customer Choice**: Allow customers to customize their subscriptions by selecting only the add-ons they need, which enhances satisfaction and reduces churn.
## Creating Add-ons
Add-ons are created as separate products in your Dodo Payments dashboard and then attached to your main subscription products. This separation allows you to:
* Reuse add-ons across multiple subscription products
* Manage pricing independently
* Track add-on performance separately
* Update add-ons without affecting base subscriptions
### Add-on Configuration
When creating add-ons, you can configure:
* **Pricing**: Set one-time or recurring pricing for the add-on
* **Billing cycle**: Match your subscription billing or use different cycles
* **Quantity limits**: Set minimum and maximum quantities per customer
* **Availability**: Control which subscription products can use the add-on
* **Tax settings**: Configure appropriate tax categories
### Getting Started
Ready to implement add-ons in your subscription business? Here's how to get started:
Identify the additional features, services, or capacity that would benefit your customers as add-ons.
Consider:
* What do customers frequently request?
* What features could be monetized separately?
* What would create natural upgrade paths?
Use the Dodo Payments dashboard or API to create your first add-on product.
Follow our step-by-step guide to create add-ons in the dashboard.
Connect your add-ons to the appropriate subscription products where they should be available.
Create test checkout sessions with different add-on combinations to ensure everything works correctly.
Track add-on adoption rates and revenue impact to optimize your pricing strategy.
## API Management
Dodo Payments provides a comprehensive API for managing add-ons programmatically:
Use the `POST /addons` endpoint to create new add-ons with custom pricing, descriptions, and configuration options.
View the complete API documentation for creating add-ons.
Modify existing add-ons using the `PATCH /addons/{id}` endpoint to update pricing, descriptions, or availability.
Learn how to update add-on details programmatically.
Use `GET /addons` to list all add-ons or `GET /addons/{id}` to retrieve specific add-on details.
Access the complete listing and retrieval API documentation.
Update add-on images using the `PUT /addons/{id}/images` endpoint for better product presentation.
Learn how to manage add-on images via API.
## Common Use Cases
* **Seat-Based Billing**: Additional team members, user licenses, or concurrent users
* **Feature Upgrades**: Premium features, advanced analytics, or priority support
* **Usage Extensions**: Extra storage, API calls, or bandwidth allowances
* **Service Add-ons**: Professional services, training, or consultation hours
## Integration Examples
### Checkout Sessions with Add-ons
When creating checkout sessions, you can include add-ons with custom quantities:
```typescript theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{
product_id: 'your_subscription_id',
quantity: 1,
addons: [
{
addon_id: 'your_addon_id',
quantity: 3 // 3 additional seats
}
]
}
],
// ... other checkout options
});
```
### Plan Changes with Add-ons
Modify existing subscriptions to add, remove, or update add-ons:
```typescript theme={null}
// Add add-ons to existing subscription
await client.subscriptions.changePlan('sub_123', {
product_id: 'prod_new',
quantity: 1,
proration_billing_mode: 'difference_immediately',
addons: [
{ addon_id: 'addon_123', quantity: 2 }
]
});
// Remove all existing add-ons
await client.subscriptions.changePlan('sub_123', {
product_id: 'prod_new',
quantity: 1,
proration_billing_mode: 'difference_immediately',
addons: [] // Empty array removes all existing add-ons
});
```
### Dynamic Pricing
Calculate total costs dynamically based on add-on selections:
```typescript theme={null}
function calculateTotalCost(basePrice: number, addons: AddonSelection[]) {
const addonTotal = addons.reduce((sum, addon) =>
sum + (addon.price * addon.quantity), 0
);
return basePrice + addonTotal;
}
```
## Best Practices
* **Start simple**: Launch with 2-3 core add-ons and expand options based on customer feedback and usage.
* **Maintain pricing clarity**: Clearly communicate add-on pricing and value, so customers understand what they're getting for the extra cost.
* **Test thoroughly**: Validate add-on combinations to ensure pricing calculations remain accurate and checkout flows function smoothly.
### Design Considerations
* **Clear Value Proposition**: Each add-on should have a clear benefit that customers can easily understand
* **Logical Grouping**: Group related add-ons together in your checkout flow
* **Flexible Quantities**: Allow customers to adjust quantities of add-ons as needed
* **Transparent Pricing**: Show total costs clearly throughout the checkout process
Add-ons are a powerful way to create flexible, scalable pricing models that grow with your customers. Start with simple use cases and expand as you learn what works best for your business and customers.
# Affiliates
Source: https://docs.dodopayments.com/features/affiliates
Launch and manage your affiliate program while processing transactions through Dodo Payments.
## Introduction
This guide walks you through how to track affiliate referrals, handle commission events, and grow your revenue with trusted affiliate partnerships via Dodo Payments.
## Key Features
No spreadsheets or manual tracking. Referrals are automatically attributed and logged when a purchase is made.
Both you and your affiliates can monitor performance and leads from dedicated dashboards.
Use tracking scripts, metadata fields, and webhook events to capture referral data across signups and transactions.
All sensitive credentials and webhook data are securely exchanged and stored using best-in-class encryption.
Launch your affiliate program in minutes—no code changes required beyond copy-pasting script snippets and API tokens.
Plug affiliate tracking directly into your checkout flows using our integration with Affonso, a leading affiliate marketing platform.
## Integration Steps
We have partnered with Affonso, a leading affiliate management platform to power your sales. Follow the steps below to connect your Dodo Payments account with Affonso and begin tracking affiliate-driven sales.
To get started, connect your Dodo Payments account by adding your API key.
### How to Get Your Dodo Payments API Key
* Log in to your **Dodo Payments Dashboard**
* Go to the **Developer** section in the left menu
* Click on **API Keys**
* Select **Add API Key**
* Give it a name (e.g. "Affonso Integration")
* Click **Create** and **copy** your API key
### Add Your API Key to Affonso
* Open your **Affonso Dashboard via** [affonso.io](https://affonso.io/)
* Go to **Program Details > Connect**
* Click the **Dodo Payments integration** card
* Paste your Dodo Payments API key
* Click **Connect**
Your API key will be securely encrypted by Affonso.
Once connected, you'll receive a unique webhook URL from Affonso. This enables payment tracking and affiliate commission triggering.
### Add Webhook URL in Dodo Payments
* In your Dodo Payments Dashboard, go to **Developer → Webhooks**
* Click **Add Webhook**
* Paste your **Affonso webhook URL** (from your program settings)
* Save the webhook
### Add Webhook Secret in Affonso
* After saving the webhook in Dodo Payments, **copy the signing key** provided
* Paste it back into Affonso under the **Dodo Payments webhook settings**
This allows Affonso to securely verify incoming events from Dodo Payments.
Place the Affonso pixel on your site to start tracking affiliate visits and conversions.
```html theme={null}
```
This script:
* Tracks affiliate visits
* Sets a cookie with the referral ID (`affonso_referral`)
* Enables signup and payment tracking
Place this on all domains where users visit or convert (e.g. marketing site, app).
To gain funnel visibility and optimize affiliate performance, track user signups as leads.
```js theme={null}
// After successful registration
window.Affonso.signup(userEmail);
```
This will:
* Log a new referral as a **LEAD** if the user came via an affiliate link
* Display it in your dashboard and the affiliate's stats
Best practice: Trigger this after successful registration or double opt-in (DOI).
To attribute commissions correctly, pass the referral ID from Affonso to Dodo Payments during checkout.
### Payments Integration
```js theme={null}
import DodoPayments from 'dodopayments';
const referralId = window.affonso_referral;
const client = new DodoPayments({
bearerToken: process.env['DODO_PAYMENTS_API_KEY'],
});
async function main() {
const payment = await client.payments.create({
payment_link: true,
billing: { city: 'city', country: 'AF', state: 'state', street: 'street', zipcode: 0 },
customer: { email: 'email@email.com', name: 'name' },
product_cart: [{ product_id: 'product_id', quantity: 0 }],
metadata: {
affonso_referral: referralId,
},
});
console.log(payment.payment_id);
}
main();
```
### Subscription Integration
```js theme={null}
import DodoPayments from 'dodopayments';
const referralId = window.affonso_referral;
const client = new DodoPayments({
bearerToken: process.env['DODO_PAYMENTS_API_KEY'],
});
async function main() {
const subscription = await client.subscriptions.create({
billing: { city: 'city', country: 'IN', state: 'state', street: 'street', zipcode: 89789 },
customer: { customer_id: 'customer_id' },
product_id: 'product_id',
payment_link: true,
return_url: 'https://example.com/success',
quantity: 1,
metadata: {
affonso_referral: referralId,
},
});
console.log(subscription.subscription_id);
}
main();
```
### Payment Links
```js theme={null}
// Original Dodo Payments Link
const originalLink = 'https://checkout.dodopayments.com/buy/';
// Append referral ID as reference_id
const linkWithReferral = `${originalLink}?metadata_affonso_referral=${window.affonso_referral}`;
```
For more details, see the [Dodo Payments API documentation](https://docs.dodopayments.com/api-reference/introduction)
Before launching publicly, test your setup with live affiliate links and test transactions.
**Testing Checklist:**
* ✅ Create an affiliate and generate a tracking link
* ✅ Visit your website via the affiliate link
* ✅ Ensure the Affonso script loads and the `affonso_referral` cookie is set
* ✅ Complete a signup (optional)
* ✅ Complete a payment via Dodo Payments
* ✅ Confirm tracking and attribution in your Affonso dashboard
## Need Help?
If you face any issues or need support during integration:
* Chat with us via **live chat** on [affonso.io](https://affonso.io/)
* Email us at **[support@affonso.io](mailto:support@affonso.io)**
For Dodo Payments-specific API queries, refer to docs.dodopayments.com or contact our support team.
# Analytics and Reporting
Source: https://docs.dodopayments.com/features/analytics-and-reporting
Gain actionable business insights with Dodo Payments' Analytics & Reporting dashboard. Learn to track revenue, customer trends, and transaction health, and download detailed reports.
## Introduction
Dodo Payments' **Analytics & Reporting** dashboard empowers you to monitor your business performance, understand customer behavior, and make data-driven decisions. Access real-time metrics, visualize trends, and export detailed reports—all from a single, intuitive interface.
Track total revenue, growth trends, and top-performing products or countries.
Analyze customer acquisition, retention, and average revenue per user (ARPU).
Monitor payment and refund success rates, and identify failure reasons.
Download payments, refunds, and payouts reports for custom date ranges.
## Dashboard Overview
The Analytics dashboard provides comprehensive insights into three key areas of your business performance. Each section offers detailed metrics and visualizations to help you understand your revenue patterns, customer behavior, and transaction health.
## Revenue Analytics
Track your income and payment activity to understand revenue trends and identify your top-performing markets.
* **Total Revenue:** Instantly see your all-time revenue and recent trends.
* **Revenue Trend:** The 30-day chart helps you spot spikes or dips in income.
* **Growth Rate:** Track monthly revenue growth to measure business momentum.
* **Geographic Insights:** Visualize your top 5 revenue-generating countries.
Use these insights to focus your marketing and product efforts on high-performing regions and periods of growth.
## Revenue Breakdown Analytics
Track your recurring revenue metrics with detailed breakdowns:
* **Projected ARR**: Annual Recurring Revenue projections with trend analysis
* **MRR Breakdown and Growth Rate**: Monthly Recurring Revenue analysis with month-over-month growth metrics
* **New MRR Chart**: Visual representation of new customer revenue acquisition
* **Expansion MRR Chart**: Tracking revenue growth from existing customer upgrades and expansions
* **Churn MRR Chart**: Monitoring revenue lost from customer cancellations and downgrades
* **Net New MRR Chart**: Net revenue impact combining new, expansion, and churn MRR
## Retention Analytics
Monitor customer retention and churn patterns:
* **Customer Churn Rate**: Percentage of customers who cancel their subscriptions
* **Revenue Churn Rate**: Percentage of revenue lost from cancellations and downgrades
* **Churn Rate Trends**: Historical tracking of both customer and revenue churn patterns over time
* **User Retention Matrix**: Cohort analysis showing customer retention rates across different time periods
## Customer Analytics
Analyze customer acquisition, retention, and spending patterns to optimize your customer strategy.
* **ARPU (Average Revenue Per User):** Understand how much each customer contributes on average.
* **Active Customers:** See how many customers are engaging with your business.
* **Customer Split:** Instantly view the ratio of new vs. returning customers.
* **Top Customers:** Identify your highest spenders for targeted engagement.
Monitor ARPU and customer split to refine your retention and acquisition strategies.
## Transaction Success & Failure Analytics
Monitor payment and refund success rates to identify and resolve transaction issues quickly.
* **Payment Success Rate:** Track the percentage of successful payment attempts.
* **Refund Success Rate:** Monitor how many refunds are processed successfully.
* **Failure Reasons:** Quickly identify common issues (e.g., expired cards, unsupported methods).
Get all possible transaction failure codes and it's reasons.
* **Payments Breakdown:** Visualize the distribution of successful and failed transactions.
If you notice a drop in success rates or a spike in failures, review the failure reasons and address them promptly to minimize revenue loss.
## Filtering & Customization
You can filter analytics data by:
* **Product Type** (one-time vs. subscription)
* **Date Range** (customizable)
* **Payment Method**
Use filters to drill down into specific products, time periods, or payment methods for targeted insights.
## Reports
We've completely revamped the reports section with more comprehensive and detailed reporting capabilities. Navigate to **Business > Reports** to access the new reporting suite.
Available reports include:
* **Payment Report**: Detailed record of all processed payments with transaction status tracking
* **Refund Report**: Comprehensive log of all refund activities with detailed refund information
* **Payout Report**: Summary of payouts made to your account
* **Product-wise Sales Report**: Sales performance insights with revenue and business growth metrics
* **Payment Failure Report**: Summary of failed payment attempts with failure reasons
* **Customer Report**: Payment summary organized by customer
* **Disputes Report**: Payment disputes summary with resolution status
* **Account Summary Report**: Complete account overview and summary
## Enhanced Payout Reporting
We've added detailed payout reports that provide clear visibility into fees and transaction details for each payout you receive from Dodo Payments. Access these enhanced reports in **Business > Payouts**.
## Downloading & Exporting Reports
You can export detailed reports directly from the dashboard:
Navigate to the Reports area in your dashboard.
Choose the report you need (Payments, Refunds, Payouts) and specify the date range.
Click Download to export your report in CSV format.
Your report will be saved to your device.
## Best Practices & Troubleshooting
* Regularly review your analytics to spot trends and anomalies early.
* Use filters to compare performance across products, time periods, or geographies.
* If data appears incorrect or missing, check your filters and date range settings first.
If you notice persistent discrepancies or missing data, contact [support@dodopayments.com](mailto:support@dodopayments.com) for assistance.
## Next Steps & Related Resources
Understand your earnings, deductions, and payout cycles.
Track and manage all payment transactions.
Initiate and monitor refunds, and export refund reports.
Manage and respond to payment disputes.
# Selling Digital Goods on iOS
Source: https://docs.dodopayments.com/features/appstore-digital-goods
A comprehensive guide to selling digital goods and services on iOS apps using Dodo Payments
## Introduction
Dodo Payments empowers developers to sell digital goods and services in iOS apps, handling complex aspects like tax compliance, currency conversion, and payouts. This comprehensive guide details how to integrate Dodo Payments into your iOS app, specifically for SaaS tools, content subscriptions, and digital utilities.
## Overview
Dodo Payments serves as your **Merchant of Record (MoR)**, managing critical aspects of your digital business:
* Tax collection and remittance (VAT, GST, and other regional taxes)
* Global payments as per policies and local payment methods
* Currency conversion and foreign exchange
* Chargebacks and fraud prevention
* End-customer invoicing and receipts
* Compliance with regional regulations
* A unified API for web and mobile platforms
* Support for in-app checkouts (UPI, cards, wallets, BNPL)
* Global payout support (Payoneer, Wise, local bank transfers)
* Analytics and reporting dashboard
* Secure payment processing
## Use Cases
* Premium content or feature access
* Recurring billing with flexible options, Free trials, Proration, or Upgrades and downgrades
* Pay-per-course access
* Bundled content packages
* Lifetime or renewable licenses
* Progress tracking integration
* One-time purchases (PDFs, music, tools)
* Digital asset delivery
* License key management
* Software-as-a-Service subscriptions
* Usage-based billing
* Team and enterprise plans
## Integration Flow
You can integrate Dodo Payments into your app using our hosted checkout or in-app browser solution.
```mermaid theme={null}
flowchart LR
A[Mobile App] -->|Create Payment Link| B(Dodo API)
B -->|Checkout URL| A
A -->|Open in WebView| C[Checkout Page]
C -->|Redirects / Webhooks| D[Your Server]
D -->|Grant Access| A
```
### Integration Steps
The process begins with the mobile app creating a payment link by interacting with the Dodo API.
The Dodo API responds by providing a checkout URL back to the mobile app.
The mobile app then opens this checkout URL within a WebView, leading the user to the checkout page.
Upon completion of the checkout process, the checkout page communicates with your server through redirects or webhooks.
Finally, your server grants access to the purchased content or service, completing the transaction cycle back in the mobile app.
For a complete developer walkthrough, explore our Mobile Integration Guide.
## Regional Availability
Dodo Payments enables alternative in-app purchase flows only in App Store regions where Apple explicitly allows external payments, or where a regulator or court order mandates it.
### Supported Regions
Supported to the extent permitted by current court orders and Apple's updated guidelines.
* Available under specific court-mandated provisions
* Subject to Apple's compliance with legal requirements
* Must follow Apple's implementation guidelines
Supported via Apple's EU Alternative Terms and External Purchase Entitlement.
* Enabled through Apple's EU Alternative Terms
* Requires External Purchase Entitlement approval
* Must comply with EU Digital Markets Act requirements
Supported through the StoreKit External Purchase Entitlement for Korea-only binaries.
* Available via StoreKit External Purchase Entitlement
* Requires Korea-specific app binary
* Must comply with Korean telecommunications law
Always review and comply with Apple's region-specific entitlements and App Store Connect requirements before enabling Dodo Payments for any storefront. Using alternative payment flows in unsupported regions may result in app rejection or removal.
For some business models - such as services or certain categories of content - Apple may not require the use of in-app purchase (IAP) at all. Dodo Payments supports these models as well. Always verify your app's classification and Apple's latest guidelines to determine if IAP is mandatory for your use case.
### Learn More
For a detailed breakdown of global policies, legal precedents, and strategic approaches to bypassing App Store fees, see our comprehensive guide:
Learn where and how you can legally implement alternative payment flows, with up-to-date regional guidance and compliance tips.
# Atlas Demo Product
Source: https://docs.dodopayments.com/features/atlas-demo-website
Explore Atlas, a fully functional AI art generation demo application showcasing usage-based billing, subscriptions, and one-time payments with Dodo Payments checkout integration.
Atlas is a fully functional demo application by Dodo Payments that showcases an AI art generation platform. It demonstrates all three billing models—**usage-based billing**, **one-time payments**, and **subscriptions**—with integrated checkout flows.
**Live Demo**: Visit [atlas.dodopayments.com](https://atlas.dodopayments.com/) to explore the platform and test all billing flows.
## Overview
Atlas simulates a real-world AI art generation platform where users create images from text prompts. The demo highlights three distinct purchase flows, each demonstrating different billing models and checkout experiences:
* **Usage-Based Billing**: Pay per image generated (\$0.75 per image)
* **Subscription**: Unlimited Pro plan (\$20/month)
* **One-Time Purchase**: Credit packs (\$7 for 10 credits)
After completing any purchase, users are redirected to the **Atlas Studio dashboard** where they can generate artwork and track their usage in real-time.
## Purchase Flows
Atlas demonstrates three separate billing flows. Each flow follows a similar pattern but showcases different checkout experiences and billing models.
Choose the **pay-per-image** option (\$0.75 per image) for AI art generation. This demonstrates usage-based billing where customers pay only for what they consume.
Enter a text prompt and generate an image. The system automatically tracks usage for billing purposes.
The checkout process includes:
* **Branded checkout page**: Dodo Payments checkout page displays your merchant brand name and logo
* **Customer information**: Enter user details and billing address
* **Tax calculation**: Taxes are automatically calculated after address confirmation
* **Payment**: Enter test card details to complete the transaction
Use test card `4242 4242 4242 4242` with any future expiry date and CVC to simulate successful transactions.
After payment, Atlas displays a confirmation screen with usage details and remaining credits.
Users are redirected back to the **Atlas Studio dashboard** where they can continue generating artwork and monitor their usage tracking.
Choose the **Unlimited Pro** subscription plan (\$20/month) for unlimited AI art generation.
Enter a prompt and generate an image to test the platform before subscribing.
The subscription checkout includes:
* **Branded checkout page**: Merchant branding is visible throughout the checkout process
* **Customer information**: Enter user details and billing address
* **Tax calculation**: Automatic tax calculation based on billing address
* **Payment**: Enter test card details to activate the subscription
Subscriptions automatically renew monthly. Users can manage their subscription from the dashboard after purchase.
Post-payment, Atlas displays a confirmation screen with subscription activation details and next billing date.
Users are redirected to the **Atlas Studio dashboard** with unlimited access to generate artwork.
Choose a **Credit Pack** (\$7 for 10 credits) for AI art generation. This demonstrates one-time purchase transactions.
Enter a prompt and generate an image to preview the platform before purchasing credits.
The one-time purchase checkout includes:
* **Branded checkout page**: Dodo Payments checkout with merchant branding
* **Customer information**: Enter user details and billing address
* **Tax calculation**: Automatic tax calculation after address confirmation
* **Payment**: Enter test card details to complete the purchase
Post-payment, Atlas displays a confirmation screen with a "thank you" message and purchased credit details.
Users are redirected to the **Atlas Studio dashboard** where their purchased credits are immediately available for generating artwork.
## Post-Purchase Dashboard Experience
The Atlas Studio dashboard demonstrates how to implement billing-aware features and entitlement tracking after checkout completion.
### Dashboard Features
* **Immediate access**: Users can start generating artwork immediately after purchase
* **Usage tracking**: Real-time display of remaining credits, active subscriptions, and generation history
* **Billing management**: View purchase history, subscription status, and upcoming renewals
* **Seamless integration**: Showcases how to redirect users back to your application with proper entitlement tracking
The dashboard experience demonstrates best practices for post-purchase user flows, including entitlement verification and usage monitoring.
## Technical Implementation
Atlas is built using the [Dodo Payments TypeScript SDK](/developer-resources/sdks/typescript), demonstrating how to integrate billing flows with just a few lines of code.
Learn how to integrate Dodo Payments checkout flows into your application.
Explore the TypeScript SDK documentation and API reference.
## Open Source
Atlas is an open-source project. Explore the codebase to see how billing flows are implemented in a real application.
Browse the Atlas source code and learn from the implementation.
# B2B Payments
Source: https://docs.dodopayments.com/features/b2b-payments
The B2B Payments feature ensures that merchants processing B2B transactions have their customer’s Tax IDs verified during checkout, ensuring accurate tax application based on the customer's country and applicable regulations. This validation helps determine whether input tax collection applies or if the transaction falls under a reverse charge mechanism.
## **Tax ID Validation for B2B Transactions**
* If a **valid Tax ID** is provided during checkout, the invoice will include:
* The **customer’s Tax ID**.
* Tax deductions will be determined based on the applicable regulations for the customer’s country.
* The invoice will include **Dodo Payments’ tax details**, along with the tax applied to the transaction.
## **Tax Treatment for B2C Transactions**
* Transactions where a valid Tax ID is **not provided** will be treated as **B2C transactions**, and tax will be deducted as per the applicable tax regulations in the customer’s country.
* The invoice will include **Dodo Payments’ tax details**, along with the tax applied to the transaction.
## Reverse Charge Mechanism
Generally, the supplier of goods or services is liable to pay VAT/Service Tax/GST.
However, in specified cases like imports and other notified supplies, the liability is on the recipient (customer) under the reverse charge mechanism. Reverse Charge means the liability to pay tax is on the recipient of supply of goods or services instead of the supplier of such goods or services in respect of notified categories of supply.
## **Reverse Charge Applicability**
* In certain cases, where applicable by law, tax will **not be deducted** at checkout and the transaction will be subject to a **reverse charge mechanism**.
* The invoice will clearly indicate when a reverse charge applies, as per regulatory requirements.
# Bypassing App Store & Play Store Fees: A Strategic and Legal Playbook
Source: https://docs.dodopayments.com/features/bypassing-app-store-fees
Learn how to legally bypass Apple and Google's 30% app store fees using Merchant of Record solutions and compliant payment strategies
## The Platform Tollbooth Problem
For over a decade, Apple and Google have operated their app stores not just as marketplaces, but as powerful toll booths. Any developer or startup offering digital goods within an app was forced to route payments through their respective billing systems: Apple's In-App Purchase (IAP) and Google's Play Billing-while forfeiting up to 30% of revenue.
If that sounds like a steep cut, it's because it absolutely is.
This "store tax" drew widespread criticism, particularly from developers from large apps like Spotify, Epic Games, and Netflix, who found the commissions not only excessive but anti-competitive. Apps couldn't even inform users about cheaper options outside the app. These policies-especially Apple's "anti-steering" rules-made app stores de facto monopolies for digital distribution.
The 30% app store commission represents a significant revenue drain for digital businesses, especially for subscription-based services and SaaS applications.
## The Epic Games Catalyst
Epic Games' CEO, Tim Sweeney, initiated Project Liberty as a strategic legal campaign to challenge the monopolistic practices of Apple and Google's app stores, particularly their 30% commission fees on in-app purchases. The project aimed to disrupt these platforms' control over app distribution and payments, advocating for a more open environment for developers.
Tim Sweeney knew Apple would contest this and laid a bait. Epic updated Fortnite with a hidden direct-pay option, bypassing Apple's IAP. Apple fell for the bait, swiftly banned Fortnite, and Epic sued.
The Epic Games v. Apple legal battle cracked open the conversation globally.
## The Epic v. Apple Verdict
In 2021, the U.S. District Judge ruled in a landmark decision:
* **Apple did not violate antitrust laws outright**, meaning it could continue to control app distribution on iOS
* **However, Apple's anti-steering policies were deemed anti-competitive**. The court issued a permanent injunction-Apple must allow developers to direct users to external payment methods
After delays and appeals, Apple finally updated its App Store guidelines in May 2025 to allow external purchase links in U.S. apps.
This U.S. ruling spurred similar regulatory pressure and legal action across South Korea, Japan, the EU, and other regions. Combined with the EU's Digital Markets Act (DMA)-the tide turned decisively in favor of developers.
## Global Overview: Where Developers Can Legally Bypass Fees
| Region | Apple Policy (2025) | Google Policy (2025) |
| --------------- | ------------------------------------- | ------------------------------- |
| **USA** | External links allowed (no Apple fee) | UCB allowed (reduced fee) |
| **EU (EEA)** | Alt payments/links allowed (10–17%) | UCB allowed for non-games |
| **South Korea** | Apple-approved PSPs (26% fee) | UCB mandated (26% fee) |
| **Netherlands** | Dating apps only | UCB allowed |
| **Japan** | Reader apps can link out | UCB allowed |
| **India** | No change | UCB allowed (4% fee reduction) |
| **Other** | No change | UCB allowed in select countries |
| **China** | No external billing allowed | Google Play not relevant |
UCB stands for User Choice Billing, Google's alternative billing system that allows developers to use third-party payment processors.
## Pre-2025 Loopholes: How Apps Circumvented App Store Rules
Before regulations loosened their grip, some apps found "creative" ways to get around the 30% cut. These hacks were rarely compliant but undeniably clever.
### Reader App Loophole
One common tactic was the "reader app loophole," where apps like Kindle or Netflix claimed they were merely displaying previously purchased content. Apple couldn't fault apps for letting users access pre-purchased content. So as long as the app didn't explicitly link to external payment or prompt an in-app purchase, it was allowed to operate.
### Hidden Payment Redirections
Others experimented with code obfuscation, embedding hidden payment redirections. A few even used email notifications post-download to nudge users to pay outside the app.
Spotify was one such company that implemented this strategy. If you're in India and use Spotify, you've probably noticed that you can't buy the Premium plan inside the app. You have to pay on the web. This is why.
All of this was risky, brittle, and usually led to takedowns once discovered. But it underscored one thing: developers were desperate to reclaim control over their margins and user data.
## The Payment Gateway Trap: What Developers Often Miss
At first glance, payment gateways (PGs) like Stripe or Razorpay seem like the obvious solution. But for in-app purchases, it's not that simple.
If you're on iOS outside of allowed regions (like India or China), plugging in a PG directly into the app violates Apple's policies, risking an app takedown.
Even where it is legal, PGs come with limitations: they don't solve for tax compliance across geographies, invoicing, refund management, or regulatory filings.
That's where Merchant-of-Record (MoR) platforms like Dodo Payments come in. We handle the full stack: payments, taxes, documentation, customer support, and legal accountability.
It's not just about collecting money. It's about doing it cleanly, globally, and without risking your App Store listing.
## Two Legal Workarounds for Developers
### 1. App-to-Web Redirect (Link Out Flow)
**What it is:** The app links users to a web-based checkout (hosted by Dodo Payments or the developer). Payment is completed outside the app, then access is granted in-app.
**Legal Status:**
* **iOS:** Legal in US, EU, South Korea, Netherlands (only dating apps), Japan (only reader apps)
* **Android:** Legal in all UCB-supported markets
**Advantages:**
* Simple to implement
* Fully compliant
* No app-side PCI burden
**Drawbacks:**
* Slight friction - user leaves app to pay
### 2. In-App SDK Integration (Third-Party SDK)
**What it is:** Embed a payment UI - such as Dodo's React Native SDK - directly in your app to enable seamless checkout without requiring users to leave the app.
**Legal Status:**
* **iOS:** Legal only in EU (under DMA terms); elsewhere it's still a violation
* **Android:** Legal in UCB markets, if proper UX and reporting are followed
**Advantages:**
* Smooth UX
* User stays in app
* Can match IAP experience
**Drawbacks:**
* Integration complexity
* Compliance risk outside allowed regions
## Developer Strategy: Combine Legal Compliance with Revenue Efficiency
An effective global rollout strategy could look like this:
Use App-to-Web (no Apple cut)
Use In-App SDK or App-to-Web (10–17% Apple cut)
Use In-App SDK with Google-compliant UX
Use Apple IAP (or omit in-app purchases)
Use Google Play billing or distribute via alt app stores
## The Biggest Opportunity
Among all the regulatory changes, the U.S. App Store presents the single largest opportunity for developers. With the 2025 guideline update following the Epic v. Apple ruling, developers can now legally include external links in their iOS apps that direct users to a web-based checkout.
This means: **no mandatory Apple IAP, no 30% fee, and full control over customer relationships and revenue.**
Given that the U.S. is the largest app revenue market globally, this change alone can significantly improve margins for any digital product business from subscriptions and SaaS tools to media and learning apps. For many, it's a 30% revenue unlock in their most lucrative geography.
The U.S. market represents the largest opportunity for revenue recovery, with developers able to bypass the full 30% Apple commission through compliant external payment links.
## How Dodo Payments Enables This
Dodo Payments acts as a Merchant of Record, handling:
* **Global tax compliance** (VAT/GST)
* **Payment processing** (cards, UPI, wallets)
* **Invoicing, refunds, fraud prevention**
* **SDK & hosted checkout integration**
This simplifies compliance and operational load while offering fee-saving alternatives that are 100% legal in supported regions.
Begin your integration with Dodo Payments
Learn how to integrate payments in your mobile app
## Conclusion: A Developer-Friendly Future Is Here
For years, the 30% store tax was a take-it-or-leave-it ultimatum. Thanks to landmark cases like Epic v. Apple, regulatory pushback, and public pressure, that model is no longer universally enforceable.
In 2025, developers finally have choices.
By combining compliant workarounds like App-to-Web flows or SDK integrations with services like Merchant-of-Record platforms like Dodo Payments, it's now practically and legally feasible to retain more revenue and control.
The app stores haven't opened the gates willingly but the gates are opening. And for developers, it's time to walk through.
The future of app monetization is here-one where developers can legally bypass excessive platform fees while maintaining compliance and user experience.
# Customer Credits
Source: https://docs.dodopayments.com/features/customer-credit
Learn how to manage customer credit balances, issue service credits, and handle billing adjustments seamlessly with Dodo Payments.
Access the complete API documentation for managing customer credits and test credit operations interactively.
## What is Customer Credit?
Customer Credits provide a flexible way to manage customer balances and enhance billing relationships. With customer credits, you can:
* **Reward loyal customers** with account credits for exceptional service or loyalty programs
* **Issue service credits** to compensate for service interruptions or billing adjustments
* **Handle manual adjustments** without disrupting existing billing cycles
* **Provide refunds** that customers can use for future purchases
Customer credits create a positive balance in a customer's account that automatically applies to future subscription payments, reducing the amount charged to their payment method.
## How Customer Credits Work
Customer credits operate on a simple principle: available credit is automatically applied to subscription payments before charging the customer's payment method.
### Automatic Credit Application
When a subscription payment is due, Dodo Payments follows this payment sequence:
The system checks if the customer has available credit in their account.
If credit is available, it's automatically applied to reduce the payment amount.
Any remaining amount after credit application is charged to the customer's payment method.
The final payment is processed for the remaining balance, if any.
Dodo Payments supports separate credit wallets for INR and USD. Credits added to an INR wallet can only be used for INR subscriptions, while USD wallet credits apply exclusively to USD subscriptions. You cannot use credits from one currency wallet to pay for subscriptions in another currency.
### Payment Scenarios
**Scenario**: Customer has 60 USD credit, subscription costs 20 USD
**Result**:
* \$20 deducted from credit balance
* \$0 charged to payment method
* Remaining credit: \$40
No payment method charge required when credit fully covers the subscription cost.
**Scenario**: Customer has 60 USD credit, subscription costs 100 USD
**Result**:
* \$60 deducted from credit balance
* \$40 charged to payment method
* Remaining credit: \$0
Credits are always applied first, minimizing the charge to the customer's payment method.
**Scenario**: Customer has 0 USD credit, subscription costs 50 USD
**Result**:
* \$0 deducted from credit balance
* \$50 charged to payment method
* Remaining credit: \$0
## Managing Customer Credits
Effectively manage customer credit balances through the Dodo Payments dashboard with comprehensive credit and debit operations.
### Adding Credits to Customer Accounts
Increase customer account balances by adding credits for various business scenarios:
Go to **Customers** in your dashboard and select the specific customer you want to manage.
Navigate to the **Credit Ledger** section on the customer detail page.
The Credit Ledger provides a complete transaction history and current balance overview for the customer.
Click **Apply Credit** to open the credit management interface.
Set up the credit transaction with the following information:
Select **Credit** to add funds to the customer's account balance.
Enter the credit amount to add to the customer's account.
Choose the currency for the credit transaction:
* **USD** - US Dollars
* **INR** - Indian Rupees
Provide an optional explanation for the credit application, such as "Service compensation" or "Loyalty reward".
Review the credit details and click **Apply** to add the credit to the customer's account.
The credit is immediately available for use on the customer's next subscription payment.
### Removing Credits (Debit Operations)
Remove funds from customer accounts when adjustments or corrections are needed:
Follow the same navigation path: **Customers** → **Customer Detail Page** → **Credit Ledger** → **Apply Credit**.
Set up the debit operation:
Select **Debit** to remove funds from the customer's account balance.
Enter the amount to deduct from the customer's credit balance.
Choose the appropriate currency (USD or INR).
Provide an explanation for the debit, such as "Billing correction" or "Credit adjustment".
Ensure the customer has sufficient credit balance before applying a debit. Negative balances are not supported.
Review and confirm the debit transaction to remove the specified amount from the customer's account.
## Understanding the Credit Ledger
The Credit Ledger provides comprehensive tracking and transparency for all customer credit transactions.
### Available Credit Balance
The customer's available credit balance is prominently displayed at the top of the Credit Ledger interface, providing immediate visibility into their current credit status.
**Total Balance Overview**\
The main balance display shows the combined credit amount across all supported currencies, converted to a single currency for easy reference. This unified view helps you quickly assess the customer's total available credit without needing to calculate across multiple currencies.
The total balance is automatically calculated using current exchange rates to provide an accurate combined view of multi-currency credits.
**Currency Breakdown**
Click the "View Breakdown" button to expand the detailed view and see individual balances for each currency:
* **USD Balance**: Shows the exact amount available in US Dollars
* **INR Balance**: Shows the exact amount available in Indian Rupees
* **Additional Currencies**: Any other currencies with available credit balances
The breakdown view is particularly useful when managing customers who make payments in multiple currencies or when you need to verify specific currency allocations.
### Ledger Entry Components
Each transaction in the Credit Ledger contains the following information:
Unique identifier for each credit transaction, ensuring complete auditability.
Payment ID when the transaction is linked to a specific payment or billing event.
The type of transaction that created this ledger entry:
* **Payment** - Credit used for subscription payment
* **PaymentReversal** - Payment reversal creating credit
* **Refund** - Refund processed as credit
* **RefundReversal** - Refund reversal removing credit
* **Dispute** - Dispute-related credit adjustment
* **DisputeReversal** - Dispute resolution adjustment
* **MerchantAdjustment** - Manual credit/debit by merchant
The transaction amount, showing positive values for credits and negative values for debits.
Customer's credit balance before this transaction was processed.
Customer's credit balance after this transaction was completed.
Optional explanation provided when the transaction was created.
When the transaction was processed, in ISO 8601 format.
## Real-World Credit Scenarios
Understanding practical applications helps you leverage customer credits effectively for business growth and customer satisfaction.
### Scenario Examples
**Situation**: Your service experienced a 2-hour outage affecting a premium customer.
**Action**: Apply a 25 USD service credit with reason "Compensation for service downtime on \[date]"
**Result**: Customer receives credit that automatically applies to their next USD 50 subscription, reducing their charge to \$25.
**Business Impact**: Maintains customer satisfaction and demonstrates commitment to service quality.
**Situation**: A customer has been subscribed for 12 consecutive months.
**Action**: Apply a \$10 loyalty credit with reason "Annual loyalty reward - Thank you for being with us!"
**Result**: Customer receives unexpected value, strengthening retention and brand loyalty.
**Business Impact**: Proactive customer appreciation that encourages continued subscription.
## Current Limitations
The following features are planned for Phase 2 release and are not currently available:
### Checkout Integration
Customer credits cannot currently be applied during the checkout process. Customers cannot view or use their available credit balance when making new purchases through payment pages.
**Planned Enhancement**: Future releases will include credit balance display and application options in checkout flows.
### One-Time Payment Products
Credits are currently only applicable to recurring subscription payments. They cannot be used for one-time payment products or single purchases.
# Customer Portal
Source: https://docs.dodopayments.com/features/customer-portal
Empower customers to self-serve and manage their subscriptions, billing history, license keys, and profile securely.
The Customer Portal is a secure, hosted area where your customers can manage subscriptions, view invoices and access license keys details—without contacting support.
Programmatically create secure, time‑bound portal sessions.
Manage recurring plans, upgrades, downgrades, and add‑ons.
## What Is the Customer Portal?
The portal provides a trusted, branded self‑service experience for customers to:
* **Access billing history**: View invoices and download.
* **Manage subscriptions**: Cancel a subscription.
* **Retrieve license keys**: Access all keys tied to purchases
## Key Benefits
* **Lower support volume**: Customers resolve common billing requests on their own
* **Faster time‑to‑value**: Immediate access to invoices and keys
* **Reduced churn risk**: Clear visibility into renewals and plan details
* **Secure by design**: Tokenized access with expiring links
## Access Methods
Customers can reach the portal using either a static link or a one‑time dynamic link.
### Static link (email‑based access)
Customers can request portal access by entering their email at a static link that never expires.
The static portal link format varies by environment:
**Test Mode** (for testing and development):
```bash theme={null}
https://test.customer.dodopayments.com/login/{business_id}
```
**Live Mode** (for production with real transactions):
```bash theme={null}
https://customer.dodopayments.com/login/{business_id}
```
Replace `{business_id}` with your actual business identifier, then share the appropriate link with customers so they can enter their email and receive secure access to the portal.
1. Go to Sales → Customer.
2. Click Share invite.
3. Copy the Static link and share it with your customer.
1. Open the static link.
2. Enter the email used at purchase.
3. Receive a secure login link to access the portal.
Existing customers are recognized automatically.
### Dynamic link (magic link)
A personalized, one‑time magic link that sends customers directly into the portal. The link expires in 24 hours.
Dynamic links expire after 24 hours. If it expires, generate and send a new link.
1. Go to Sales → Customer.
2. Click Share invite.
3. Copy the Dynamic link and share it with your customer.
1. Open the dynamic link.
2. Access the Customer Portal directly without entering an email.
## Portal Features
* See all historical transactions and invoice details
* View and Download invoices.
* List active and past subscriptions
* Cancel a plan
* View all license keys issued.
* View and update name, email, and phone number
## Integration Examples
Create a time‑bound Customer Portal session for a specific customer via API, then redirect the user to the session URL.
```typescript Node.js theme={null}
const session = await client.customers.customerPortal.create('cus_123');
// Redirect the user to the hosted Customer Portal
window.location.href = session.link;
```
## Troubleshooting
* **Link expired**: Generate and send a new dynamic link.
* **Email not recognized**: Ask the customer to use the email associated with their purchase
# Customer Wallets
Source: https://docs.dodopayments.com/features/customer-wallet
Build credit-based billing systems like OpenAI or Claude with customer wallets. Track balances, manage credits, and create sophisticated financial workflows.
Check customer wallet balances across currencies.
Add or remove credits from customer wallets.
View complete transaction history with pagination.
## What are Customer Wallets?
Think of customer wallets as digital piggy banks for your users. Every customer gets one automatically when you create their account. You can use these wallets to:
* **Track credit balances** in USD and INR
* **Build API credit systems** like OpenAI or Claude
* **Create prepaid billing** where customers buy credits upfront
* **Handle refunds** as wallet credits instead of cash
* **Manage complex billing** with detailed transaction history
Every customer automatically gets a wallet when you create their account. Wallets support USD and INR currencies with separate balances for each.
## How It Works
Customer wallets are simple: they hold money (credits) that customers can spend on your services. When a customer makes a purchase, their wallet balance is checked first, and any available credits are used before charging their payment method.
### Automatic Setup
When you create a new customer, Dodo Payments automatically creates a wallet with zero balance. It's ready to use immediately through our API.
### Multi-Currency Support
Each wallet can hold balances in different currencies:
Balance in US Dollars (stored in cents)
Balance in Indian Rupees (stored in paise)
Currently, only USD and INR balances are available. More currencies coming soon.
## Working with Wallets
### Check Customer Balances
See how much credit a customer has across all currencies. Perfect for checking if they have enough funds before processing a purchase.
Check a customer’s wallet credit balances in all supported currencies.
### Add or Remove Credits
Give customers credits (like welcome bonuses) or deduct credits (like usage charges). You can add reasons for each transaction to keep track of what happened.
Add or remove credits from a customer’s wallet.
### View Transaction History
See every credit and debit transaction for a customer. Great for debugging billing issues or showing customers their spending history.
View every credit and debit transaction for a customer.
## Real-World Examples
### API Credit System (Like OpenAI)
Build a system where customers buy credits and spend them on API calls:
```javascript theme={null}
// Give new customers welcome credits
async function giveWelcomeCredits(customerId) {
await client.customers.wallets.ledgerEntries.create(customerId, {
amount: 10000, // $100 in cents
currency: 'USD',
entry_type: 'credit',
reason: 'Welcome bonus - 100 API credits',
idempotency_key: `welcome_${customerId}_${Date.now()}`
});
}
// Charge customers for API usage
async function chargeForApiUsage(customerId, usageCost) {
try {
await client.customers.wallets.ledgerEntries.create(customerId, {
amount: usageCost, // Cost in cents
currency: 'USD',
entry_type: 'debit',
reason: `API usage - ${usageCost} credits consumed`,
idempotency_key: `usage_${customerId}_${Date.now()}`
});
} catch (error) {
if (error.status === 400) {
console.log('Customer needs to buy more credits');
}
}
}
```
### Prepaid Billing System
Let customers buy credits upfront and spend them over time:
Give new customers some free credits to get started.
```javascript theme={null}
await client.customers.wallets.ledgerEntries.create(customerId, {
amount: 5000, // $50 welcome bonus
currency: 'USD',
entry_type: 'credit',
reason: 'Welcome bonus for new customer',
idempotency_key: `welcome_${customerId}`
});
```
When customers buy credits, add them to their wallet.
```javascript theme={null}
await client.customers.wallets.ledgerEntries.create(customerId, {
amount: purchaseAmount, // Amount paid in cents
currency: 'USD',
entry_type: 'credit',
reason: `Credit purchase - ${purchaseAmount} credits`,
idempotency_key: `purchase_${paymentId}`
});
```
Deduct credits when customers use your service.
```javascript theme={null}
await client.customers.wallets.ledgerEntries.create(customerId, {
amount: usageCost,
currency: 'USD',
entry_type: 'debit',
reason: `Service usage - ${usageCost} credits`,
idempotency_key: `usage_${usageId}`
});
```
Check if customers are running low on credits.
```javascript theme={null}
const wallets = await client.customers.wallets.list(customerId);
const usdWallet = wallets.items.find(w => w.currency === 'USD');
const balance = usdWallet.balance;
if (balance < 1000) { // Less than $10
// Send low balance notification
await sendLowBalanceNotification(customerId, balance);
}
```
### Multi-Currency Support
Handle customers in different countries:
Give USD credits to US-based customers.
```javascript theme={null}
await client.customers.wallets.ledgerEntries.create(customerId, {
amount: 20000, // $200 in cents
currency: 'USD',
entry_type: 'credit',
reason: 'USD credit purchase',
idempotency_key: `usd_purchase_${paymentId}`
});
```
Give INR credits to Indian customers.
```javascript theme={null}
await client.customers.wallets.ledgerEntries.create(customerId, {
amount: 1500000, // ₹15,000 in paise
currency: 'INR',
entry_type: 'credit',
reason: 'INR credit purchase',
idempotency_key: `inr_purchase_${paymentId}`
});
```
## Best Practices
### Prevent Duplicate Transactions
Use idempotency keys to make sure you don't accidentally charge customers twice for the same thing:
```javascript theme={null}
async function addCreditsSafely(customerId, amount, reason) {
const idempotencyKey = `${reason}_${customerId}_${Date.now()}`;
try {
const result = await client.customers.wallets.ledgerEntries.create(customerId, {
amount: amount,
currency: 'USD',
entry_type: 'credit',
reason: reason,
idempotency_key: idempotencyKey
});
return { success: true, wallet: result };
} catch (error) {
if (error.status === 400 && error.message.includes('Insufficient balance')) {
return { success: false, error: 'INSUFFICIENT_BALANCE' };
}
if (error.status === 409) {
// Transaction already processed
return { success: true, wallet: null, duplicate: true };
}
throw error;
}
}
```
### Check Balances Before Charging
Always verify customers have enough credits before processing expensive operations:
```javascript theme={null}
async function checkBalanceBeforeOperation(customerId, requiredAmount) {
const wallets = await client.customers.wallets.list(customerId);
const usdWallet = wallets.items.find(w => w.currency === 'USD');
if (!usdWallet || usdWallet.balance < requiredAmount) {
throw new Error('Not enough credits for this operation');
}
return usdWallet.balance;
}
```
## What's Coming Next
These features are planned for future releases:
* **Credit Expiration**: Set credits to expire after a certain time
* **Better Analytics**: Detailed spending reports and usage insights
* **More Webhooks**: Real-time notifications for balance changes and low credits
Start simple with basic credit/debit operations, then add more complex features as your business grows.
# Customer Management
Source: https://docs.dodopayments.com/features/customers
Manage customer relationships, track purchase history, and provide self-service options through the Dodo Payments dashboard and API.
## Overview
The Customers feature in Dodo Payments provides a centralized hub for managing all your customer relationships. Track every customer interaction, view purchase history, manage subscriptions, access wallet credit ledgers, and enable self-service options - all from a single dashboard.
View and manage all customers in one place with search and filtering capabilities.
Build credit-based billing systems with prepaid balances and transaction tracking.
Empower customers to self-manage subscriptions, invoices, and license keys.
Programmatically create, update, and retrieve customer data via API.
## Accessing the Customers Page
From the dashboard, click on **Sales > Customers** in the navigation menu.
The Customers Page will display a list of all current and past customers who have interacted with your business.
## Customer Information
On the **Customers Page**, you can see a comprehensive overview of your entire customer base. The table displays all customers with the following information:
### Customer List Columns
* **Customer Name**: The full name of the customer with an avatar indicator.
* **Customer Id**: A unique identifier (e.g., `cus_5fpsWAa...`) for each customer.
* **Email**: The customer's email address for communication and portal access.
* **Phone Number**: The customer's phone number (if provided).
* **Created At (UTC)**: The date and time when the customer account was created.
* **Invite**: Quick action button to share Customer Portal invites.
### Page Features
* **Edit Columns**: Customize which columns are displayed in the customer table.
* **Build Report**: Generate custom reports based on your customer data.
* **Search**: Quickly find specific customers by name, email, or customer ID.
* **Pagination**: Navigate through your customer list with adjustable rows per page.
Use the "Edit Columns" feature to customize your view and show only the information most relevant to your workflow.
## Customer Details Page
Click on any customer to view their detailed information page, which includes:
### About Section
The About section displays core customer information:
* **Name**: Customer's full name
* **Email**: Customer's email address
* **Customer ID**: Unique identifier for API and tracking purposes
* **Created At**: Timestamp of when the customer account was created
### Customer Activity Tabs
The customer details page includes three tabs for viewing different aspects of customer activity:
View all payment transactions for this customer including:
* **Payment ID**: Unique transaction identifier
* **Status**: Payment status (Successful, Failed, Pending, Refunded)
* **Amount**: Transaction amount
* **Date (UTC)**: When the transaction occurred
* **Pricing Type**: Whether it's a one-time purchase or subscription
* **Payment Method**: Payment method used (Card, UPI, etc.)
* **Customer Email**: Email associated with the transaction
* **Refund**: Quick action to initiate refunds
* **Actions**: Additional options for managing the transaction
View all active subscriptions for this customer:
* Subscription plan details
* Billing cycle and next renewal date
* Subscription status
* Options to manage or cancel subscriptions
View the customer's wallet credit ledger showing:
* All credit and debit transactions
* Current wallet balance in each currency
* Transaction reasons and timestamps
* Detailed transaction history for reconciliation
The Credit Ledger tab provides complete transparency into customer wallet transactions, making it easy to track credits, debits, and current balances.
## Customer Portal
The Customer Portal is a secure, hosted self-service area where your customers can manage their subscriptions, view billing history, access license keys, and update their profile—without contacting support.
### Portal Capabilities
Customers can access the portal to:
* **View Billing History**: See all past transactions and download invoices.
* **Manage Subscriptions**: Cancel or view subscription details.
* **Retrieve License Keys**: Access all license keys tied to their purchases.
* **Update Profile**: Change name, email, or phone number.
Explore detailed documentation on portal features, access methods, and integration examples.
## Customer Wallets
Customer Wallets enable you to build credit-based billing systems similar to OpenAI or Claude. Each customer automatically receives a wallet that can hold prepaid credits in multiple currencies.
### Key Features
* **Multi-Currency Support**: Track balances in USD and INR separately.
* **Credit-Based Billing**: Allow customers to purchase credits upfront and spend them on your services.
* **Transaction History**: View complete ledger of all credits and debits.
* **API-First Design**: Programmatically add, remove, and check wallet balances.
Explore detailed documentation, code examples, and best practices for implementing customer wallets.
## API Management
Dodo Payments provides a comprehensive REST API for programmatic customer management. You can create, retrieve, update, and manage customers entirely through the API.
### Available API Endpoints
Create a new customer in your Dodo Payments account with their details.
Retrieve a paginated list of all customers associated with your account.
Get detailed information about a specific customer by their ID.
Update a customer's information such as name, email, or metadata.
Generate a secure Customer Portal session link for a specific customer.
All API endpoints require authentication using your API key. Always use your secret key on the server-side and never expose it in client-side code.
# Digital Product Delivery
Source: https://docs.dodopayments.com/features/digital-product-delivery
The Digital Product Delivery feature in Dodo Payments allows merchants to deliver digital products to their customers.
## Overview
The **Digital Product Delivery** feature enables merchants to automatically deliver digital files to customers after successful purchases. Whether you're selling e-books, design templates, software, or media packs, you can attach downloadable content to your products, providing instant access via email and the customer portal.
This feature supports both hosted file uploads and external download links, offering flexibility for various use cases.
## Key Features
Upload and deliver digital files (PDF, ZIP, images, videos, etc.) directly to your customers.
Attach up to 10 files per product for comprehensive digital product delivery.
Provide external download links (Dropbox, Google Drive, etc.) as an alternative to file uploads.
Customers automatically receive download instructions via email upon successful payment.
## Setup Process
Go to your dashboard and select **Products > Create New** or edit an existing product. Navigate to **Advanced Settings → Digital Product Delivery**.
Choose one of the following options:
* **Upload File(s)**: Add up to 10 files directly to your product
* **External Download Link**: Paste a secure link hosted externally (e.g., Dropbox, Google Drive)
Click **Update Product** to make your digital product delivery settings live.
Uploaded files are served via static URLs — anyone with the link can access the file. Consider external services with authentication for sensitive content.
## Customer Experience
### Purchase Confirmation
After a successful transaction, customers receive a purchase confirmation email containing download links for their digital products.
### Customer Portal Access
Customers can access their digital products through the Customer Portal:
* Log in to view all past purchases
* Download files or access external links for each product
* Persistent access to purchased digital content
Customers can download files directly from confirmation emails or access them anytime through their portal.
## Important Considerations
* **Security**: Static download links are not order-authenticated. Customers with the link can re-share the same link with other parties. For secure or licensed content, consider integrating with external services that support gated or authenticated access.
* **File Updates**: Updating a file or external link will affect all future purchases only. Past purchases will not be affected and customers can still access those files
* **Refunds**: Issuing a refund does not revoke access to files. Customers who already downloaded a file will still have it on their device.
* **Best Practice**: For version control or limited-time access, consider delivering your content via expiring or signed URLs hosted externally.
# Discounts
Source: https://docs.dodopayments.com/features/discount-codes
Create promotional codes to drive conversions, run campaigns, and reward customers with fixed or percentage discounts across one‑time and subscription purchases.
Discount codes let you run targeted promotions and incentives. Create percentage or fixed-amount discounts, set limits and expirations, restrict to products, and apply them seamlessly in checkout.
Apply codes during hosted checkout with `discount_code` and UI controls.
Check if a code is valid and applicable before applying.
Programmatically create new discount codes.
Browse and manage existing discounts; update or delete as needed.
## What Are Discount Codes?
Discount codes are promotional tokens that reduce order totals at checkout. They’re ideal for:
* **Seasonal campaigns**: Black Friday, product launches, or anniversaries
* **Acquisition offers**: First‑purchase incentives or referral rewards
* **Retention**: Win‑back or loyalty rewards for existing customers
* **B2B deals**: Contracted or negotiated pricing via private codes
## Key Benefits
* **Flexible discounts**: Percentage or fixed amount off
* **Targeted control**: Restrict by product and subscription cycles
* **Campaign governance**: Expiration dates and usage limits
* **Seamless checkout**: UI field and API support via checkout sessions
## Creating Discount Codes
Create discount codes in your Dodo Payments dashboard, then apply them in hosted checkout or via API.
### Dashboard setup
* **Discount Name** (required): Internal and dashboard display name
* **Code** (required): The string customers enter at checkout
* **Type & Amount** (required): Set a percentage or fixed‑amount value, or generate a random code using the button provided
* **Expiration Date** (optional): Date after which the code becomes invalid
* **Usage Limit** (optional): Max total redemptions across all customers
* **Product Restriction** (optional): Limit applicability to selected products
* **Subscription Cycle Limit** (optional): Number of billing cycles the discount applies to
Use cycle limits for introductory pricing on subscriptions (e.g., "50% off for 3 months").
## Checkout Experience
1. Shoppers enter the code in the checkout field.
2. Eligible discounts are applied and totals update immediately.
In Checkout Sessions, pass `discount_code` to pre‑apply a code, and set `feature_flags.allow_discount_code` to show the input field.
## API Management
Create discount codes programmatically with type and amount.
View the create discount API.
List all discounts or retrieve details for management and auditing.
Browse listing and retrieval APIs.
Modify discount configuration such as amount, expiration, or restrictions.
Learn how to update discount details.
Check whether a discount is valid and applicable before applying.
Validate discount usage.
Deactivate or remove discounts that are no longer needed.
Delete a discount.
## Common Use Cases
* **Intro offers**: Limited‑time launch promotions for new products
* **Bulk or B2B**: Contracted discounts for select product sets
* **Retention plays**: Win‑back codes in churn‑prevention workflows
* **Seasonal campaigns**: Holiday or event‑based promotions
## Integration Examples
### Apply a discount in Checkout Sessions
Pre‑apply a discount and show the code input UI.
```typescript theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{ product_id: 'prod_abc', quantity: 1 }
],
discount_code: 'BLACKFRIDAY2024',
customer: { email: 'user@example.com', name: 'Jane Doe' },
return_url: 'https://yoursite.com/return'
});
```
### Enable discount entry without pre‑applying
Let customers enter a code at checkout without passing one upfront.
```typescript theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{ product_id: 'prod_abc', quantity: 1 }
],
feature_flags: {
allow_discount_code: true
},
return_url: 'https://yoursite.com/return'
});
```
## Best Practices
* **Name clearly**: Use recognizable codes that match campaign names
* **Time‑box**: Add expirations to drive urgency and prevent misuse
* **Scope wisely**: Limit to specific products to avoid margin leakage
* **Validate early**: Check code applicability before confirming checkout
* **Monitor impact**: Track usage and conversion by campaign
Discount codes are powerful levers for acquisition and retention. Start with simple, well‑named offers, validate thoroughly, and iterate based on performance.
# Feature Request and Roadmap
Source: https://docs.dodopayments.com/features/feature-request
We value feedback from our merchants and aim to continuously improve Dodo Payments to meet your business needs. The **Feature Request and Roadmap** section allows you to directly contribute to our product development by suggesting new features, voting on existing requests, and staying informed about upcoming updates.
## **Request a New Feature**
If you have an idea for a feature that would enhance your experience with Dodo Payments, you can submit a feature request.
* **How to Request a Feature**:
1. Navigate to the **Feature Request** section on the dashboard.
2. Click on **"Request a New Feature"**.
3. Provide the following details:
* **Feature Name**: A concise title for your feature idea.
* **Feature Description**: A brief explanation of the feature, including why it’s important and how it will help your business.
4. Submit the request.
* **Tips for Effective Requests**:
* Be clear and concise.
* Highlight the business impact of the feature.
* Provide examples, if applicable.
## **Vote for Existing Feature Requests**
We prioritize features that benefit the most merchants. You can browse existing feature requests and vote for those you find valuable.
* **How to Vote**:
1. Go to the **Feature Request** section.
2. Scroll through the list of submitted requests.
3. Click the **"Upvote"** button next to the feature(s) you support.
* **Benefits of Voting**:
* Influence the development priority for features you care about.
* Help our team identify features that have widespread demand.
## **View the Product Roadmap**
Stay informed about the status of feature development through the **Roadmap** section.
* **Roadmap Features**:
* **Planned**: Features that have been approved and are scheduled for development.
* **In Progress**: Features currently being worked on by our team.
* **Completed**: Features that have been successfully implemented and are live.
* **How to Access**:
* Navigate to the **Roadmap** tab within the Feature Request section.
* Browse the feature categories and their current statuses.
# Invoice Management
Source: https://docs.dodopayments.com/features/invoice-generation
Automatically generate and deliver professional invoices for every successful payment, with customizable branding and seamless email delivery.
## Overview
Dodo Payments automatically generates professional invoices for every successful transaction, eliminating manual invoice creation and ensuring consistent documentation for your business. Invoices are instantly delivered to both you and your customers via email after each payment.
Invoices are generated automatically for both one-time purchases and recurring subscription payments.
## Key Features
Invoices are created instantly after successful payment processing without manual intervention.
Add your business logo and name to maintain brand consistency across all customer touchpoints.
Invoices are automatically sent to customers and merchants immediately after generation.
Access and download invoices from a centralized dashboard view.
## Setting Up Invoice Branding
Open the Dodo Payments dashboard and go to **Settings** → **Business Profile**.
Upload your business logo and enter your business name. These details will appear on all generated invoices.
Use a high-resolution logo (minimum 300x300px) in PNG format for the best print quality.
Click **Save** to apply your branding settings. All future invoices will automatically use these details.
Your invoice branding is now configured and will be applied to all new invoices.
## What's Included in Invoices
Every generated invoice contains comprehensive transaction details:
* **Business Information**: Your business name, logo, and contact details
* **Customer Details**: Customer name and email address
* **Invoice Number**: Unique identifier for tracking and reference
* **Transaction Date**: When the payment was processed
* **Payment Details**: Amount paid, currency, and payment method
* **Product/Service Description**: Itemized list of purchased products or subscriptions
* **Tax Information**: Applicable taxes and tax identification numbers
* **Payment Status**: Confirmation of successful payment
Invoice numbers are automatically generated and follow a sequential format for easy tracking.
## How Invoice Generation Works
When a customer completes a one-time purchase:
1. Payment is processed successfully
2. Invoice is automatically generated with transaction details
3. Invoice is emailed to both customer and merchant
4. Invoice appears in your dashboard **Invoices** section
All one-time purchase invoices include product details, pricing, and payment confirmation.
For recurring subscription payments:
1. Subscription renewal payment is processed automatically
2. Invoice is generated for the billing period
3. Invoice is emailed to customer with subscription details
4. Invoice is stored in your dashboard for records
Subscription invoices include the billing period, subscription plan details, and next renewal date.
## Managing Invoices
### Accessing Your Invoices
Navigate to the **Invoices** section in your Dodo Payments dashboard to view all generated invoices. You can:
* **View Invoice Details**: Click any invoice to see complete transaction information
* **Download PDF**: Export invoices in PDF format for your records
* **Resend to Customer**: Email the invoice again if needed
* **Filter and Search**: Find specific invoices by date, customer, or invoice number
Use the search and filter options to quickly locate invoices for accounting or customer support purposes.
## Invoice Template Preview
Here's what your branded invoices will look like:
## Related Features
Learn how subscriptions work with automatic recurring invoices.
Manage multiple brands with separate invoice branding for each.
Allow customers to access their invoice history through the customer portal.
Configure tax settings to ensure invoices display correct tax information.
# License Keys
Source: https://docs.dodopayments.com/features/license-keys
Securely generate, manage, and track license keys for your digital products with advanced access control and revocation.
Issue unique keys for your software or digital goods, control activations, and revoke access when needed. License keys pair well with subscriptions and one-time purchases.
## What Are License Keys?
License keys are unique tokens that authorize access to your product. They're ideal for:
* **Software licensing**: Desktop apps, plugins, and CLIs
* **Per-seat controls**: Limit activations per user or device
* **Digital goods**: Gate downloads, updates, or premium features
## Key Benefits
* **Access control**: Define expiry and activation limits
* **Operational visibility**: Track usage, devices, and history
* **Security and support**: Disable, re-enable, and audit quickly
## Creating License Keys
Go to your Dodo Payments dashboard and open the License Keys section. Select the option to create a new license key.
Set how your license keys will be issued and used by specifying these fields:
* **License Key Expiry Date**: Define how long the key stays valid. Choose a specific duration (for example, 30 days or 1 year), or select "no expiry" for a perpetual key.
* **Activation Limit**: Set the maximum number of concurrent activations per key. Use a specific number for scenarios like single-user (1) or team licenses (5), or select "unlimited" for no activation cap.
* **Activation Instructions**: Provide clear activation steps for your customers, which will also be emailed with the license key. Examples: "Paste the key in Settings → License" or "Run: `mycli activate `".
Check all entered details. When ready, save the license key configuration to finish key creation.
Use realistic instructions and include support links to reduce activation tickets.
## Manage & Monitor
The **License Keys Section** provides a comprehensive view of all license key activity and enables you to manage and monitor licenses effectively.
### Key Information
View detailed information for each license key:
* **Key Details**: License key value, associated product name, customer details (customer\_id), and purchase date
* **Usage Activity**: Number of activations, activation dates and times, and status (active, expired, disabled)
* **Expiry and Limits**: Key expiry date, remaining activation count, and current activation instances
### Available Actions
You can perform the following actions on license keys:
* **Disable License Key**: Immediately disable a key to prevent further usage
* **Enable Key**: Re-enable a previously disabled license key
* **View Activation Instances**: See all associated activation instances for a particular license key
### Benefits
* Track license key activity in real-time
* Identify potential misuse or unauthorized activations
* Simplify customer support by providing detailed usage records in case of discrepancy
## API Management
Use these APIs to activate, validate, list, and update license keys and their activation instances.
**Public Endpoints**: The activate, deactivate, and validate license endpoints are **public** and do **not require an API key**. This allows you to call them directly from your client applications, desktop software, or CLIs without exposing your API credentials.
Trigger activation, deactivation, and validation actions on license keys.
Create or record an activation instance for a license key.
Revoke a prior activation to free up capacity.
Check authenticity, status, and constraints before granting access.
List, retrieve, and update license keys with their settings and status.
Browse all keys with status and usage details.
Retrieve a specific key and its metadata.
Modify expiry, activation limits, or enable/disable a key.
Work with individual device activations for each key.
Return all activation instances for a specific key.
Fetch details for a single activation instance.
Revoke or update device information for an instance.
## Integration Examples
### Activate a license
These endpoints do not require an API key and can be called directly from your client applications.
```typescript TypeScript/Node.js theme={null}
import DodoPayments from 'dodopayments';
// No API key needed for public license endpoints
const client = new DodoPayments();
const response = await client.licenses.activate({
license_key: 'license_key',
name: 'Device Name'
});
console.log(response.id);
```
```bash cURL theme={null}
curl -X POST https://api.dodopayments.com/licenses/activate \
-H "Content-Type: application/json" \
-d '{
"license_key": "your_license_key",
"name": "Device Name"
}'
```
### Validate a license
```typescript TypeScript/Node.js theme={null}
import DodoPayments from 'dodopayments';
// No API key needed for public license endpoints
const client = new DodoPayments();
const response = await client.licenses.validate({
license_key: '2b1f8e2d-c41e-4e8f-b2d3-d9fd61c38f43'
});
console.log(response.valid);
```
```bash cURL theme={null}
curl -X POST https://api.dodopayments.com/licenses/validate \
-H "Content-Type: application/json" \
-d '{
"license_key": "2b1f8e2d-c41e-4e8f-b2d3-d9fd61c38f43"
}'
```
## Best Practices
* **Keep limits clear**: Choose sensible defaults for expiry and activations
* **Guide users**: Provide precise activation instructions and self-serve documentation
* **Automate checks**: Validate keys server-side before granting access
* **Monitor events**: Use webhooks to detect abuse and automate revocations
# Android & iOS Mobile App
Source: https://docs.dodopayments.com/features/mobile-app
Manage your Dodo Payments business on the go with our mobile app, available on Google Play and Apple App Store. Monitor transactions, receive real-time notifications, and access your dashboard anywhere.
## Introduction
Manage your payments, subscriptions, and business activity — anytime, anywhere. The **Dodo Payments Mobile App** brings the power of the Dodo Dashboard to your fingertips, allowing you to stay connected to your revenue, customers, and activity in real time.
Whether you're commuting, traveling, or just away from your desk, you'll never miss an important update again.
## Key Features
Get instant alerts for new transactions, payouts, subscription renewals, and verification status updates
View your revenue, transaction history, customer statistics, and visual performance graphs
Monitor all active subscriptions and check individual customer details
Create and manage products directly from your mobile device
## Parity with Desktop Dashboard
All the features of the desktop version are fully available in the mobile app, including:
* Test Mode and Live Mode switching
* Verification progress and submission
* Developer settings and API key visibility
* Pricing and Discount configuration
## Download Now
Download from Google Play Store
Download from Apple App Store
# MoR vs PG
Source: https://docs.dodopayments.com/features/mor-vs-pg
When it comes to processing payments globally, digital businesses and SaaS companies are often torn between two models: Merchant of Record (MoR) and Payment Gateway (PG). At Dodo Payments, we believe in simplifying global payments while taking care of compliance, taxes, and local laws - and that's why we operate as a Merchant of Record.
## Introduction
This guide compares the MoR model with the traditional Payment Gateway approach, helping you understand the advantages Dodo Payments brings to your business.
## The Core Difference
| Feature | MoR (Dodo Payments) | Payment Gateway (Traditional PG) |
| ------------------------------ | ---------------------------------------- | --------------------------------------------- |
| Legal Seller | Dodo Payments (MoR) | Your Company |
| Tax Collection & Remittance | Handled by Dodo | You are responsible |
| Compliance & Regulatory Burden | Dodo assumes liability | You handle local laws and chargebacks |
| Settlement Currency | USD, EUR, INR, and 25+ others supported | Depends on your merchant account |
| Risk Management | Built-in fraud and chargeback protection | You set up your own tools (e.g. Stripe Radar) |
| Payouts | Aggregated and simplified global payouts | Direct from PG to you, with bank setup |
## What It Means For You
With **Dodo as MoR**, we become the legal seller to your customers, allowing you to:
* Skip setting up local entities
* Avoid handling VAT, GST, or sales tax
* Offer more payment methods globally
* Reduce legal risk
* Launch faster in new markets
Imagine selling a digital subscription to a user in France. With Dodo Payments, we collect the payment, file VAT with French authorities, and send you the net revenue. No tax headaches. No lawyers. Just growth.
Additionally, the MoR model simplifies your entire back office. As your MoR, Dodo handles PCI compliance, fraud detection, currency conversion, and even customer billing support, freeing your team to focus on product and growth.
## Visual Comparison
**Revenue Flow: Payment Gateway**
```mermaid theme={null}
flowchart LR
A[Customer] --> B[Your Website]
B --> C["Payment Gateway (e.g. Stripe)"]
C --> D[Your Bank]
D -.-> E["You handle: tax, support, refunds, fraud"]
```
**Revenue Flow: Merchant of Record (Dodo)**
```mermaid theme={null}
flowchart LR
A[Customer] --> B[Dodo Checkout]
B --> C[Dodo Payments]
C --> D[Your Wallet]
C -.-> E["Dodo handles: Tax, Compliance, FX, Fraud, Invoicing"]
```
## Why It Matters for SaaS & Digital Businesses
As your business scales, managing taxes, compliance, and global payment preferences can become overwhelming. With a payment gateway, you're responsible for:
* VAT/GST registration and filing in multiple jurisdictions
* Managing currency conversion and chargebacks
* Providing localized checkout and payment methods
With Dodo Payments as your MoR:
* You expand globally without setting up local entities
* Taxes are calculated, collected, and remitted on your behalf
* You gain access to a library of payment methods tailored to your customers
* We act as your legal buffer and operational partner
"Think of a payment gateway as a tunnel. Now imagine the Merchant of Record as a tunnel, train, driver, and ticketing staff all-in-one."
## Who Should Use MoR?
Dodo Payments is perfect for:
* SaaS & digital product companies
* Indie creators and solopreneurs
* Global businesses with customers across 100+ countries
* Companies who don't want to manage taxes & compliance in-house
If you're expanding internationally, selling subscriptions, or just want to reduce operational headaches, MoR is the smarter choice.
## When to Use a Payment Gateway Instead
There are cases where using just a payment gateway might make sense:
* Your business operates only in one country
* You already have internal finance and compliance resources
* You require complete control over customer billing experience
* You are highly cost-sensitive with thin margins at scale
For many startups, using a gateway may suffice initially - but as complexity grows, switching to an MoR can save time, reduce risk, and accelerate international growth.
## Why Choose Dodo Payments
Dodo Payments offers:
* All-in-one payments, tax, and compliance stack
* Real-time FX and multi-currency support
* Access to 30+ payment methods
* Seat-based billing, subscriptions, and one-time payments
* Automated tax handling in 150+ countries
* Built-in fraud prevention and PCI compliance
Whether you're a solo founder or a scaling SaaS team, Dodo simplifies the complexities of selling globally.
## Learn More
Learn how Dodo automatically presents prices in your customers' local currencies
Discover the 30+ payment methods available through Dodo Payments
## Ready to Switch?
Join 3,000+ digital businesses using Dodo Payments to sell globally, without borders or bottlenecks.
Create your Dodo Payments account and start selling globally today
Get personalized guidance from our team
Let Dodo handle the hard stuff - so you can focus on building a great product.
# Multi-Brand Setup
Source: https://docs.dodopayments.com/features/multi-brands
Learn how to set up multi-brand in Dodo Payments.
## Introduction
Multi-Brand lets you attach several brands—each with its own logo, descriptor, and website URL—to **one verified business**. Your payout account, fees, and KYC stay exactly the same, while every Product, Payment Link, Subscription, Invoice, and Transaction can now be filed under a specific brand. Use it to test new niches, localise a site, or separate B2B and B2C lines without opening extra business accounts.
## Merchant Dashboard Flow
The Brands page lists your Primary brand and any Secondary brands.
A Brand Verification form opens up.
Enter Brand Name, Website URL (or social profile), Description and Submit the form.
Upload Logo, set Statement Descriptor, and Support Email for each brand.
Once our compliance team marks the website "Verified", live mode will be enabled for this brand. Products can now be tagged to this brand and live payment links will display its branding.
A Brand filter is now visible on Product creation.
Transactions and Subscriptions have a Brand ID to help you identify which brand the transaction belongs to.
## Additional Points
* **No payout changes:** All funds, payout cycles and fees still flow to the business-level wallet and merchant bank account.
* **Suspensions:** If a brand is suspended, only its payment links stop—other brands under the same business keep transacting.
* No changes to Storefront, Invoices, License Keys, Discount Codes, Payout Settings and any other business level features.
# One‑Time Payments
Source: https://docs.dodopayments.com/features/one-time-payment-products
Sell fixed‑price digital goods or services with a single upfront charge, no renewals.
One‑time payments let you sell lifetime access, downloads, or single‑use services with a single charge. There are no renewals, proration rules, or billing cycles. Just a clean, instant purchase experience.
Issue unique license keys after purchase.
Automatically deliver files and content after payment.
React to payment succeeded/failed and receipt events.
Create full or partial refunds programmatically.
## What Are One‑Time Payments?
One‑time payments are fixed, up‑front purchases - ideal for:
* **Software licenses**: Lifetime or major‑version unlocks
* **Digital downloads**: E‑books, templates, graphics, or plugins
* **Services**: Audits, onboarding packages, or one‑off consulting
* **Courses and tickets**: Courses, workshops, or live sessions
## Key Benefits
* **Simple checkout**: No renewals or proration logic
* **Instant fulfillment**: Deliver licenses or files right away
* **Flexible pricing**: Discounts and taxes supported
* **Developer‑first**: Clear APIs for creation, refunds, and webhooks
## Creating One‑Time Products
Create products in your Dodo Payments dashboard, then sell them via hosted checkout, payment links, or your API.
### Product configuration
Configure the fields in the dashboard to define how your product appears and sells.
#### Product details
* **Product Name** (required): Display name shown in checkout and invoices
* **Product Description** (required): Clear value statement customers see before paying
* **Product Image** (required): PNG/JPG/WebP up to 3 MB
* **Brand**: Associate the product with a specific brand for theming and emails
* **Tax Category** (required): Choose the correct category to determine tax rules
Pick the most accurate tax category to ensure correct tax collection per region.
#### Pricing
* **Pricing Type**: Choose Single Payment
* **Price** (required): Fixed charge with currency
* **Discount Applicable (%)**: Optional percentage discount displayed in checkout and invoices
Changing product pricing affects only new purchases. Existing customers aren’t impacted.
#### Advanced settings
* **Tax Inclusive Pricing**: Display prices inclusive of applicable taxes
* **Generate license keys**: Issue a unique key after purchase. See License Keys
* **Digital Product Delivery**: Deliver files or content automatically. See Digital Product Delivery
* **Metadata**: Attach custom key–value pairs for internal tagging or integrations. See Metadata
Use metadata to store identifiers from your system (e.g., orderId) to reconcile events and receipts later.
## API Management
Use `POST /checkout-sessions/create` to create one‑time charges from products.
View the create payment API.
Use `POST /refunds` to create full or partial refunds.
Learn how to refund a payment.
Use `GET /payments` to list payments and `GET /payments/{payment_id}` to retrieve one. Retrieve line items with `GET /payments/{payment_id}/line-items`.
Browse the listing API.
See the retrieval API.
Inspect purchased items for a payment.
React to events like payment.succeeded or payment.failed in your backend.
Handle payment lifecycle events.
## Common Use Cases
* **License‑based tools**: Unlock features with a one‑time key
* **Digital assets**: Themes, templates, icon sets, or media
* **Professional services**: Fixed‑fee implementation or setup
* **Education**: Course tickets and one‑off workshops
## Integration Examples
### Checkout Sessions (single payment)
Create a checkout session with a one‑time product in the cart:
```typescript theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{ product_id: 'prod_one_time', quantity: 1 }
]
});
```
## Best Practices
* **Clarify deliverables**: Tell buyers exactly what they get and how it’s delivered
* **Deliver instantly**: Use license keys or digital delivery for immediate access
* **Track with metadata**: Store your IDs for easy reconciliation
One‑time payments are the fastest path to selling fixed‑price products. Start simple, verify delivery, and iterate your offer based on conversion and feedback.
# Pay What You Want Pricing
Source: https://docs.dodopayments.com/features/pay-what-you-want
Let customers choose their price above a minimum. Optionally show a suggested price. Optimized for one‑time purchases.
**Pay What You Want (PWYW)** lets customers decide how much they pay—at or above a merchant‑defined minimum. It increases engagement, encourages community support, and provides flexible pricing for digital products.
## What Is Pay What You Want?
PWYW is a pricing model where customers set their own price within guardrails you define. It’s ideal for:
* **Digital products and media**: Templates, e‑books, courses, plugins
* **Experimental launches**: Early access, beta releases, time‑boxed promos
* **Bundles and extras**: Add a PWYW tier to boost participation
## Key Benefits
* **Customer‑driven pricing**: Buyers choose what they pay
* **Revenue floor**: Enforce a minimum to protect margins
* **Anchored expectations**: Optional suggested price to guide choices
* **Seamless checkout**: Hosted checkout and receipts handled for you
* **Developer‑first**: Clear APIs for creation, refunds, and webhooks
PWYW is available only for **Single Payment** (one‑time) products.
## Creating Pay What You Want
Create a one‑time product and enable PWYW in your Dodo Payments dashboard. The sections below map directly to the creation form.
### Product creation
#### Product details
* **Product Name** (required): Display name shown in checkout and invoices
* **Product Description** (required): Clear value statement customers see before paying
* **Product Image** (required): PNG/JPG/WebP up to 3 MB
* **Brand**: Associate the product with a specific brand for theming and emails
* **Tax Category** (required): Choose the correct category to determine tax rules
Pick the most accurate tax category to ensure correct tax collection per region.
#### Pricing
* **Pricing Type**: Choose Single Payment
* **Pay What You Want** (toggle): Enable to let buyers choose the amount
* **Minimum Price** (required): Lowest allowed amount a customer can pay
* **Suggested Price** (optional): Recommended amount shown on the product page
Use metadata to store identifiers from your system (e.g., orderId) so you can reconcile events and receipts later.
## API Management
Use `POST /checkout-sessions/create` to create one‑time checkout sessions from products. Checkout will prompt buyers to enter an amount that meets your PWYW minimum.
View the create checkout session API.
Use `POST /refunds` to create full or partial refunds. Customers are refunded the exact amount they paid.
Learn how to refund a payment.
Use `GET /payments` to list payments and `GET /payments/{payment_id}` to retrieve one. Retrieve line items with `GET /payments/{payment_id}/line-items`.
Browse the listing API.
See the retrieval API.
Inspect purchased items for a payment.
React to events like payment.succeeded or payment.failed in your backend.
Handle payment lifecycle events.
## Common Use Cases
* **Digital assets**: Themes, templates, icon sets, media, or plugins
* **Content and education**: E‑books, courses, workshops with supporter pricing
* **Community & donations**: Fundraisers, open‑source sponsorships, creator support
* **Launch experiments**: Beta access and time‑boxed campaigns to gauge demand
## Integration Examples
### Checkout Sessions (single payment, PWYW)
Add a PWYW product to the cart. Checkout collects the buyer’s amount (respecting your minimum) — no extra fields are required.
```typescript theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{ product_id: 'prod_pwyw_demo', quantity: 1 }
],
return_url: 'https://yoursite.com/checkout/success'
});
```
## Best Practices
* **Choose a sensible minimum**: Cover costs while keeping participation easy
* **Use a suggested price**: Anchor expectations to improve average order value
* **Explain value clearly**: Tell buyers what their contribution unlocks
* **A/B test campaigns**: Time‑box offers and experiment with messaging
* **Monitor distribution**: Track paid amounts to refine minimum and suggested price
PWYW is a flexible way to invite more customers in while preserving a revenue floor. Start simple, test thoroughly, and iterate based on conversion, contribution distribution, and refund rates.
# Payment Methods
Source: https://docs.dodopayments.com/features/payment-methods
Complete guide to payment methods supported by Dodo Payments, including credit/debit cards, digital wallets, BNPL, Paypal, UPI/Rupay, European options, and how to configure them for your checkout.
Dodo Payments supports a wide range of payment methods to help you serve customers globally. All listed payment methods become available automatically once your merchant account is verified and live mode is enabled.
**Automatic Activation:** No additional configuration is needed from your side. All payment methods are pre-configured and managed by Dodo Payments. You can optionally restrict which methods appear at checkout using the API configuration described below.
## Supported Payment Methods
The following table provides a quick reference for all available payment methods, their regional availability, and key limitations:
| Payment Method | Supported Countries | Supported Currencies | Supports Subscription |
| :----------------------- | :------------------ | :----------------------------------------------- | :-------------------- |
| **Credit Cards** | Global | All currencies | Yes |
| **Debit Cards** | Global | All currencies | Yes |
| **Apple Pay** | Global (Excl. IN) | All currencies | Yes |
| **Google Pay** | Global (Excl. IN) | All currencies | Yes |
| **Amazon Pay** | Global (Excl. IN) | USD | No |
| **Cash App Pay** | US | USD | No |
| **Afterpay (BNPL)** | US, UK | USD, GBP | No |
| **Klarna (BNPL)** | US, Europe\* | USD, EUR, GBP, DKK, NOK, SEK, CZK, RON, PLN, CHF | No |
| **PayPal** | Global | EUR, GBP | No |
| **UPI** | India | INR | Yes\* |
| **Rupay (Credit/Debit)** | India | INR | Yes\* |
| **Bancontact** | Belgium | EUR | No |
| **EPS** | Austria | EUR | No |
| **iDEAL** | Netherlands | EUR | No |
| **Multibanco** | Portugal | EUR | No |
Each transaction must meet the minimum amount required for each currency. For USD, the minimum is 0.50 USD. Minimums for other currencies vary by region.
[See Supported Currencies & Minimums](/features/adaptive-currency#supported-currencies)
Klarna and Afterpay require a minimum transaction amount of 50.01 USD (or equivalent in supported currencies) for eligibility at checkout. Transactions below this threshold will not be eligible for these BNPL payment options.
For details on Klarna's supported countries, refer to the BNPL section below.
Indian Payment Methods: UPI and Rupay subscriptions are supported with RBI-compliant mandates. See the UPI and Rupay section for important details about one-time payment restrictions, mandate limits, and processing delays.
## Payment Method Details
Dodo Payments accepts all major credit and debit card networks globally. You can accept:
* **Visa** – Reliable, widely accepted global network
* **Mastercard** – Robust, secure network with global reach
* **American Express** – Premium benefits and strong customer service
* **Discover** – Competitive rewards, strong US presence
* **JCB** – Leading Asian card network with expanding reach
* **UnionPay** – Dominant in China and Asia, with growing global acceptance
* **Interac** – Canada's secure debit payment network
* **Cartes Bancaires** – Widely used across France
* **Diners Club** – Premium international card with exclusive benefits
* **Korean Local Cards** – Support for Korean domestic card networks
Offer customers a fast and secure checkout using digital wallets:
* **Apple Pay** – Pay easily on iOS, macOS, and watchOS
* **Google Pay** – Secure payments on Android, web, and in-store
* **PayPal** – Global payments in EUR and GBP (not available for subscriptions)
* **Cash App Pay** – Mobile payments for US customers (not available for subscriptions)
* **Amazon Pay** – Use Amazon credentials for a simple checkout (not available for subscriptions)
Increase conversion by offering flexible installment options:
* **Klarna** – Enable flexible payments with Klarna’s options: pay in 4 installments, pay in 30 days, or finance your purchase. Klarna is available in the US and 19 European countries, including Austria, Belgium, Czech Republic, Denmark, Finland, France, Germany, Greece, Ireland, Italy, Netherlands, Norway, Poland, Portugal, Romania, Spain, Sweden, Switzerland, and the United Kingdom. Supported currencies include USD, EUR, GBP, DKK, NOK, SEK, CZK, RON, PLN, and CHF.
* **Afterpay** – Allow customers to split purchases into four interest-free payments. Afterpay is available in the US and UK, supporting payments in USD and GBP.
Accept leading Indian payment options:
* **UPI** – Customers scan a QR code or use apps (Google Pay, PhonePe, Paytm, CRED) for instant, contactless bank transfers
* **Rupay Cards** – Secure credit and debit payments via India's domestic card network
### Subscriptions with RBI-Compliant Mandates
UPI and Indian card subscriptions operate under RBI (Reserve Bank of India) regulations with specific mandate requirements:
#### Mandate Limits
The mandate type and amount depend on your subscription's recurring charge:
* **Charges below ₹15,000:** We create an on-demand mandate for ₹15,000 INR. The subscription amount is charged periodically according to your subscription frequency, up to the mandate limit.
* **Charges ₹15,000 or above:** We create a subscription mandate (or on-demand mandate) for the exact subscription amount.
#### Upgrade and Downgrade Considerations
**Important:** When upgrading or downgrading subscriptions, carefully consider the mandate limits:
* If an upgrade/downgrade results in a charge amount that exceeds ₹15,000 and goes beyond the existing on-demand payment limit, the transaction charge may fail.
* In such cases, the customer may need to update their payment method or change the subscription again to establish a new mandate with the correct limit.
#### Authorization for High-Value Charges
For subscription charges of ₹15,000 or more:
* The customer will be prompted by their bank to authorize the transaction.
* If the customer fails to authorize the transaction, the transaction will fail and the subscription will be put on hold.
#### 48-Hour Processing Delay
**Processing Timeline:** Recurring charges on Indian cards and UPI subscriptions follow a unique processing pattern:
* Charges are **initiated** on the scheduled date according to your subscription frequency.
* The actual **deduction** from the customer's account occurs only after **48 hours** from payment initiation.
* This 48-hour window may extend up to **2-3 additional hours** depending on bank API responses.
#### Mandate Cancellation Window
During the 48-hour processing window:
* Customers can cancel the mandate via their banking apps.
* If a customer cancels the mandate during this period, the subscription will remain **active** (this is an edge case specific to Indian card and UPI AutoPay subscriptions).
* However, the actual deduction may fail, and in that case, we will put the subscription **on hold**.
**Edge Case Handling:** If you provide benefits, credits, or subscription usage to customers immediately upon charge initiation, you need to handle this 48-hour window appropriately in your application. Consider:
* Delaying benefit activation until payment confirmation
* Implementing grace periods or temporary access
* Monitoring subscription status for mandate cancellations
* Handling subscription hold states in your application logic
Monitor subscription webhooks to track payment status changes and handle edge cases where mandates are cancelled during the 48-hour window.
Offer localized European options to increase conversions:
* **iDEAL** (Netherlands) – Instant Dutch online bank payments
* **Bancontact** (Belgium) – Belgium's trusted debit card method
* **Multibanco** (Portugal) – Portuguese payment method for bank transfers and ATM payments
* **EPS** (Austria) – Direct online banking payments in Austria
## Configuring Payment Methods
While all payment methods are automatically available based on customer location and currency, you can optionally restrict which payment options appear during checkout using the `allowed_payment_method_types` parameter.
### When to Configure Payment Methods
You may want to configure allowed payment methods to:
* Control which payment options appear to customers based on your business requirements
* Restrict payment methods for specific product types or transaction flows
* Maintain consistency across different payment experiences
* Optimize checkout experience for your target audience
### Implementation
Include the `allowed_payment_method_types` field in your payment or subscription creation request:
```json theme={null}
{
"allowed_payment_method_types": [
"credit",
"debit",
"apple_pay"
]
}
```
Verify that only the specified payment methods appear at checkout.
Test payment method availability with different customer locations and currencies to ensure the configuration works as expected.
### API Endpoints
This configuration is supported by the following endpoints:
Create checkout sessions with allowed payment methods.
One-time payments with allowed methods.
Subscription payments with recurring methods.
### Implementation Examples
```json theme={null}
{
"amount": 1000,
"currency": "usd",
"allowed_payment_method_types": [
"credit",
"debit",
"apple_pay",
"google_pay"
]
}
```
```json theme={null}
{
"product_id": "prod_123",
"customer_id": "cust_456",
"allowed_payment_method_types": [
"credit",
"debit"
]
}
```
```json theme={null}
{
"amount": 5000,
"currency": "eur",
"allowed_payment_method_types": [
"credit",
"debit",
"ideal",
"bancontact",
"sepa"
]
}
```
### Best Practices
If `allowed_payment_method_types` is not specified, all eligible payment methods will be available based on customer location, currency, and transaction type. Only use this parameter when you need to restrict payment methods.
Consider your target market when selecting payment methods. Including region-specific options (like UPI for India or iDEAL for Netherlands) can significantly improve conversion rates in those markets.
Track which payment methods your customers use most frequently and adjust your allowed methods based on actual usage patterns and conversion data.
### Important Considerations
Payment method availability depends on multiple factors:
* **Customer Location**: Some payment methods are region-specific
* **Merchant Settings**: Your account configuration and capabilities
* **Currency**: Supported payment methods vary by currency
* **Transaction Type**: Some methods don't support subscriptions or have minimum amounts
Adding a payment method to `allowed_payment_method_types` doesn't guarantee it will appear - it must also be eligible based on these factors.
## Troubleshooting
**Possible causes:**
* The payment method is not included in `allowed_payment_method_types`
* The payment method is not supported for the customer's location
* The payment method doesn't support the transaction currency
* For subscriptions: the payment method doesn't support recurring payments
* The transaction amount is below the minimum threshold (e.g., BNPL methods)
**Solution:** Verify all eligibility requirements are met and check your API request configuration.
**Possible causes:**
* The `allowed_payment_method_types` parameter is not being passed correctly
* There may be conflicting configurations in your merchant dashboard
**Solution:** Review your API request payload and ensure the parameter is properly formatted as an array of strings.
**Possible causes:**
* The customer's location doesn't match the payment method's supported region
* The currency is not supported by the regional payment method
* Your merchant account may not be enabled for that specific payment method
**Solution:** Verify regional and currency compatibility, and contact support if you believe the method should be available.
**Subscription charge failed after upgrade/downgrade:**
* The new charge amount may exceed the existing mandate limit (₹15,000 threshold)
* Customer may need to update their payment method to establish a new mandate
**Solution:** Prompt the customer to update their payment method or adjust the subscription amount to stay within mandate limits.
**Subscription on hold after charge initiation:**
* Customer may have cancelled the mandate during the 48-hour processing window
* Customer may have failed to authorize a high-value charge (≥₹15,000)
**Solution:** Monitor webhook events for mandate cancellations and payment failures. Implement retry logic or prompt customers to re-establish their payment method.
**Payment deduction delayed:**
* Normal behavior: Indian card and UPI subscriptions have a 48-hour delay between charge initiation and actual deduction
* This delay may extend up to 2-3 additional hours depending on bank APIs
**Solution:** This is expected behavior. Do not activate benefits or credits until payment confirmation is received via webhooks.
## Expanding Payment Options
We continuously add new payment methods to Dodo Payments. This page will be updated as more options become available. If you require a specific payment method not listed here, please contact us at [support@dodopayments.com](mailto:support@dodopayments.com), and we'll consider adding it to our roadmap.
# Payouts
Source: https://docs.dodopayments.com/features/payouts/payout-structure
Consolidate your eligible earnings and manage your payout threshold directly from the dashboard.
## Introduction
The **Payouts Section** consolidates all eligible earnings that can be disbursed in the next payout cycle. It provides transparency on the net available balance after accounting for all fees, refunds, and taxes.
## Key Features
* Displays the total balance eligible for payout in the upcoming cycle in USD
* Automatically applies FX conversion for non-USD currencies
* Lets businesses **set or modify their payout threshold** directly from the wallet settings
The displayed amount is **for reference only**. Final payout amounts may vary based on the actual exchange rate at the time of transfer.
## Payout Cycles
We support the following payout cycles:
**Billing Period 1:** 1st to 15th of the month\
**Payout Date 1:** 18th of the same month
**Billing Period 2:** 16th to the end of the month\
**Payout Date 2:** 4th of the following month
**Billing Period 1:** 1st to 7th\
**Payout Date 1:** 11th of the same month
**Billing Period 2:** 8th to 14th\
**Payout Date 2:** 18th of the same month
**Billing Period 3:** 15th to 21st\
**Payout Date 3:** 25th of the same month
**Billing Period 4:** 22nd to the end of the month\
**Payout Date 4:** 4th of the following month
**Billing Period:** 1st to the end of the month\
**Payout Date:** 11th of the following month
**Note:**
* Payouts will only be initiated if all verifications are successful and Payout Threshold is crossed in the billing period.
* The default payout cycle for all businesses is **bi-monthly**.
* Businesses with higher transaction volumes may request **weekly** payouts by contacting [support@dodopayments.com](mailto:support@dodopayments.com).
* Certain high-risk businesses may be placed on a **monthly** payout cycle at our discretion.
## Payout Processing and Bank Timelines
1. The dates provided above are the **initiation dates** for payouts.
2. It typically takes **1-2 business days** for the amount to appear in your bank account.
3. Delays may occur if payout processing dates fall on weekends or bank holidays.
## Minimum Payout Threshold
| Currency | Minimum Threshold |
| -------- | ----------------- |
| USD | \$50 |
| INR | ₹1,000 |
If your balance does not meet the threshold, it will carry forward to the next payout cycle.
## Flexible Minimum Payout Threshold
You can customize the balance that must build up in your USD wallet before Dodo Payments triggers a payout.
**Configuration:**
* **Allowed values:** \$50 or any higher amount
* **Effective from:** Your current payout cycle
* **Note:** Payouts already scheduled (if entry is created in the payout table) remain unchanged
### How to Edit Your Threshold
Go to **Payouts → Payouts Page → Minimum Threshold**
Choose edit threshold and enter a number **≥ 50** (e.g., "250")
Click **Confirm** to save your new threshold
You will see the updated amount in the Minimum Payout Threshold field
This feature is only applicable to the USD/EUR/GBP Wallet as of now. The INR wallet will have no changes.
## Payout Deductions
The following amounts are deducted from your sales before payout:
**Taxes:**
All applicable taxes as per the Tax Calculator will be deducted from the sales amount before payout.
**Fees:**
Dodo Payment Fees and Payout Fees will be deducted before sending the payout.
For detailed fee information, check out our [Pricing Page](https://dodopayments.com/pricing).
## Payout Status
Track the progress of your payouts through the following status indicators:
| Status | Description |
| ------------- | ---------------------------------------------------------------------------------------- |
| Not Initiated | Payout due date has not yet arrived hence it has not been initiated |
| In Progress | Payout due date has arrived and payout is in progress |
| On Hold | Payout is put on hold by the Payout Team (by backend until admin dashboard is available) |
| Successful | Payout to the business account is processed successfully |
| Failed | Payout to the business account has failed |
## Payout Invoice
A reverse invoice is generated to indicate the amount paid by Dodo Payments to the business.
### Invoice Structure
## Important Disclaimer for Indian Businesses
* **INR Payouts to businesses in India are handled independently** and are **not part of the consolidated USD/GBP/EUR payout cycle**.
* Businesses with Indian bank accounts will receive **two separate payouts** per cycle:
* One for INR Wallet (domestic payout)
* One for global currencies (USD/GBP/EUR consolidated payout)
* Each payout will generate a separate invoice and entry in the balance ledger.
Get a comprehensive overview of all your USD, GBP, EUR, and INR wallet balances, transaction ledgers, and payout-related financial activity.
# Products
Source: https://docs.dodopayments.com/features/products
Create one-time, subscription, or usage-based products in minutes. Manage pricing, media, fields, and automated entitlements - all in one place.
Products are the foundation of Dodo Payments. Whether you sell a one‑time download, a recurring subscription, or usage‑based access, you start by creating a product. Each product defines how it’s priced, presented at checkout, and fulfilled after purchase.
Charge once for lifetime access or a single deliverable.
Bill on a schedule with trials, proration, and add‑ons.
Meter consumption and bill by actual usage.
## Create a product
You can create products from the dashboard or via API. Choose the pricing model up‑front, One‑Time, Subscription, or Usage‑Based and then configure details. The pricing model can’t be changed later; create a new product if you need a different model.
Provide a clear title and a concise value‑oriented description. Markdown is supported in descriptions.
Keep the first sentence customer‑facing and outcome‑oriented; it appears prominently in checkout.
Select the pricing model:
* **One‑Time**: Fixed price paid once.
* **Subscription**: Recurring price with interval and optional trial.
* **Usage‑Based**: Price derived from metered events.
Then set pricing:
* **Price**: Base amount and currency.
* **Discount (%)**: Optional inline discount shown in checkout and invoices.
* For subscriptions, set **Repeat every** (e.g., 1 month or 1 year) and **Trial days** if needed.
Changing price affects only new purchases. Existing subscriptions follow plan‑change rules.
Upload images to showcase the product on checkout and invoices. Supported PNG/JPG/WebP up to 3 MB. Reorder or replace any time.
Attach fulfillment that activates automatically after payment:
* **License Keys**: Issue and validate unique keys
* **File Downloads**: Grant secure file access
* **Custom**: Trigger your own entitlement logic via webhooks
Add or remove benefits as your offer evolves. Existing subscribers gain or lose access accordingly.
## Variants and pricing options
Instead of variants under one product, create separate products for each pricing option (for example, Monthly and Yearly). You can present multiple products side‑by‑side in the same checkout and let customers switch between them.
This keeps APIs predictable and simplifies reporting. Each product has a single pricing model.
## Managing products
You can manage products through the dashboard or programmatically via API. The API provides full control over product creation, updates, retrieval, image uploads, and archiving.
### Dashboard Management
* **Update**: Edit name, description, images, price, fields, and benefits at any time (pricing model is immutable).
* **Archive**: Hide a product from new purchases without disrupting existing customers. You can unarchive later.
### API Management
The following instructions allow you to create, update, manage, and retrieve products, including uploading images.
A product can be a one-time item or a subscription-based service. To create a new product, send a `POST` request to the `/products` endpoint with details such as name, description, price, currency, and whether it is a recurring product.
For recurring products, set `recurring: true` and specify a `billing_cycle` (`daily`, `weekly`, `monthly`, `yearly`).
View detailed request and response structure in the Create Product API documentation.
To modify an existing product, send a `PATCH` request to the `/products/{product_id}` endpoint. You can update properties such as name, price, and description while keeping other details unchanged.
Ensure that the `product_id` in the endpoint matches an existing product.
View detailed request and response structure in the Update Product API documentation.
You can fetch a list of products stored in your account using a `GET` request to the `/products` endpoint. This allows you to retrieve product details, including active and archived products.
View detailed request and response structure in the Retrieve Products API documentation.
You can associate an image with a product by uploading it to AWS S3 using a pre-signed URL provided by the API. First, request an image upload URL from the `/products/{product_id}/images` endpoint, then use the provided URL to upload the image within 60 seconds.
The pre-signed URL expires in 60 seconds, so the image must be uploaded within that timeframe.
Once the pre-signed URL is received from the API, upload the image using the `PUT` method. This ensures secure and temporary access to AWS S3 for uploading the image.
**Supported libraries for uploading to S3:**
* **Node.js**: `axios`, `node-fetch`
* **Python**: `requests`, `boto3`
* **Go**: `net/http`
* **PHP**: `GuzzleHttp`
* **Ruby**: `rest-client`
If the upload is successful, AWS S3 will return a `200 OK` status, indicating that the image has been stored successfully.
View detailed request and response structure in the Upload Product Image API documentation.
If you no longer wish to display or use a product, you can archive it using a `POST` request to the `/products/{product_id}/archive` endpoint. This action hides the product but does not delete it permanently.
View detailed request and response structure in the Archive Product API documentation.
If you need to restore an archived product, send a `POST` request to the `/products/{product_id}/unarchive` endpoint. This will reactivate the product and make it available for use again.
View detailed request and response structure in the Unarchive Product API documentation.
Create payment or subscription flows from products, and fulfill automatically via benefits and webhooks.
Create checkout sessions for one‑time or subscription purchases.
React to payment lifecycle events.
Handle subscription created, renewed, and canceled events.
## Best practices
* **Start with clarity**: Separate products for each pricing option (Monthly vs Yearly)
* **Use trials thoughtfully**: Pair trials with onboarding to drive activation
* **Automate fulfillment**: Use benefits and webhooks to deliver instantly
* **Tag with metadata**: Store your system IDs for reconciliation
You’re ready to create products and start selling - one‑time, recurring, or by usage.
# Purchasing Power Parity
Source: https://docs.dodopayments.com/features/purchasing-power-parity
Learn how to implement Purchasing Power Parity pricing with ParityDeals integration to offer location-based discounts for your customers on Dodo Payments
## Introduction
[ParityDeals](https://www.paritydeals.com/) is a platform that enables automatic pricing optimization based on a customer's geographical location, commonly known as "purchasing power parity" pricing.
With ParityDeals, you can also offer [automatic time-based discounts](https://www.paritydeals.com/features/time-based-discounts/) and [holiday-based promotions](https://www.paritydeals.com/features/holiday-based-discounts/) throughout the year.
While you can't directly embed the ParityDeals banner on a Dodo Payments storefront (as custom scripts aren't supported), you can still implement ParityDeals effectively on your own website or application.
## How It Works
When ParityDeals is integrated into your website, eligible visitors will see a banner that includes a location-based discount code they can use at checkout.
These discount codes are automatically created and managed by ParityDeals and will appear in your **Dodo Payments Dashboard** under **Sales → Discounts**.
## Step-by-Step Setup Guide
### 1. Sign up on ParityDeals
Visit [app.paritydeals.com](https://app.paritydeals.com/) and create an account.
### 2. Create Deals
After signing in, locate the option to create deals. From the dropdown menu, select:
**→ "Create Deals via Dodo Payments"**
### 3. Generate a Dodo Payments API Key
Log in to your **Dodo Payments dashboard** and navigate to:
**Developers → API Keys**
Click **"Create New API"** and name it something descriptive like "ParityDeals".
### 4. Connect Your API Key to ParityDeals
Copy the API key you created and paste it into the ParityDeals interface to establish the connection with your Dodo Payments account.
### 5. Select Products
Once connected, ParityDeals will fetch your Dodo Payments product catalog. Select the products you want to associate with your deals.
### 6. Configure Deal Settings
Enter your website URL and optionally select specific target pages where the discount banner should appear.
Then choose your preferred deal type:
* Location-based deals
* Holiday deals
* Time-based flash deals
### 7. Set Country-Based Rules
Select the countries you want to offer localized discounts to and define the discount percentage for each. ParityDeals provides recommended defaults that you can adjust as needed.
### 8. Customize the Banner
Design your discount banner by:
* Customizing the text
* Selecting colors and layout
* Choosing the banner position (top or bottom of page)
This banner will be displayed to eligible visitors based on your configured rules.
### 9. Add the ParityDeals Script to Your Website
After saving your deal, ParityDeals will generate a script. Add this script to your website code (typically in the `` section).
This script automatically detects visitor locations and displays the appropriate discount banner.
## All Set!
You're now ready to offer:
* **Location-based deals** - Adjust pricing based on purchasing power
* **Countdown/time-sensitive discounts** - Create urgency with limited-time offers
* **Holiday promotions** - Capitalize on seasonal shopping trends
All of these are automated through ParityDeals and work seamlessly with Dodo Payments.
For more detailed information, refer to the [ParityDeals Documentation](https://www.paritydeals.com/docs/) or contact their support team.
# Storefront
Source: https://docs.dodopayments.com/features/storefront
The Storefront Feature allows you to create a simple, branded, and mobile-friendly online store where you can showcase both One-Time Payment products and Subscription products. This feature eliminates the complexity of building an entire website, enabling you to quickly offer your products or services online without additional web development efforts.
## Key Highlights of the Feature
1. **Branded Storefront**
* Customize your store with a Store Name, Logo, and Cover Image.
* Easily share your store URL (e.g., `store.dodopayments.com/your-store`).
2. **Product Segregation**
* Automatically organizes your products into One-Time Payment and Subscription sections.
3. **Simple Checkout Flow**
* Customers can select a product & quantity and click **“Buy Now”** to be redirected to Dodopayments checkout.
4. **Publish/Unpublish**
* Control which products appear on your store: quickly publish or unpublish within the Merchant Dashboard.
5. **No Extra Setup**
* You don’t need a separate CMS; all product management happens in your Merchant Dashboard, where you already manage pricing, descriptions, and subscription details.
6. **Mobile-Friendly**
* The storefront layout is responsive, ensuring a seamless shopping experience on phones, tablets, and desktops.
## Store Front Setup
Below is the step-by-step process for merchants to set up and manage their storefront:
### Accessing Your Storefront Settings
1. Open Business Section → Select Store Front
### Store Branding
1. **Store Name**: Enter the public-facing name of your store. This appears prominently at the top of your storefront.
2. **Logo**: Upload an image (e.g., PNG or JPG) for your brand logo.
3. **Cover Image**: Select or upload a banner/cover image that appear on the storefront homepage.
4. **Store URL**:
* Typically auto-generated based on your business name (e.g., `business.dodopayments.com/my-store`).
* You can edit this URL if the system supports custom slugs.
5. **Products:** You can select the Products you want to display on your storefront
### Saving & Viewing Your Store
1. **Click Publish**: Click Publish to confirm your storefront setup and product publishing decisions.
2. **Preview Store**: You can preview your storefront at the generated URL.
3. **Share URL**: Once satisfied, share the link with your customers via email, social media, or your website.
### Test Mode
Test Mode and Live Mode have separate storefronts. You can see the test mode indicator on the top of your website and store front generated through test mode as well.
Test Mode is for you try out the user flow and purchase experience on the store. Please share the live mode Store link with your users to facilitate real purchases
## Customer Experience
### Landing on the Storefront
* **Visual Branding**: Customers see your store name, logo, and cover image immediately.
* **Organized Products**: Two categories are displayed: One-Time Payment and Subscription.
### Browsing Products
* **Product Cards**: Each product shows a name, description, and price. Description can be viewed by clicking the i icon on the bottom right of the card.
* **Quantity Selection**: Customers can pick how many items they want to buy.
* **Subscription Frequency & Trial**: Subscription products explicitly state billing frequency (e.g., monthly) and if a trial is available (e.g., 7-day free trial).
### Buying Process
1. **Select “Buy Now”**: Once ready, the customer clicks “Purchase” for the desired product, selects quantity and clicks “Buy Now”
2. **Redirect to Dodopayments checkout**:
* The customer will be taken to the dodo payments checkout
* The customer completes the secure checkout flow on Dodopayments.
3. **Confirmation**:
* After successful payment, Dodopayments shows a confirmation.
* Optionally, the customer may be redirected back to a “Thank You” page on your storefront based on the redirect URL entered.
# Subscriptions
Source: https://docs.dodopayments.com/features/subscription
Offer recurring billing for memberships, SaaS, or services with flexible cycles, trials, proration, add-ons, and on‑demand charges.
Subscriptions let you sell ongoing access with automated renewals. Use flexible billing cycles, free trials, plan changes, and add‑ons to tailor pricing for each customer.
Control plan changes with proration and quantity updates.
Authorize a mandate now and charge later with custom amounts.
Let customers manage plans, billing, and cancellations.
React to lifecycle events like created, renewed, and canceled.
## What Are Subscriptions?
Subscriptions are recurring products customers purchase on a schedule. They’re ideal for:
* **SaaS licenses**: Apps, APIs, or platform access
* **Memberships**: Communities, programs, or clubs
* **Digital content**: Courses, media, or premium content
* **Support plans**: SLAs, success packages, or maintenance
## Key Benefits
* **Predictable revenue**: Recurring billing with automated renewals
* **Flexible cycles**: Monthly, annual, custom intervals, and trials
* **Plan agility**: Proration for upgrades and downgrades
* **Add‑ons and seats**: Attach optional, quantifiable upgrades
* **Seamless checkout**: Hosted checkout and customer portal
* **Developer-first**: Clear APIs for creation, changes, and usage tracking
## Creating Subscriptions
Create subscription products in your Dodo Payments dashboard, then sell them through checkout or your API. Separating products from active subscriptions lets you version pricing, attach add‑ons, and track performance independently.
### Subscription product creation
Configure the fields in the dashboard to define how your subscription sells, renews, and bills. The sections below map directly to what you see in the creation form.
#### Product details
* **Product Name** (required): The display name shown in checkout, customer portal, and invoices.
* **Product Description** (required): A clear value statement that appears in checkout and invoices.
* **Product Image** (required): PNG/JPG/WebP up to 3 MB. Used on checkout and invoices.
* **Brand**: Associate the product with a specific brand for theming and emails.
* **Tax Category** (required): Choose the category (for example, SaaS) to determine tax rules.
Pick the most accurate tax category to ensure correct tax collection per region.
#### Pricing
* **Pricing Type**: Choose Subscription (this guide). Alternatives are Single Payment and Usage Based Billing.
* **Price** (required): Base recurring price with currency.
* **Discount Applicable (%)**: Optional percentage discount applied to the base price; reflected in checkout and invoices.
* **Repeat payment every** (required): Interval for renewals, e.g., every 1 Month. Select the cadence (months or years) and quantity.
* **Subscription Period** (required): Total term for which the subscription remains active (e.g., 10 Years). After this period ends, renewals stop unless extended.
* **Trial Period Days** (required): Set trial length in days. Use 0 to disable trials. The first charge occurs automatically when the trial ends.
* **Select add‑on**: Attach up to 3 add‑ons that customers can purchase alongside the base plan.
Changing pricing on an active product affects new purchases. Existing subscriptions follow your plan‑change and proration settings.
Add‑ons are ideal for quantifiable extras such as seats or storage. You can control allowed quantities and proration behavior when customers change them.
#### Advanced settings
* **Tax Inclusive Pricing**: Display prices inclusive of applicable taxes. Final tax calculation still varies by customer location.
* **Generate license keys**: Issue a unique key to each customer after purchase. See the License Keys guide.
* **Digital Product Delivery**: Deliver files or content automatically after purchase. Learn more in Digital Product Delivery.
* **Metadata**: Attach custom key–value pairs for internal tagging or client integrations. See Metadata.
Use metadata to store identifiers from your system (e.g., accountId) so you can reconcile events and invoices later.
## API Management
Use `POST /subscriptions` to create subscriptions programmatically from products, with optional trials and add‑ons.
View the create subscription API.
Use `PATCH /subscriptions/{id}` to update quantities, cancel at period end, or modify metadata.
Learn how to update subscription details.
Change the active product and quantities with proration controls.
Review plan change options.
For on‑demand subscriptions, charge specific amounts on demand.
Charge an on‑demand subscription.
Use `GET /subscriptions` to list all subscriptions and `GET /subscriptions/{id}` to retrieve one.
Browse listing and retrieval APIs.
Fetch recorded usage for metered or hybrid pricing models.
See usage history API.
## Common Use Cases
* **SaaS and APIs**: Tiered access with add‑ons for seats or usage
* **Content and media**: Monthly access with introductory trials
* **B2B support plans**: Annual contracts with premium support add‑ons
* **Tools and plugins**: License keys and versioned releases
## Integration Examples
### Checkout Sessions (subscriptions)
When creating checkout sessions, include your subscription product and optional add‑ons:
```typescript theme={null}
const session = await client.checkoutSessions.create({
product_cart: [
{
product_id: 'prod_subscription',
quantity: 1
}
]
});
```
### Plan changes with proration
Upgrade or downgrade a subscription and control proration behavior:
```typescript theme={null}
await client.subscriptions.changePlan('sub_123', {
product_id: 'prod_new',
quantity: 1,
proration_billing_mode: 'difference_immediately'
});
```
### Cancel at period end
Schedule a cancellation without immediate termination of access:
```typescript theme={null}
await client.subscriptions.update('sub_123', {
cancel_at_period_end: true
});
```
### On‑demand subscriptions
Create an on‑demand subscription and charge later as needed:
```typescript theme={null}
const onDemand = await client.subscriptions.create({
customer_id: 'cus_123',
product_id: 'prod_on_demand',
on_demand: true
});
await client.subscriptions.createCharge(onDemand.id, {
amount: 4900,
currency: 'USD',
description: 'Extra usage for September'
});
```
## Best Practices
* **Start with clear tiers**: 2–3 plans with obvious differences
* **Communicate pricing**: Show totals, proration, and next renewal
* **Use trials thoughtfully**: Convert with onboarding, not just time
* **Leverage add‑ons**: Keep base plans simple and upsell extras
* **Test changes**: Validate plan changes and proration in test mode
Subscriptions are a flexible foundation for recurring revenue. Start simple, test thoroughly, and iterate based on adoption, churn, and expansion metrics.
# Tax Inclusive Pricing
Source: https://docs.dodopayments.com/features/tax-inclusive-pricing
Show final, tax‑inclusive prices upfront. Dodo calculates tax by location and clearly breaks it out at checkout and on invoices.
## What Is Tax Inclusive Pricing?
Tax Inclusive Pricing lets you embed applicable taxes directly in your displayed product price. Customers see one final amount upfront for a more transparent, conversion‑friendly experience. At checkout, Dodo calculates the tax‑exclusive portion and tax amount from the displayed price based on customer location, and clearly breaks them out on the invoice.
Final tax still varies by customer location and tax category. Checkout and invoices show the calculated tax portion derived from the displayed price.
## Key Benefits
* **Clarity for customers**: One final price displayed upfront
* **Compliance by region**: Tax is calculated per location and broken out
* **Accurate invoicing**: Invoices show net amount and tax derived from price
* **Conversion friendly**: Reduced surprises at checkout
## Product Setup
Create or edit a product in your dashboard, then enable tax‑inclusive pricing under pricing settings.
#### Pricing
* **Price** (required): Base price that is displayed to the customer.
* **Tax Inclusive Pricing**: Toggle on to treat the displayed price as tax inclusive. Dodo will compute the net (pre‑tax) amount and tax portion at checkout based on customer location and tax category.
Only future purchases reflect updated tax‑inclusive settings. Existing purchases and active subscriptions are not retroactively changed.
Use consistent product copy such as “Tax inclusive” so customers understand totals at a glance.
## API Management
Use `POST /products` to create products with `tax_inclusive` set to `true`.
Create product via API
Use `PATCH /products/{id}` to toggle `tax_inclusive` on existing products.
Update product via API
Use `POST /checkout-sessions/create` to sell products marked as tax inclusive. The displayed price remains the same; checkout derives net amount and tax.
Create checkout session
Use `POST /refunds` to issue refunds. You can specify whether the refund is tax inclusive using the `tax_inclusive` flag when applicable.
Create a refund
## Integration Examples
### Create a product with tax‑inclusive pricing
```typescript theme={null}
const product = await client.products.create({
name: 'Pro Plan',
description: 'All features included',
price: {
currency: 'USD',
price: 10000
tax_inclusive: true
}
});
```
### Update an existing product to toggle tax inclusive
```typescript theme={null}
await client.products.update(product.id, {
price: {
tax_inclusive: true
}
});
```
## Best Practices
* **Choose the right tax category** to ensure correct calculation per region.
* **Communicate totals** clearly with “Tax Inclusive” labels in product copy.
* **Test in sandbox** to validate.
## Related
Manage pricing options across products.
Configure recurring products with advanced settings.
Offer promotions while keeping invoice clarity.
# Team Members
Source: https://docs.dodopayments.com/features/team
The Team feature enables you to add teammates to your Dodo Payments dashboard, granting them access based on customizable permissions. You can allow full modification rights or restrict access to view-only mode, ensuring secure and flexible collaboration.
## How to add a teammate?
1. Go to Business → Team.
2. Click on +**invite** button on the top right corner.
3. Provide the teammates email and assign a role to them.
4. Click on **Send Invite**.
## How can a teammate access the dashboard?
1. The teammate receives an invitation link via email and clicks on it.
2. If they do not have a Dodo Payments account, they are directed to the signup page where they need to provide the required details. If they already have a Dodo Payments account they can access the business through the business toggle on the bottom left.
3. After completing the signup process, they gain access to the dashboard.
## Roles and Permissions
The Dodo Dashboard has three types of user roles, each with different levels of access. The role icon of the user can be seen in the bottom left of the dashboard.
### 1. Owner
The **Owner** has the highest level of access and full control over the account. Only the account owner holds this role and has the exclusive ability to invite new users. The ownership of the account cannot be transferred.
### 2. Editor
Editors have almost the same permissions as the Owner, except they cannot invite new Editors. However, they can invite and remove users with the Viewer role. Additionally, they can create, edit, and delete products and make other modifications within the dashboard.
### 3. Viewer
Viewers have read-only access to the dashboard. They can view all information, including products, transactions, and reports, but cannot add, edit, or delete products or make any modifications.
# Disputes
Source: https://docs.dodopayments.com/features/transactions/disputes
The Disputes section is dedicated to managing customer disputes related to payments. Merchants can view and manage disputes from the Transactions page, including tracking the status of chargebacks and responding to disputes.
## Introduction
The disputes section provides a detailed view of all the disputes raised along with the current status of these disputes. You will have to manage the dispute resolution at your end but the dashboard will provide a visibility into the status, reason for dispute, dispute ID for convenience of access to information.
## Viewing and Understanding Disputes
Go to the Transactions page in your dashboard and select the Disputes tab. This displays a list of all disputes associated with your account.
Click the eye icon next to any dispute entry to open a detailed view. Here, you can review the dispute reason, status, dispute ID, and other relevant information.
In the dispute details, locate the Associated Payment field. Click the eye icon here to view the full payment details related to the dispute.
## Responding to a Dispute
When a dispute is raised against your transaction, follow these steps to respond effectively:
1. **Check Your Dashboard**
* Navigate to the transactions → disputes section
* Look for the new dispute entry
2. **Review Email Notification**
* Check your registered email for dispute creation notification
3. **Choose Your Response**
* You can either accept or counter the dispute
4. **If Accepting**
* No further action required
* You will lose the dispute
5. **If Countering**
* Prepare to submit evidence
* Follow the best practices below
You have 4 days to respond to a dispute after creation
## Dispute Response Best Practices
Review the dispute reason carefully. Common reasons include:
The customer claims they did not authorize the transaction
The customer claims they did not receive the product/service after payment
The customer claims the product/service differs from what was advertised
Your response must directly address the specific reason. Avoid submitting generic or unrelated evidence.
Focus on customer identity, access, and usage evidence.
* **Invoice** of the purchase
* **IP address, device, and location** details at time of purchase
* **Login timestamps** showing access to your platform or service
* **Browser/device fingerprint** or account activity logs
* Previous successful transactions from the same customer or account
* Terms of Service and Refund Policy accepted at time of purchase
* **Invoice** of the purchase
* Screenshot or logs showing **successful login**
* Timestamped activity (downloads, content viewed, sessions started)
* Emails or support messages confirming access was granted
* Welcome emails or onboarding steps completed by the customer
* Internal logs showing product/service was delivered as promised
* **Invoice** of the purchase
* Product/service description from your website at time of purchase
* Comparison of what was promised vs. what was delivered
* Documentation or screenshots of what was accessed
* Any customer communication acknowledging receipt or usage
* Refund policy and support options made available to the customer
* What the customer purchased
* When and how they accessed it
* Why the dispute claim is inaccurate
Keep your language factual and professional. Avoid emotional or accusatory statements.
"We offer a digital image generation from text service using AI. The customer purchased a monthly subscription on March 5 and accessed the product multiple times from March 5–10 and generated 4 images in the duration. Please see attached access logs and the accepted terms showing our no-refund policy."
* Use **PDF, JPG, or PNG** formats only
* Clearly **label each file** (e.g., `Login_Log_March_5.pdf`)
* Submit only **relevant** and **organized** files
* Keep files under 5 MB each
* Do **not include links or URLs**
* Ignoring the actual dispute reason
* Using vague or emotional statements
* Uploading unrelated or unstructured files
* Providing links instead of files
* Include **timestamps with timezone** to prove access timelines
* Highlight any **two-factor authentication** or verification steps
* Share **refund refusal communication** if it aligns with your policy
* Keep your policies clearly visible and easy to prove were accepted
# Payments
Source: https://docs.dodopayments.com/features/transactions/payments
The Payments section provides merchants with a detailed overview of all payment transactions. Merchants can track payment statuses, initiate refunds, and access payment details for each transaction.
## **Payments Listing Page**
The Payments listing page provides a record of all the payments that have taken place and includes important information about the payment like the transaction amount, payment status and payment method along with the invoice for reference to the purchase associated with the payment.
* The following Payment statuses are shown:
* **In Progress**: The payment is currently being processed.
* **Failed**: The payment attempt was unsuccessful.
* **Successful**: The payment was completed successfully.
The payment method and timestamp are updated once the payment is completed.
***
## **Payment Details Page**
Upon clicking a payment in the listing, merchants are directed to the **Payment Details Page**, which provides a comprehensive breakdown of the transaction
## **Important Information**
* **Automatic Activation**: All the payment methods listed above will become available to customers automatically once the merchant's account is verified and live mode is enabled.
* **No Additional Configuration Required**: Merchants do not need to configure these payment options, as they are pre-defined and managed by Dodo Payments.
* **Additional Payment Methods**: We are constantly adding more payment methods to our roster. We will keep updating this page as and when we add more methods. If you have a requirement for any particular payment method, please write to us at [support@dodopayments.com](mailto:support@dodopayments.com) and we will add this to our roadmap if not present.
## How to Navigate and Use the Payments Page
Open the **Transactions** page and click on the **Payments** tab to view all payment transactions.
Use the available filters to narrow down your payment list:
* **Status filter**: View payments by status.
* **Date range**: Select custom date ranges to view payments from specific periods
Click on the **Array icon** next to any payment to access the detailed payment information.
On the payment details page, you can view:
* **Transaction details**: Amount, currency, payment method, and timestamp
* **Customer information**: Name, email, and billing details
* **Payment status**: Current status with timestamp of status changes
To download the associated invoice, click the **download Invoice** (📥) on the invoice preview section.
# Refunds
Source: https://docs.dodopayments.com/features/transactions/refunds
The Refunds section allows merchants to manage and initiate refunds for transactions. Merchants can initiate full or partial refunds directly from the transaction details view, with built-in rules ensuring compliance and protecting against errors.
## Initiate Refund
You can initiate two types of refunds from the Payment Details Page:
* **Full refund**: Refunds the entire payment amount to the customer.
* **Partial refund**: Refunds only a portion of the payment amount, as specified by you.
To begin, click the **Initiate Refund** button. The refund will only proceed if all refund rules are met.
Partial refunds are useful when only a portion of the order needs to be refunded.
### Details Required for Refund
* **Refund Amount**: The amount to be refunded. For partial refunds, enter the specific amount you wish to return to the customer. The system will prevent you from refunding more than the original payment.
* **Refund Reason**: The reason for the refund, to be recorded in the system.
### Refund Rules
1. The payment must have been successful.
2. Refunds (full or partial) must be initiated within 30 days of the transaction date. Initiating refunds will not be allowed post that.
3. Refunds are issued to the original payment method used by the customer.
If any rule is violated, the refund request will be denied. Please reach out to support for assistance if needed.
## Refunds Page
The **Refunds Page** provides a detailed view of all refund transactions:
* Clicking the **Associated Payment ID** will take the merchant to the **Payment Details Page** for that transaction.
* Merchants can export the refund report in CSV format for a specified date range.
## Refund Receipt
Once a refund is successfully processed, a **Refund Receipt** is automatically sent to the customer.
**Refund Receipt Template:**
# Event Ingestion
Source: https://docs.dodopayments.com/features/usage-based-billing/event-ingestion
Send usage events from your application to track customer consumption and power billing.
Events are the foundation of usage-based billing. Send events when billable actions occur, and meters aggregate them into charges.
Complete API documentation with examples and response codes.
## Event Structure
Unique identifier. Use UUIDs or combine customer ID + timestamp + action.
Dodo Payments customer ID. Must be a valid existing customer.
Event type that matches your meter's event name (case-sensitive). Examples: `api.call`, `image.generated`
ISO 8601 timestamp. Defaults to server time if omitted. Include for accurate billing with delayed/batch events.
Additional properties for aggregation and filtering:
* Numeric values: `bytes`, `tokens`, `duration_ms`
* Filters: `endpoint`, `method`, `quality`
```javascript theme={null}
metadata: {
endpoint: "/v1/orders",
method: "POST",
tokens: 1024
}
```
## Sending Events
```javascript Single Event theme={null}
await fetch('https://test.dodopayments.com/events/ingest', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.DODO_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
events: [{
event_id: "api_call_1234",
customer_id: "cus_abc123",
event_name: "api.call",
metadata: { endpoint: "/v1/orders" }
}]
})
});
```
```javascript Batch Events theme={null}
await fetch('https://test.dodopayments.com/events/ingest', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.DODO_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
events: [
{ event_id: "call_1", customer_id: "cus_abc", event_name: "api.call" },
{ event_id: "call_2", customer_id: "cus_abc", event_name: "api.call" },
{ event_id: "call_3", customer_id: "cus_abc", event_name: "api.call" }
]
})
});
```
```python Python theme={null}
import requests
requests.post(
'https://test.dodopayments.com/events/ingest',
headers={'Authorization': f'Bearer {api_key}', 'Content-Type': 'application/json'},
json={'events': [{'event_id': 'call_1', 'customer_id': 'cus_abc', 'event_name': 'api.call'}]}
)
```
```curl cURL theme={null}
curl -X POST 'https://test.dodopayments.com/events/ingest' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"events": [{"event_id": "call_1", "customer_id": "cus_abc", "event_name": "api.call"}]}'
```
Batch up to 100 events per request for better performance.
## Ingestion Blueprints
Ready-made event patterns for common use cases. Start with a proven blueprint instead of building from scratch.
Track AI token usage across OpenAI, Anthropic, Groq, Gemini, and more.
Meter API requests with endpoint filtering and rate limiting support.
Track file uploads and storage consumption for cloud storage services.
Measure streaming bandwidth for video, audio, and real-time data.
Bill by elapsed time for serverless functions and compute instances.
See all available blueprints with detailed implementation guides.
## Best Practices
Use deterministic IDs to prevent duplicates: `${customerId}_${action}_${timestamp}`
Retry on 5xx errors with exponential backoff. Don't retry 4xx errors.
Omit for real-time events. Include for delayed/batch events for accuracy.
Track success rates and queue failed events for retry.
## Troubleshooting
* Event name must exactly match meter (case-sensitive)
* Customer ID must exist
* Check meter filters aren't excluding events
* Verify timestamps are recent
Verify API key is correct and use format: `Bearer YOUR_API_KEY`
Ensure all required fields are present: `event_id`, `customer_id`, `event_name`
* Metadata keys must match meter's "Over Property" exactly
* Use numbers, not strings: `tokens: 150` not `tokens: "150"`
## Next Steps
Define how your events are aggregated into billable quantities with filters and aggregation functions.
Use ready-made blueprints for common use cases like LLM tracking, API gateways, and storage.
Build a full AI image generator with usage-based billing from scratch.
Complete API documentation with all parameters, response codes, and interactive testing.
# Ingestion Blueprints
Source: https://docs.dodopayments.com/features/usage-based-billing/ingestion-blueprints
Ready-made event ingestion patterns for common use cases with sample code.
Ingestion blueprints are pre-built patterns for tracking usage in common scenarios. Instead of figuring out event structures and meter configurations from scratch, start with a proven blueprint and customize it for your needs.
## What Are Blueprints?
Each blueprint includes:
* **Event structure recommendations**: Optimal event naming and metadata design
* **Meter configuration**: Suggested aggregation types and filters
* **Sample code**: Working implementation examples in multiple languages
* **Best practices**: Tips for accuracy, performance, and cost optimization
Blueprints save hours of setup time and help you avoid common pitfalls. Start with a blueprint, then adapt it to your specific requirements.
## Why Use Blueprints?
Get usage tracking working in minutes instead of hours. Copy proven patterns instead of designing from scratch.
Avoid common mistakes with tested event structures, proper metadata, and optimal aggregation choices.
Sample code is based on real-world implementations. Just add your API keys and go.
## Available Blueprints
Choose the blueprint that matches your use case:
Track AI token usage across OpenAI, Anthropic, Groq, Gemini, Vercel AI SDK, and more.
**Perfect for**: AI chatbots, code generators, content creation tools, AI-powered apps.
**What you'll track**: Input tokens, output tokens, model usage, request counts.
Meter API requests at the gateway level with endpoint filtering and rate limiting support.
**Perfect for**: REST APIs, GraphQL services, webhook providers, API-first platforms.
**What you'll track**: Request counts, endpoint usage, HTTP methods, response times.
Track file uploads, storage consumption, and bandwidth for cloud storage services.
**Perfect for**: File hosting, media platforms, backup services, document management.
**What you'll track**: Bytes stored, file uploads, downloads, storage snapshots.
Measure streaming bandwidth for video, audio, and real-time data delivery.
**Perfect for**: Video streaming, audio platforms, live broadcasts, real-time data feeds.
**What you'll track**: Bandwidth consumed, stream duration, quality levels, concurrent streams.
Bill by elapsed time for compute resources, serverless functions, and long-running jobs.
**Perfect for**: Serverless platforms, container hosting, compute instances, batch processing.
**What you'll track**: Execution time, compute hours, function invocations, job duration.
## How to Use a Blueprint
Select the blueprint that best matches your billing scenario. Each blueprint page includes detailed implementation guides.
Understand the recommended event naming and metadata design. Copy the event structure for your use case.
Follow the blueprint's meter configuration instructions. Use the suggested aggregation type and filters.
Copy the sample code for your language/framework. Replace placeholder values with your actual API keys and customer IDs.
Send test events and verify they appear in your meter dashboard with correct aggregation.
Adapt the blueprint to your specific requirements—add custom metadata, adjust filters, or modify pricing.
## Blueprint Comparison
Not sure which blueprint fits your needs? Here's a quick comparison:
| Use Case | Blueprint | Aggregation | Common Metadata |
| -------------- | -------------- | --------------- | ------------------------------------ |
| AI chat app | LLM | Sum (tokens) | model, input\_tokens, output\_tokens |
| REST API | API Gateway | Count | endpoint, method, status\_code |
| File storage | Object Storage | Sum (bytes) | file\_size, file\_type, operation |
| Video platform | Stream | Sum (bandwidth) | duration, quality, bitrate |
| Serverless | Time Range | Sum (duration) | function\_name, execution\_time\_ms |
## Custom Implementations
Don't see a blueprint for your exact use case? No problem:
1. **Start with the closest blueprint**: Pick the one most similar to your needs
2. **Adapt the event structure**: Modify metadata fields to match your requirements
3. **Adjust meter configuration**: Change aggregation type or filters as needed
4. **Test thoroughly**: Verify billing calculations with sample usage
All blueprints are starting points. Feel free to mix concepts from different blueprints or create entirely custom implementations.
## Need Help?
If you're unsure which blueprint to use or need help customizing:
Follow our end-to-end tutorial building an AI image generator with usage billing.
Comprehensive guide covering all aspects of usage-based billing implementation.
# Usage Based Billing
Source: https://docs.dodopayments.com/features/usage-based-billing/introduction
Charge customers based on what they actually use with flexible metering and automated billing.
Usage-based billing lets you charge customers for what they actually use—API calls, storage, AI tokens, or any metric you define. Customers pay for consumption, not fixed fees.
**Perfect for**: APIs, AI services, infrastructure platforms, and any product where value scales with usage.
## How It Works
Your application sends events when billable actions occur.
```javascript theme={null}
{
"event_id": "evt_123",
"customer_id": "cus_abc",
"event_name": "api.call",
"metadata": { "endpoint": "/v1/users" }
}
```
Meters aggregate events into billable quantities using Count, Sum, Max, or Last aggregation.
Set pricing per unit and free thresholds. Customers are billed automatically each cycle.
## Core Concepts
### Events
Events represent usage actions in your application. Each event includes:
* **Event Name**: Type of usage (e.g., `api.call`)
* **Customer ID**: Who used it
* **Metadata**: Additional properties for aggregation
### Meters
Meters aggregate events into billable quantities:
* **Count**: Total events (API calls)
* **Sum**: Add values (total bytes)
* **Max**: Highest value (peak users)
* **Last**: Most recent value
### Products with Usage Pricing
Set pricing per unit and optional free thresholds:
**Example**: 2,500 calls - 1,000 free = 1,500 × $0.02 = $30.00
### Customer Credits
Give customers included usage credits that refresh each billing cycle. Credits apply before paid usage.
## Quick Start
In your dashboard: **Meters** → **Create Meter**
1. Set event name (e.g., `api.call`)
2. Choose aggregation (Count, Sum, Max, Last)
3. Add optional filters
Link meter to a product with pricing:
1. Select **Usage-Based Billing**
2. Choose your meter
3. Set price per unit and free threshold
```javascript theme={null}
await fetch('https://test.dodopayments.com/events/ingest', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.DODO_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
events: [{
event_id: "unique_event_id",
customer_id: "cus_abc123",
event_name: "api.call",
metadata: { endpoint: "/v1/users" }
}]
})
});
```
Check your meter's dashboard to see events and usage aggregation. Customers are billed automatically each cycle.
## Use Cases
Track API calls, compute hours, bandwidth, storage. Example: \$0.001 per call after 10,000 free.
Track tokens, generations, processing time. Example: \$0.04 per standard image, \$0.08 per HD.
Bill by data processed, queries run, records analyzed. Example: \$5 per GB after 10 GB free.
Count messages, minutes, data transferred. Example: \$0.01 per SMS sent.
## Next Steps
Learn how to structure and send usage events from your application.
Master meter configuration, aggregation types, and event filtering.
Build a complete AI image generator with usage-based billing from scratch.
Comprehensive guide to implementing usage-based billing in your application.
# Meters
Source: https://docs.dodopayments.com/features/usage-based-billing/meters
Transform raw usage events into billable quantities with aggregation and filtering.
Meters convert raw events into billable quantities. They filter events and apply aggregation functions (Count, Sum, Max, Last) to calculate usage per customer.
## API Resources
Create meters programmatically via API.
Retrieve all meters in your account.
Fetch details for a specific meter by ID.
Archive a meter to stop tracking usage.
Restore an archived meter to resume tracking.
## Creating a Meter
Descriptive name (e.g., "API Requests", "Token Usage")
Exact event name to match (case-sensitive). Examples: `api.call`, `image.generated`
Choose how events are aggregated:
* **Count**: Total number of events (API calls, uploads)
* **Sum**: Add numeric values (tokens, bytes)
* **Max**: Highest value in period (peak users)
* **Last**: Most recent value
Metadata key to aggregate (required for all types except Count). Examples: `tokens`, `bytes`, `duration_ms`
Unit label for invoices. Examples: `calls`, `tokens`, `GB`, `hours`
Add conditions to filter which events are counted:
* **AND logic**: All conditions must match
* **OR logic**: Any condition can match
**Comparators**: equals, not equals, greater than, less than, contains
Enable filtering, choose logic, add conditions with property key, comparator, and value.
Review configuration and click **Create Meter**.
## Viewing Analytics
Your meter dashboard shows:
* **Overview**: Total usage and usage chart
* **Events**: Individual events received
* **Customers**: Per-customer usage and charges
## Troubleshooting
* Event name must match exactly (case-sensitive)
* Check meter filters aren't excluding events
* Verify customer IDs exist
* Temporarily disable filters to test
* Verify Over Property matches metadata key exactly
* Use numbers, not strings: `tokens: 150` not `"150"`
* Include required properties in all events
* Match case exactly
* Use correct operators for data type
* Ensure events include filtered properties
* Check Events tab to count actual events received
* Verify aggregation type (Count vs Sum)
* Ensure values are numeric for Sum/Max
## Next Steps
Start sending usage events from your application to your meters.
Use ready-made meter configurations for common use cases.
# Close CRM
Source: https://docs.dodopayments.com/integrations/close-crm
Sync Dodo Payments data with Close CRM to track leads and manage sales pipeline.
## Introduction
Connect your payment data directly to Close CRM for seamless lead management and sales tracking. Automatically create contacts and opportunities from successful payments, keeping your sales team informed of revenue-generating activities.
This integration requires a Close CRM API key with appropriate permissions.
## Getting Started
In your Dodo Payments dashboard, navigate to Webhooks → + Add Endpoint and expand the integrations dropdown.
Choose the Close CRM integration card.
Provide your Close CRM API key in the configuration.
Edit the transformation code to map payment data to Close CRM objects.
Test with sample payloads and click Create to activate the sync.
🎉 Payment events will now automatically create/update records in Close CRM.
## Transformation Code Examples
### Create Contact from Payment
```javascript create_contact.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "payment.succeeded") {
const p = webhook.payload.data;
webhook.url = "https://api.close.com/api/v1/contact/";
webhook.payload = {
name: p.customer.name,
emails: [p.customer.email],
phones: [p.customer.phone || ''],
custom: {
payment_amount: (p.total_amount / 100).toFixed(2),
payment_method: p.payment_method || '',
dodo_customer_id: p.customer.customer_id
}
};
}
return webhook;
}
```
### Create Opportunity from Subscription
```javascript create_opportunity.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "subscription.active") {
const s = webhook.payload.data;
webhook.url = "https://api.close.com/api/v1/opportunity/";
webhook.payload = {
lead_id: s.customer.customer_id,
value: (s.recurring_pre_tax_amount / 100).toFixed(2),
value_period: s.payment_frequency_interval,
title: `Subscription - ${s.product_id}`,
custom: {
subscription_id: s.subscription_id,
billing_frequency: s.payment_frequency_interval,
next_billing: s.next_billing_date
}
};
}
return webhook;
}
```
## Tips
* Use Close CRM's API documentation to understand field mappings
* Include custom fields for payment-specific data
* Map subscription amounts to opportunity values
* Use customer IDs for proper lead associations
## Troubleshooting
* Verify API key has write permissions
* Check that required fields are included
* Ensure email format is valid
* Review Close CRM API rate limits
* Validate JSON structure matches Close CRM API format
* Check that all required fields are present
* Ensure field names match Close CRM schema exactly
# Customer.io
Source: https://docs.dodopayments.com/integrations/customer-io
Send Dodo Payments events to Customer.io to trigger email automations and customer journeys.
## Introduction
Trigger personalized email campaigns and customer journeys based on payment events. Send welcome emails for new customers, subscription updates, and payment failure notifications automatically through Customer.io.
This integration requires your Customer.io Site ID and API Key.
## Getting Started
In your Dodo Payments dashboard, navigate to Webhooks → + Add Endpoint and expand the integrations dropdown.
Choose the Customer.io integration card.
Provide your Customer.io Site ID and API Key in the configuration.
Edit the transformation code to format events for Customer.io's Track API.
Test with sample payloads and click Create to activate the sync.
🎉 Payment events will now trigger Customer.io email automations.
## Transformation Code Examples
### Track Payment Events
```javascript track_payments.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "payment.succeeded") {
const p = webhook.payload.data;
webhook.url = "https://track.customer.io/api/v2/entity";
webhook.payload = {
type: "person",
identifiers: {
id: p.customer.customer_id
},
action: "payment_completed",
name: "Payment Completed",
attributes: {
email: p.customer.email,
name: p.customer.name,
payment_amount: (p.total_amount / 100).toFixed(2),
payment_method: p.payment_method || "unknown",
payment_id: p.payment_id,
currency: p.currency || "USD"
}
};
}
return webhook;
}
```
### Track Subscription Lifecycle
```javascript track_subscriptions.js icon="js" expandable theme={null}
function handler(webhook) {
const s = webhook.payload.data;
switch (webhook.eventType) {
case "subscription.active":
webhook.url = "https://track.customer.io/api/v2/entity";
webhook.payload = {
type: "person",
identifiers: {
id: s.customer.customer_id
},
action: "subscription_started",
name: "Subscription Started",
attributes: {
email: s.customer.email,
subscription_id: s.subscription_id,
product_id: s.product_id,
amount: (s.recurring_pre_tax_amount / 100).toFixed(2),
frequency: s.payment_frequency_interval,
next_billing: s.next_billing_date
}
};
break;
case "subscription.cancelled":
webhook.url = "https://track.customer.io/api/v2/entity";
webhook.payload = {
type: "person",
identifiers: {
id: s.customer.customer_id
},
action: "subscription_cancelled",
name: "Subscription Cancelled",
attributes: {
email: s.customer.email,
subscription_id: s.subscription_id,
cancelled_at: s.cancelled_at,
cancel_at_next_billing: s.cancel_at_next_billing_date
}
};
break;
}
return webhook;
}
```
### Track Customer Attributes
```javascript track_attributes.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "payment.succeeded") {
const p = webhook.payload.data;
webhook.url = "https://track.customer.io/api/v2/entity";
webhook.payload = {
type: "person",
identifiers: {
id: p.customer.customer_id
},
action: "identify",
name: "Customer Identified",
attributes: {
email: p.customer.email,
name: p.customer.name,
total_spent: (p.total_amount / 100).toFixed(2),
payment_method: p.payment_method || "unknown",
last_payment_date: webhook.payload.timestamp,
customer_since: webhook.payload.timestamp
}
};
}
return webhook;
}
```
## Tips
* Use consistent event names that match your Customer.io campaigns
* Include relevant attributes for personalization
* Set proper customer identifiers for accurate tracking
* Use meaningful event names for campaign triggers
## Troubleshooting
* Verify Site ID and API Key are correct
* Check that event names match your Customer.io campaigns
* Ensure customer identifiers are properly set
* Review Customer.io API rate limits
* Validate JSON structure matches Customer.io API format
* Check that all required fields are present
* Ensure event names and attributes are properly formatted
# Discord
Source: https://docs.dodopayments.com/integrations/discord
Send real-time Dodo Payments notifications to your Discord channels.
## Introduction
Keep your team informed where they already collaborate. The Discord integration delivers payment, subscription, dispute, and other important events directly to any channel you choose—no polling or dashboards required.
This guide assumes you have access to the Integrations section of the Dodo Payments dashboard.
## Getting Started
In your Dodo Payments dashboard, open Webhooks → + Add Endpoint and expand the dropdown to reveal integrations.
Choose the Discord card then click Connect your Discord workspace.
Grant the requested permissions so the bot can post messages in your selected channel.
Tailor the payload → embed mapping to your needs—or start with the templates below.
Use sample payloads to preview the embed, then hit Create.
🎉 Your Discord channel will now receive live Dodo Payments updates.
## Transformation Code Examples
### Minimal Payment Embed
```javascript payment_embed.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "payment.succeeded") {
const p = webhook.payload.data;
webhook.payload = {
embeds: [{
title: "✅ Payment Successful",
description: `**Amount:** $${(p.total_amount / 100).toFixed(2)}\n**Customer:** ${p.customer.email}`,
color: 0x2ecc71 // green
}]
};
}
return webhook;
}
```
### Subscription Events
```javascript subscription_embed.js icon="js" expandable theme={null}
function handler(webhook) {
const s = webhook.payload.data;
switch (webhook.eventType) {
case "subscription.active":
webhook.payload = {
embeds: [{
title: "📄 Subscription Activated",
fields: [
{ name: "Customer", value: s.customer.email, inline: true },
{ name: "Product", value: s.product_id, inline: true },
{ name: "Next Billing", value: new Date(s.next_billing_date).toLocaleDateString(), inline: true }
],
color: 0x2ecc71
}]
};
break;
case "subscription.cancelled":
webhook.payload = {
embeds: [{
title: "⚠️ Subscription Cancelled",
fields: [
{ name: "Customer", value: s.customer.email, inline: true },
{ name: "Product", value: s.product_id, inline: true }
],
color: 0xf1c40f
}]
};
break;
}
return webhook;
}
```
### Dispute Alerts
```javascript dispute_embed.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType.startsWith("dispute.")) {
const d = webhook.payload.data;
webhook.payload = {
embeds: [{
title: d.dispute_status === "won" ? "🏆 Dispute Won" : d.dispute_status === "lost" ? "❌ Dispute Lost" : "🚨 Dispute Update",
fields: [
{ name: "Payment ID", value: d.payment_id, inline: true },
{ name: "Amount", value: `$${(d.amount / 100).toFixed(2)}`, inline: true },
{ name: "Status", value: d.dispute_status, inline: true }
],
color: d.dispute_status === "won" ? 0x2ecc71 : d.dispute_status === "lost" ? 0xe74c3c : 0xe67e22
}]
};
}
return webhook;
}
```
## Tips
* Prefer embeds for rich formatting and colors.
* Keep titles short; put details in fields.
* Use intuitive colors: green (success), red (failure), orange (warnings).
## Troubleshooting
* Confirm the bot has access to the channel.
* Check that the transformation returns a JSON object with `embeds`.
* Validate your code in the editor – syntax errors will block delivery.
* Ensure field names match the webhook payload structure.
# Figma Plugin Boilerplate
Source: https://docs.dodopayments.com/integrations/figma-plugin
Build Figma plugins with Dodo Payments license key validation and activation
This is an example implementation and boilerplate designed as a starting point for Figma plugin developers who want to integrate Dodo Payments license key API to grant access to their plugins. The template demonstrates complete license key validation and activation functionality, providing everything you need to create premium Figma plugins with built-in licensing.
## Features
* **License Key Validation**: Validates license keys against the Dodo Payments API
* **License Key Activation**: Activates license keys with user identification
* **Environment Support**: Configurable for both test and live environments
* **CORS Proxy**: Uses a Cloudflare Worker proxy to handle CORS restrictions
* **TypeScript Support**: Fully typed implementation with proper error handling
## Getting Started
Create your own Figma plugin project using [Figma's plugin quickstart guide](https://www.figma.com/plugin-docs/plugin-quickstart-guide/). This will generate a scaffold with a `manifest.json` file.
This step is just to get a unique name and ID for your plugin. You can discard the generated files after committing the ID and name to your git repository in the next step.
Clone the [dodopayments-figma repository](https://github.com/dodopayments/dodopayments-figma) and update its `manifest.json` with the name and ID from your generated plugin folder.
```bash theme={null}
git clone https://github.com/dodopayments/dodopayments-figma.git
cd dodopayments-figma
```
This example serves as your starting point for integration.
You need to change the remote origin URL to your own repository for version control.
Run the following command in your project root:
```bash theme={null}
npm install
```
Set the `API_MODE` in `src/ui/api.ts`:
```typescript theme={null}
const API_MODE = "test_mode"; // for development
// const API_MODE = "live_mode"; // for production
```
Always use `test_mode` during development to avoid processing real payments.
Customize the plugin components to match your needs:
* Update `Authenticated.tsx` to expose functionality for users with valid license keys
* Customize `LicenseKeyInput.tsx` to match your design preferences
The `Authenticated.tsx` component is where you'll add your plugin's premium features that require a valid license.
## Configuration
The plugin needs network access to communicate with the CORS proxy. Add the following to your `manifest.json`:
```json theme={null}
{
"networkAccess": {
"allowedDomains": ["https://dodo-payments-proxy.aagarwal9782.workers.dev"]
}
}
```
This allows the plugin to validate and activate license keys using the Dodo Payments API.
The CORS proxy is necessary because Dodo Payments API doesn't allow API calls from browser-based applications as of now.
In `src/ui/api.ts`, configure the API mode:
```typescript theme={null}
const API_MODE = "test_mode"; // or "live_mode"
```
Switch to `live_mode` only when you're ready for production and have tested thoroughly in test mode.
## Development
Import the plugin into Figma using "Import Manifest" from the Figma desktop app.
You'll need to have the Figma desktop app installed to develop and test plugins locally.
```bash theme={null}
npm run dev
```
This starts the development server with file watching for automatic rebuilds.
Your plugin will automatically rebuild when you make changes to the source code.
## Build Commands
| Command | Description |
| ---------------- | ------------------------------------------- |
| `npm run build` | Build the plugin for production |
| `npm run dev` | Start development server with file watching |
| `npm run lint` | Check code style and errors |
| `npm run format` | Format code with Prettier |
## License Key Integration
The example implementation includes complete license key validation and activation that you can customize for your plugin:
1. **User Input**: Users enter their license key in the plugin UI
2. **Validation**: The plugin validates the key against Dodo Payments API
3. **Activation**: Valid keys are activated with user identification
4. **Access Control**: Plugin functionality is unlocked for valid license holders
## Publishing
Once your plugin is ready:
1. Build for production: `npm run build`
2. Test thoroughly in both test and live modes
3. Follow [Figma's plugin publishing guidelines](https://www.figma.com/plugin-docs/publishing-plugins/)
4. Submit for review through the Figma Community
## Repository
The complete example implementation is available at: [github.com/dodopayments/dodopayments-figma](https://github.com/dodopayments/dodopayments-figma)
Use this as your starting point to build your own premium Figma plugin with Dodo Payments license key integration.
# Framer Plugin
Source: https://docs.dodopayments.com/integrations/framer-plugin
Integrate Dodo Payments checkout functionality into your Framer projects with our powerful plugin. Accept payments with customizable checkout experiences.
## Overview
The Dodo Payments Framer Plugin seamlessly integrates payment processing into your Framer websites. Create customizable checkout experiences with support for both test and production environments.
Access the official Dodo Payments plugin on the Framer marketplace.
## Features
Choose a full-page flow or an inline overlay that feels native to your site.
Add the component, paste your keys, publish.
Secure download links or license keys emailed the moment payment clears.
Bank-level acquiring on six continents, local methods built-in.
VAT, GST, sales tax, and receipts handled for every order.
Flip a switch to sell subscription plans for your products.
## Setup Guide
Visit the [Dodo Payments Dashboard](https://app.dodopayments.com/developer/api-keys) and create a new API key.
For production use with real payment processing.
Perfect for development and testing with no real charges.
Copy your API key securely - you'll need it for the next step.
1. Open the Dodo Payments plugin in Framer
2. Navigate to the Authentication page
3. Paste your API key
4. Select the appropriate mode (Test or Live)
5. Click **Continue** to authenticate
Keep your API keys secure and never share them publicly.
After authentication, you'll see your products list:
* **Existing products**: Displayed automatically
* **No products**: Click the **Plus (+)** button next to 'Products'
You'll be redirected to the Dodo Payments Dashboard to create products with pricing, descriptions, and settings.
1. Select a product from your available list
2. Click the `Add Buy Button` button
3. A buy button with the text 'Buy \{product\_name}' will be added to your canvas
The Add Buy Button will be disabled if you don't have the correct project permissions.
## Customization
Choose your preferred checkout experience:
* **Overlay (Embedded)**: Checkout appears as an overlay within your Framer project, keeping users on your site.
* **Hosted**: Users are redirected to a Dodo Payments hosted checkout page for a streamlined experience.
Customize your checkout experience with the following appearance settings:
* **Checkout Theme**: Choose between light or dark themes to match your brand aesthetic.
* **Button Label**: Customize text (e.g., 'Buy Now', 'Purchase', 'Get Started').
* **Button Styles**: Adjust colors, fonts, sizes, and other styling properties.
* **Return URL**: Set a Framer page for the user to return to after checkout.
## Troubleshooting
**Problem**: Plugin won't authenticate
**Solutions**:
* Verify your API key is correct
* Ensure you're using the right mode (Test/Live)
* Check your internet connection
**Problem**: Add Buy Button is disabled
**Solutions**:
* Verify you have the correct permissions in your Framer project
* Ensure you're authenticated properly
* Try refreshing the plugin
**Problem**: Products not showing
**Solutions**:
* Make sure you have products created in your Dodo Payments dashboard
* Check that your API key has the correct permissions
* Try re-authenticating
# HubSpot
Source: https://docs.dodopayments.com/integrations/hubspot
Automatically create and update contacts, companies, and deals in HubSpot from Dodo Payments events.
## Introduction
Sync your payment data directly to HubSpot CRM. Create contacts from successful payments, track subscription lifecycle, and build comprehensive customer profiles—all automatically triggered by Dodo Payments events.
This integration requires HubSpot admin access to configure OAuth scopes and API permissions.
## Getting Started
In your Dodo Payments dashboard, go to Webhooks → + Add Endpoint and expand the integrations dropdown.
Choose the HubSpot integration card.
Click Connect to HubSpot and authorize the required OAuth scopes.
Edit the transformation code to map payment data to HubSpot CRM objects.
Test with sample payloads and click Create to activate the sync.
🎉 Payment events will now automatically create/update records in your HubSpot CRM.
## Transformation Code Examples
### Create Contact from Payment
```javascript create_contact.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "payment.succeeded") {
const p = webhook.payload.data;
webhook.url = "https://api.hubapi.com/crm/v3/objects/contacts";
webhook.payload = {
properties: {
email: p.customer.email,
firstname: p.customer.name.split(' ')[0] || '',
lastname: p.customer.name.split(' ').slice(1).join(' ') || '',
phone: p.customer.phone || '',
company: p.customer.company || '',
amount: (p.total_amount / 100).toString(),
payment_method: p.payment_method || '',
currency: p.currency || 'USD'
}
};
}
return webhook;
}
```
### Update Contact with Subscription
```javascript update_contact.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "subscription.active") {
const s = webhook.payload.data;
webhook.url = `https://api.hubapi.com/crm/v3/objects/contacts/${s.customer.customer_id}`;
webhook.method = "PATCH";
webhook.payload = {
properties: {
subscription_status: "active",
subscription_amount: (s.recurring_pre_tax_amount / 100).toString(),
subscription_frequency: s.payment_frequency_interval,
next_billing_date: s.next_billing_date,
product_id: s.product_id
}
};
}
return webhook;
}
```
### Create Deal from Payment
```javascript create_deal.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "payment.succeeded") {
const p = webhook.payload.data;
webhook.url = "https://api.hubapi.com/crm/v3/objects/deals";
webhook.payload = {
properties: {
dealname: `Payment - ${p.customer.email}`,
amount: (p.total_amount / 100).toString(),
dealstage: "closedwon",
closedate: new Date().toISOString(),
hs_currency: p.currency || "USD"
},
associations: [
{
to: {
id: p.customer.customer_id
},
types: [
{
associationCategory: "HUBSPOT_DEFINED",
associationTypeId: 3
}
]
}
]
};
}
return webhook;
}
```
## Tips
* Use HubSpot's API explorer to test object creation
* Map payment amounts to HubSpot currency fields
* Include customer IDs for proper associations
* Set appropriate deal stages based on payment status
## Troubleshooting
* Verify OAuth scopes include write permissions
* Check that required HubSpot properties exist
* Ensure customer email is valid and unique
* Review HubSpot API rate limits
* Validate JSON structure matches HubSpot API format
* Check that all required properties are included
* Ensure property names match HubSpot field names exactly
# Inngest
Source: https://docs.dodopayments.com/integrations/inngest
Trigger serverless functions and background jobs in Inngest based on Dodo Payments events.
## Introduction
Execute serverless functions and background jobs automatically when payment events occur. Process payments, send notifications, update databases, and run complex workflows with Inngest's reliable function execution platform.
This integration requires your Inngest webhook URL from your function configuration.
## Getting Started
In your Dodo Payments dashboard, navigate to Webhooks → + Add Endpoint and expand the integrations dropdown.
Choose the Inngest integration card.
In Inngest, create a new function and copy the webhook URL from the function configuration.
Paste the Inngest webhook URL into the endpoint configuration.
Edit the transformation code to format events for your Inngest function.
Test with sample payloads and click Create to activate the integration.
🎉 Payment events will now trigger your Inngest functions automatically.
## Transformation Code Examples
### Basic Event Payload
```javascript basic_event.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "payment.succeeded") {
const p = webhook.payload.data;
webhook.payload = {
name: "payment.succeeded",
data: {
payment_id: p.payment_id,
amount: (p.total_amount / 100).toFixed(2),
currency: p.currency || "USD",
customer_email: p.customer.email,
customer_name: p.customer.name,
payment_method: p.payment_method || "unknown"
},
user: {
email: p.customer.email
},
ts: Math.floor(new Date(webhook.payload.timestamp).getTime() / 1000)
};
}
return webhook;
}
```
### Subscription Event Handler
```javascript subscription_event.js icon="js" expandable theme={null}
function handler(webhook) {
const s = webhook.payload.data;
switch (webhook.eventType) {
case "subscription.active":
webhook.payload = {
name: "subscription.started",
data: {
subscription_id: s.subscription_id,
customer_email: s.customer.email,
customer_name: s.customer.name,
product_id: s.product_id,
amount: (s.recurring_pre_tax_amount / 100).toFixed(2),
frequency: s.payment_frequency_interval,
next_billing: s.next_billing_date
},
user: {
email: s.customer.email
},
ts: Math.floor(new Date(webhook.payload.timestamp).getTime() / 1000)
};
break;
case "subscription.cancelled":
webhook.payload = {
name: "subscription.cancelled",
data: {
subscription_id: s.subscription_id,
customer_email: s.customer.email,
cancelled_at: s.cancelled_at,
cancel_at_next_billing: s.cancel_at_next_billing_date
},
user: {
email: s.customer.email
},
ts: Math.floor(new Date(webhook.payload.timestamp).getTime() / 1000)
};
break;
}
return webhook;
}
```
### Dispute Event Handler
```javascript dispute_event.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType.startsWith("dispute.")) {
const d = webhook.payload.data;
webhook.payload = {
name: webhook.eventType,
data: {
dispute_id: d.dispute_id,
payment_id: d.payment_id,
amount: (d.amount / 100).toFixed(2),
status: d.dispute_status,
stage: d.dispute_stage,
remarks: d.remarks || "",
urgent: webhook.eventType === "dispute.opened"
},
user: {
email: d.customer?.email || "unknown"
},
ts: Math.floor(new Date(webhook.payload.timestamp).getTime() / 1000)
};
}
return webhook;
}
```
## Common Inngest Use Cases
* Send confirmation emails
* Update customer records
* Process refunds
* Generate invoices
* Update inventory
* Welcome new subscribers
* Process cancellations
* Send renewal reminders
* Update billing cycles
* Handle failed payments
* Update revenue metrics
* Track customer behavior
* Generate reports
* Sync data to analytics platforms
* Calculate churn rates
## Tips
* Use descriptive event names for better function organization
* Include user context for function execution
* Set proper timestamps for event ordering
* Structure data consistently across events
* Use Inngest's retry and error handling features
## Troubleshooting
* Verify webhook URL is correct and active
* Check that Inngest function is deployed and active
* Ensure event names match function triggers
* Review Inngest function logs for errors
* Check payload structure matches Inngest expectations
* Verify event names are properly formatted
* Ensure all required fields are included
* Test with Inngest's webhook testing tool
# Introduction
Source: https://docs.dodopayments.com/integrations/introduction
Connect Dodo Payments with your favorite tools and platforms to automate workflows and enhance your payment experience.
## Overview
Providing robust support for external third-party integrations is a top priority for us. Our goal is to ensure you can seamlessly connect Dodo Payments with your favorite applications, enabling smooth automation and enhanced workflows across your business tools.
Please refer to the list of available integrations in your left sidebar, or use the search to quickly find your favorite integrations.
## Benefits of Integrations
Stay informed about important payment events as they happen, ensuring you never miss critical updates about your business.
Reduce manual tasks by automatically triggering actions based on payment events, saving time and reducing errors.
Get better insights into your payment operations with centralized notifications and reporting across your favorite tools.
Provide seamless payment experiences by integrating with your existing tools and workflows.
Each integration includes detailed setup instructions and example configurations to help you get started quickly.
## Custom Integrations
If you want to connect Dodo Payments with a platform that doesn't have a pre-built integration, you have two options:
* **Use our webhook system** to build your own custom integration. Our webhook documentation provides all the details you need to get started.
* **Request a new integration** by contacting the Dodo Payments team at [support@dodopayments.com](mailto:support@dodopayments.com). Let us know which platform you want to connect, and our team will review your request.
Learn how to create custom integrations using our webhook system.
# Loops
Source: https://docs.dodopayments.com/integrations/loops
Send transactional emails and manage customer communication through Loops based on Dodo Payments events.
## Introduction
Send transactional emails and manage customer communication automatically when payment events occur. Deliver payment confirmations, subscription updates, and important notifications with Loops' email infrastructure.
This integration requires your Loops API Key for authentication.
## Getting Started
In your Dodo Payments dashboard, navigate to Webhooks → + Add Endpoint and expand the integrations dropdown.
Choose the Loops integration card.
Provide your Loops API Key in the configuration.
Edit the transformation code to format emails for Loops' API.
Test with sample payloads and click Create to activate the email sending.
🎉 Payment events will now automatically trigger transactional emails via Loops.
## Transformation Code Examples
### Payment Confirmation Email
```javascript payment_confirmation.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "payment.succeeded") {
const p = webhook.payload.data;
webhook.url = "https://api.loops.so/v1/events/send";
webhook.payload = {
eventName: "payment_confirmation",
email: p.customer.email,
properties: {
customer_name: p.customer.name,
payment_id: p.payment_id,
amount: (p.total_amount / 100).toFixed(2),
currency: p.currency || "USD",
payment_method: p.payment_method || "unknown",
payment_date: new Date(webhook.payload.timestamp).toLocaleDateString()
}
};
}
return webhook;
}
```
### Subscription Welcome Email
```javascript subscription_welcome.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "subscription.active") {
const s = webhook.payload.data;
webhook.url = "https://api.loops.so/v1/events/send";
webhook.payload = {
eventName: "subscription_welcome",
email: s.customer.email,
properties: {
customer_name: s.customer.name,
subscription_id: s.subscription_id,
product_id: s.product_id,
amount: (s.recurring_pre_tax_amount / 100).toFixed(2),
frequency: s.payment_frequency_interval,
next_billing: s.next_billing_date
}
};
}
return webhook;
}
```
### Payment Failure Notification
```javascript payment_failure.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "payment.failed") {
const p = webhook.payload.data;
webhook.url = "https://api.loops.so/v1/events/send";
webhook.payload = {
eventName: "payment_failed",
email: p.customer.email,
properties: {
customer_name: p.customer.name,
payment_id: p.payment_id,
amount: (p.total_amount / 100).toFixed(2),
error_message: p.error_message || "Payment processing failed",
retry_link: `https://yourdomain.com/retry-payment/${p.payment_id}`
}
};
}
return webhook;
}
```
## Tips
* Use descriptive event names for better email template organization
* Include relevant customer properties for personalization
* Set up email templates in Loops dashboard for each event
* Use consistent property naming across events
* Test email delivery before going live
## Troubleshooting
* Verify API Key is correct and active
* Check that event names match your Loops templates
* Ensure recipient email addresses are valid
* Review Loops sending limits and quotas
* Validate JSON structure matches Loops API format
* Check that all required fields are present
* Ensure event names are properly formatted
* Verify API key permissions
# Microsoft Teams
Source: https://docs.dodopayments.com/integrations/microsoft-teams
Send Dodo Payments notifications to Microsoft Teams channels with rich Adaptive Cards.
## Introduction
Keep your business team in the loop with real-time payment notifications in Microsoft Teams. The integration delivers payment events as rich Adaptive Cards—perfect for enterprise environments where Teams is the primary collaboration tool.
This guide assumes you have admin access to create webhooks in your Microsoft Teams workspace.
## Getting Started
In your Dodo Payments dashboard, navigate to Webhooks → + Add Endpoint and expand the integrations dropdown.
Choose the Microsoft Teams integration card.
In Teams, go to your channel → ⋯ → Connectors → Incoming Webhook → Configure. Copy the webhook URL.
Paste the Teams webhook URL into the endpoint configuration.
Edit the transformation code to format messages as Adaptive Cards for Teams.
Test with sample payloads and click Create to activate.
🎉 Your Teams channel will now receive Dodo Payments updates as Adaptive Cards.
## Transformation Code Examples
### Basic Payment Card
```javascript payment_card.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "payment.succeeded") {
const p = webhook.payload.data;
webhook.payload = {
type: "message",
attachments: [{
contentType: "application/vnd.microsoft.card.adaptive",
content: {
type: "AdaptiveCard",
body: [
{
type: "TextBlock",
text: "✅ Payment Successful",
weight: "Bolder",
size: "Medium"
},
{
type: "FactSet",
facts: [
{ title: "Amount", value: `$${(p.total_amount / 100).toFixed(2)}` },
{ title: "Customer", value: p.customer.email },
{ title: "Payment ID", value: p.payment_id }
]
}
]
}
}]
};
}
return webhook;
}
```
### Subscription Management
```javascript subscription_card.js icon="js" expandable theme={null}
function handler(webhook) {
const s = webhook.payload.data;
switch (webhook.eventType) {
case "subscription.active":
webhook.payload = {
type: "message",
attachments: [{
contentType: "application/vnd.microsoft.card.adaptive",
content: {
type: "AdaptiveCard",
body: [
{
type: "TextBlock",
text: "📄 Subscription Activated",
weight: "Bolder",
color: "Good"
},
{
type: "FactSet",
facts: [
{ title: "Customer", value: s.customer.email },
{ title: "Product", value: s.product_id },
{ title: "Amount", value: `$${(s.recurring_pre_tax_amount / 100).toFixed(2)}/${s.payment_frequency_interval}` },
{ title: "Next Billing", value: new Date(s.next_billing_date).toLocaleDateString() }
]
}
]
}
}]
};
break;
case "subscription.cancelled":
webhook.payload = {
type: "message",
attachments: [{
contentType: "application/vnd.microsoft.card.adaptive",
content: {
type: "AdaptiveCard",
body: [
{
type: "TextBlock",
text: "⚠️ Subscription Cancelled",
weight: "Bolder",
color: "Warning"
},
{
type: "FactSet",
facts: [
{ title: "Customer", value: s.customer.email },
{ title: "Product", value: s.product_id },
{ title: "Cancelled At", value: new Date(s.cancelled_at).toLocaleDateString() }
]
}
]
}
}]
};
break;
}
return webhook;
}
```
### Dispute Alerts
```javascript dispute_card.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType.startsWith("dispute.")) {
const d = webhook.payload.data;
const color = d.dispute_status === "won" ? "Good" : d.dispute_status === "lost" ? "Attention" : "Warning";
const title = d.dispute_status === "won" ? "🏆 Dispute Won" : d.dispute_status === "lost" ? "❌ Dispute Lost" : "🚨 Dispute Update";
webhook.payload = {
type: "message",
attachments: [{
contentType: "application/vnd.microsoft.card.adaptive",
content: {
type: "AdaptiveCard",
body: [
{
type: "TextBlock",
text: title,
weight: "Bolder",
color: color
},
{
type: "FactSet",
facts: [
{ title: "Payment ID", value: d.payment_id },
{ title: "Amount", value: `$${(d.amount / 100).toFixed(2)}` },
{ title: "Status", value: d.dispute_status },
{ title: "Stage", value: d.dispute_stage }
]
}
]
}
}]
};
}
return webhook;
}
```
## Tips
* Use Adaptive Cards for rich, interactive formatting
* Choose appropriate colors: Good (green), Warning (yellow), Attention (red)
* Keep fact sets concise and readable
* Test with Teams webhook tester before deploying
## Troubleshooting
* Verify the webhook URL is correct and active
* Check that the transformation returns valid Adaptive Card JSON
* Ensure the webhook has permission to post in the channel
* Validate Adaptive Card schema in Teams webhook tester
* Check that all required fields are present
* Ensure color values are valid (Good, Warning, Attention, Default)
# Raycast Extension
Source: https://docs.dodopayments.com/integrations/raycast-extension
Install the Raycast extension to browse and manage Dodo Payments resources from your keyboard.
Your Dodo Payments dashboard, a few keystrokes away. Use Raycast to quickly view payments, subscriptions, customers, products, discount codes, license keys, and refunds without opening the dashboard.
## Install
Open the Dodo Payments extension page in the Raycast Store.
Requires Raycast on macOS and a Dodo Payments API key.
## Setup
1. Log in to your Dodo Payments dashboard.
2. Go to Settings › API Keys.
3. Copy your API key.
1. Open Raycast.
2. Search for "Dodo Payments" and select any command.
3. When prompted, enter your API key in the API Key field.
4. Choose the API mode:
* Test: use for development and testing.
* Live: use for production data.
5. Save the settings.
Start in Test mode to validate your setup, then switch to Live.
Treat your API key like a password. Do not share it or store it in plaintext.
## Use the extension
* **Search**: open Raycast, type "Dodo Payments", and pick a command.
* **Filter**: narrow results by typing parts of IDs, emails, names, or statuses.
* **Open details**: press Enter on a list item to view more information.
## Commands and views
### Payments
Browse recent payments and jump into detailed views in seconds.
### Subscriptions
Quickly scan active, trialing, and canceled subscriptions.
### Customers
Find customers fast and open full profiles.
### Products
Review and manage your product catalog at a glance.
### Discount Codes
Search and reference active discount codes quickly.
### License Keys
Look up and manage license keys without leaving Raycast.
### Refunds
See recent refunds and drill into their details.
Additional live-only views: disputes and payouts.
## Troubleshooting
Confirm the API key and mode (Test vs Live) match your dashboard data. Reopen the command and re-enter the key if needed.
Ensure your selected mode has data. Try switching between Test and Live.
Wait a moment and try again. If the issue persists, check your network connectivity.
Get assistance from the Dodo Payments team.
# Resend
Source: https://docs.dodopayments.com/integrations/resend
Send transactional emails through Resend based on Dodo Payments events with high deliverability.
## Introduction
Send professional transactional emails automatically when payment events occur. Deliver payment confirmations, subscription updates, and important notifications with Resend's reliable email infrastructure and excellent deliverability rates.
This integration requires your Resend API Key for authentication.
## Getting Started
In your Dodo Payments dashboard, navigate to Webhooks → + Add Endpoint and expand the integrations dropdown.
Choose the Resend integration card.
Provide your Resend API Key in the configuration.
Edit the transformation code to format emails for Resend's API.
Test with sample payloads and click Create to activate the email sending.
🎉 Payment events will now automatically trigger transactional emails via Resend.
## Transformation Code Examples
### Payment Confirmation Email
```javascript payment_confirmation.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "payment.succeeded") {
const p = webhook.payload.data;
webhook.url = "https://api.resend.com/emails";
webhook.payload = {
from: "payments@yourdomain.com",
to: [p.customer.email],
subject: "Payment Confirmation - $" + (p.total_amount / 100).toFixed(2),
html: `
Payment Successful!
Hi ${p.customer.name},
Your payment of $${(p.total_amount / 100).toFixed(2)} has been processed successfully.
Please update your payment method or contact support for assistance.
Update Payment Method
`,
text: `Payment Failed: We couldn't process your $${(p.total_amount / 100).toFixed(2)} payment. Please update your payment method.`
};
}
return webhook;
}
```
## Tips
* Use verified sender domains for better deliverability
* Include both HTML and text versions of emails
* Personalize content with customer data
* Use clear, action-oriented subject lines
* Include unsubscribe links for compliance
* Test email templates before going live
## Troubleshooting
* Verify API Key is correct and active
* Check that sender domain is verified in Resend
* Ensure recipient email addresses are valid
* Review Resend sending limits and quotas
* Validate JSON structure matches Resend API format
* Check that all required fields are present
* Ensure HTML content is properly formatted
* Verify from email address is verified
# Segment
Source: https://docs.dodopayments.com/integrations/segment
Send Dodo Payments events to Segment for analytics and customer data platform integration.
## Introduction
Track payment events in Segment to power your analytics, marketing automation, and customer data platform. Send payment, subscription, and customer lifecycle events to 300+ downstream tools automatically.
This integration requires a Segment Write Key from your Segment workspace.
## Getting Started
In your Dodo Payments dashboard, navigate to Webhooks → + Add Endpoint and expand the integrations dropdown.
Choose the Segment integration card.
Provide your Segment Write Key in the configuration.
Edit the transformation code to format events for Segment's Track API.
Test with sample payloads and click Create to activate the sync.
🎉 Payment events will now be tracked in Segment and sent to your connected tools.
## Transformation Code Examples
### Track Payment Events
```javascript track_payments.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "payment.succeeded") {
const p = webhook.payload.data;
webhook.url = "https://api.segment.io/v1/track";
webhook.payload = {
userId: p.customer.customer_id,
event: "Payment Completed",
properties: {
amount: (p.total_amount / 100).toFixed(2),
currency: p.currency || "USD",
payment_method: p.payment_method || "unknown",
payment_id: p.payment_id,
customer_email: p.customer.email,
customer_name: p.customer.name
},
timestamp: webhook.payload.timestamp
};
}
return webhook;
}
```
### Track Subscription Lifecycle
```javascript track_subscriptions.js icon="js" expandable theme={null}
function handler(webhook) {
const s = webhook.payload.data;
switch (webhook.eventType) {
case "subscription.active":
webhook.url = "https://api.segment.io/v1/track";
webhook.payload = {
userId: s.customer.customer_id,
event: "Subscription Started",
properties: {
subscription_id: s.subscription_id,
product_id: s.product_id,
amount: (s.recurring_pre_tax_amount / 100).toFixed(2),
frequency: s.payment_frequency_interval,
next_billing: s.next_billing_date,
customer_email: s.customer.email
},
timestamp: webhook.payload.timestamp
};
break;
case "subscription.cancelled":
webhook.url = "https://api.segment.io/v1/track";
webhook.payload = {
userId: s.customer.customer_id,
event: "Subscription Cancelled",
properties: {
subscription_id: s.subscription_id,
product_id: s.product_id,
cancelled_at: s.cancelled_at,
cancel_at_next_billing: s.cancel_at_next_billing_date,
customer_email: s.customer.email
},
timestamp: webhook.payload.timestamp
};
break;
}
return webhook;
}
```
### Identify Customer Properties
```javascript identify_customer.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "payment.succeeded") {
const p = webhook.payload.data;
webhook.url = "https://api.segment.io/v1/identify";
webhook.payload = {
userId: p.customer.customer_id,
traits: {
email: p.customer.email,
name: p.customer.name,
total_spent: (p.total_amount / 100).toFixed(2),
payment_method: p.payment_method || "unknown",
last_payment_date: webhook.payload.timestamp,
customer_since: webhook.payload.timestamp
}
};
}
return webhook;
}
```
## Tips
* Use consistent event names across your integration
* Include relevant properties for analytics and segmentation
* Set proper timestamps for accurate event tracking
* Use customer IDs as userId for proper user identification
## Troubleshooting
* Verify Write Key is correct and active
* Check that event names follow Segment naming conventions
* Ensure userId is properly set for user identification
* Review Segment API rate limits
* Validate JSON structure matches Segment API format
* Check that all required fields are present
* Ensure event names are strings, not objects
# SendGrid
Source: https://docs.dodopayments.com/integrations/sendgrid
Send transactional emails based on Dodo Payments events using SendGrid's Mail Send API.
## Introduction
Automatically send transactional emails for payment confirmations, subscription updates, and important notifications using SendGrid. Trigger personalized emails based on payment events with dynamic content and professional templates.
This integration requires a SendGrid API Key with Mail Send permissions.
## Getting Started
In your Dodo Payments dashboard, navigate to Webhooks → + Add Endpoint and expand the integrations dropdown.
Choose the SendGrid integration card.
Provide your SendGrid API Key in the configuration.
Edit the transformation code to format emails for SendGrid's Mail Send API.
Test with sample payloads and click Create to activate the email sending.
🎉 Payment events will now automatically trigger transactional emails via SendGrid.
## Transformation Code Examples
### Payment Confirmation Email
```javascript payment_confirmation.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "payment.succeeded") {
const p = webhook.payload.data;
webhook.url = "https://api.sendgrid.com/v3/mail/send";
webhook.payload = {
personalizations: [
{
to: [{ email: p.customer.email }],
dynamic_template_data: {
customer_name: p.customer.name,
payment_amount: (p.total_amount / 100).toFixed(2),
payment_id: p.payment_id,
payment_date: new Date(webhook.payload.timestamp).toLocaleDateString(),
currency: p.currency || "USD"
}
}
],
from: {
email: "payments@yourdomain.com",
name: "Your Company"
},
template_id: "d-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
};
}
return webhook;
}
```
### Subscription Welcome Email
```javascript subscription_welcome.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "subscription.active") {
const s = webhook.payload.data;
webhook.url = "https://api.sendgrid.com/v3/mail/send";
webhook.payload = {
personalizations: [
{
to: [{ email: s.customer.email }],
dynamic_template_data: {
customer_name: s.customer.name,
subscription_id: s.subscription_id,
product_name: s.product_id,
amount: (s.recurring_pre_tax_amount / 100).toFixed(2),
frequency: s.payment_frequency_interval,
next_billing: new Date(s.next_billing_date).toLocaleDateString()
}
}
],
from: {
email: "welcome@yourdomain.com",
name: "Your Company"
},
template_id: "d-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
};
}
return webhook;
}
```
### Payment Failure Notification
```javascript payment_failure.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "payment.failed") {
const p = webhook.payload.data;
webhook.url = "https://api.sendgrid.com/v3/mail/send";
webhook.payload = {
personalizations: [
{
to: [{ email: p.customer.email }],
dynamic_template_data: {
customer_name: p.customer.name,
payment_amount: (p.total_amount / 100).toFixed(2),
error_message: p.error_message || "Payment processing failed",
payment_id: p.payment_id,
retry_link: `https://yourdomain.com/retry-payment/${p.payment_id}`
}
}
],
from: {
email: "support@yourdomain.com",
name: "Your Company Support"
},
template_id: "d-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
};
}
return webhook;
}
```
## Tips
* Use SendGrid dynamic templates for personalized content
* Include relevant payment data in template variables
* Set proper from addresses and sender names
* Use template IDs for consistent email formatting
* Include unsubscribe links for compliance
## Troubleshooting
* Verify API Key has Mail Send permissions
* Check that template IDs are valid and active
* Ensure recipient email addresses are valid
* Review SendGrid sending limits and quotas
* Validate JSON structure matches SendGrid API format
* Check that all required fields are present
* Ensure template data variables are properly formatted
* Verify from email addresses are verified in SendGrid
# Slack
Source: https://docs.dodopayments.com/integrations/slack
Integrate Dodo Payments with Slack to receive real-time notifications about your payments.
## Introduction
The Dodo Payments Slack integration allows you to receive real-time notifications about your payments directly in your Slack workspace. This integration enables you to stay updated on the status of your payments, track transactions, and manage your payments more efficiently.
This integration uses our webhook management portal to automatically transform Dodo Payments webhook events into Slack-compatible messages. No additional coding is required - just configure the connector and start receiving notifications.
## Getting Started
Go to the Webhook section in your Dodo Payments dashboard. Click the + Add Endpoint button, then open the webhook dropdown to reveal other integrations.
Select the Slack integration and click Connect your Slack workspace.
Grant the necessary permissions to the Incoming Webhooks Slack App so it can post messages in your chosen channel.
Add or edit the transformation code to customize your Slack notifications for your use case. You can use the pre-made templates or write your own logic.
Test your transformation code with custom or pre-made event payloads. Once you're satisfied, click Create to activate the integration.
🎉 You have successfully created the Slack integration! Your Dodo Payments events will now be delivered to your selected Slack channel in real time.
## Transformation Code Examples
### Basic Payment Notifications
This transformation sends simple text messages for payment events:
```javascript payment_notifs.js icon="js" expandable theme={null}
function handler(webhook) {
switch (webhook.eventType) {
case "payment.succeeded":
webhook.payload = {
text: `✅ Payment Successful\nAmount: $${(webhook.payload.data.total_amount / 100).toFixed(2)}\nCustomer: ${webhook.payload.data.customer.email}\nPayment ID: ${webhook.payload.data.payment_id}`
};
break;
case "payment.failed":
webhook.payload = {
text: `❌ Payment Failed\nAmount: $${(webhook.payload.data.total_amount / 100).toFixed(2)}\nCustomer: ${webhook.payload.data.customer.email}\nReason: ${webhook.payload.data.error_message || 'Unknown'}`
};
break;
case "payment.processing":
webhook.payload = {
text: `⏳ Payment Processing\nAmount: $${(webhook.payload.data.total_amount / 100).toFixed(2)}\nCustomer: ${webhook.payload.data.customer.email}`
};
break;
}
return webhook;
}
```
### Rich Subscription Notifications
This transformation creates rich Slack messages with attachments for subscription events:
```javascript subscription_notifs.js icon="js" expandable theme={null}
function handler(webhook) {
switch (webhook.eventType) {
case "subscription.active":
webhook.payload = {
attachments: [{
color: "good",
title: "🎉 Subscription Activated",
fields: [
{
title: "Customer",
value: webhook.payload.data.customer.email,
short: true
},
{
title: "Product ID",
value: webhook.payload.data.product_id,
short: true
},
{
title: "Amount",
value: `$${(webhook.payload.data.recurring_pre_tax_amount / 100).toFixed(2)}/${webhook.payload.data.payment_frequency_interval}`,
short: true
},
{
title: "Next Billing",
value: new Date(webhook.payload.data.next_billing_date).toLocaleDateString(),
short: true
}
],
footer: "Dodo Payments",
ts: Math.floor(new Date(webhook.payload.timestamp).getTime() / 1000)
}]
};
break;
case "subscription.cancelled":
webhook.payload = {
attachments: [{
color: "warning",
title: "⚠️ Subscription Cancelled",
fields: [
{
title: "Customer",
value: webhook.payload.data.customer.email,
short: true
},
{
title: "Product ID",
value: webhook.payload.data.product_id,
short: true
},
{
title: "Cancellation Date",
value: new Date(webhook.payload.data.cancelled_at).toLocaleDateString(),
short: true
},
{
title: "Cancel at Next Billing",
value: webhook.payload.data.cancel_at_next_billing_date ? "Yes" : "No",
short: true
}
],
footer: "Dodo Payments",
ts: Math.floor(new Date(webhook.payload.timestamp).getTime() / 1000)
}]
};
break;
case "subscription.renewed":
webhook.payload = {
attachments: [{
color: "good",
title: "🔄 Subscription Renewed",
fields: [
{
title: "Customer",
value: webhook.payload.data.customer.email,
short: true
},
{
title: "Product ID",
value: webhook.payload.data.product_id,
short: true
},
{
title: "Amount",
value: `$${(webhook.payload.data.recurring_pre_tax_amount / 100).toFixed(2)}`,
short: true
},
{
title: "Next Billing",
value: new Date(webhook.payload.data.next_billing_date).toLocaleDateString(),
short: true
}
],
footer: "Dodo Payments",
ts: Math.floor(new Date(webhook.payload.timestamp).getTime() / 1000)
}]
};
break;
}
return webhook;
}
```
### Dispute Management Notifications
This transformation handles dispute events with appropriate colors and urgency:
```javascript dispute_notifs.js icon="js" expandable theme={null}
function handler(webhook) {
switch (webhook.eventType) {
case "dispute.opened":
webhook.payload = {
attachments: [{
color: "danger",
title: "🚨 New Dispute Opened",
fields: [
{
title: "Payment ID",
value: webhook.payload.data.payment_id,
short: true
},
{
title: "Amount",
value: `$${(webhook.payload.data.amount / 100).toFixed(2)}`,
short: true
},
{
title: "Status",
value: webhook.payload.data.dispute_status,
short: true
},
{
title: "Stage",
value: webhook.payload.data.dispute_stage,
short: true
},
{
title: "Remarks",
value: webhook.payload.data.remarks || "No remarks",
short: false
}
],
footer: "Dodo Payments - Action Required",
ts: Math.floor(new Date(webhook.payload.timestamp).getTime() / 1000)
}]
};
break;
case "dispute.won":
webhook.payload = {
attachments: [{
color: "good",
title: "✅ Dispute Won",
fields: [
{
title: "Payment ID",
value: webhook.payload.data.payment_id,
short: true
},
{
title: "Amount",
value: `$${(webhook.payload.data.amount / 100).toFixed(2)}`,
short: true
},
{
title: "Status",
value: webhook.payload.data.dispute_status,
short: true
},
{
title: "Resolution",
value: "Dispute resolved in your favor",
short: false
}
],
footer: "Dodo Payments",
ts: Math.floor(new Date(webhook.payload.timestamp).getTime() / 1000)
}]
};
break;
case "dispute.lost":
webhook.payload = {
attachments: [{
color: "danger",
title: "❌ Dispute Lost",
fields: [
{
title: "Payment ID",
value: webhook.payload.data.payment_id,
short: true
},
{
title: "Amount",
value: `$${(webhook.payload.data.amount / 100).toFixed(2)}`,
short: true
},
{
title: "Status",
value: webhook.payload.data.dispute_status,
short: true
},
{
title: "Impact",
value: "Funds will be debited from your account",
short: false
}
],
footer: "Dodo Payments - Review Required",
ts: Math.floor(new Date(webhook.payload.timestamp).getTime() / 1000)
}]
};
break;
}
return webhook;
}
```
### Comprehensive All-Events Handler
This transformation handles all event types with consistent formatting:
```javascript all_events_notifs.js icon="js" expandable theme={null}
function handler(webhook) {
const event = webhook.payload.data;
const timestamp = new Date(webhook.payload.timestamp).toLocaleString();
let color, emoji, title, fields = [];
switch (webhook.eventType) {
case "payment.succeeded":
color = "good";
emoji = "✅";
title = "Payment Successful";
fields = [
{ title: "Amount", value: `$${(event.total_amount / 100).toFixed(2)}`, short: true },
{ title: "Customer", value: event.customer.email, short: true },
{ title: "Payment ID", value: event.payment_id, short: true },
{ title: "Method", value: event.payment_method || "Unknown", short: true }
];
break;
case "payment.failed":
color = "danger";
emoji = "❌";
title = "Payment Failed";
fields = [
{ title: "Amount", value: `$${(event.total_amount / 100).toFixed(2)}`, short: true },
{ title: "Customer", value: event.customer.email, short: true },
{ title: "Reason", value: event.error_message || "Unknown", short: false }
];
break;
case "subscription.active":
color = "good";
emoji = "🎉";
title = "Subscription Activated";
fields = [
{ title: "Customer", value: event.customer.email, short: true },
{ title: "Product ID", value: event.product_id, short: true },
{ title: "Amount", value: `$${(event.recurring_pre_tax_amount / 100).toFixed(2)}/${event.payment_frequency_interval}`, short: true },
{ title: "Next Billing", value: new Date(event.next_billing_date).toLocaleDateString(), short: true }
];
break;
case "subscription.cancelled":
color = "warning";
emoji = "⚠️";
title = "Subscription Cancelled";
fields = [
{ title: "Customer", value: event.customer.email, short: true },
{ title: "Product ID", value: event.product_id, short: true },
{ title: "Cancellation Date", value: new Date(event.cancelled_at).toLocaleDateString(), short: true },
{ title: "Cancel at Next Billing", value: event.cancel_at_next_billing_date ? "Yes" : "No", short: true }
];
break;
case "refund.succeeded":
color = "good";
emoji = "💰";
title = "Refund Processed";
fields = [
{ title: "Amount", value: `$${(event.amount / 100).toFixed(2)}`, short: true },
{ title: "Refund ID", value: event.refund_id, short: true },
{ title: "Payment ID", value: event.payment_id, short: true },
{ title: "Reason", value: event.reason || "Not specified", short: true }
];
break;
case "dispute.opened":
color = "danger";
emoji = "🚨";
title = "New Dispute Opened";
fields = [
{ title: "Payment ID", value: event.payment_id, short: true },
{ title: "Amount", value: `$${(event.amount / 100).toFixed(2)}`, short: true },
{ title: "Status", value: event.dispute_status, short: true },
{ title: "Stage", value: event.dispute_stage, short: true },
{ title: "Remarks", value: event.remarks || "No remarks", short: false }
];
break;
case "license_key.created":
color = "good";
emoji = "🔑";
title = "License Key Created";
fields = [
{ title: "License ID", value: event.id, short: true },
{ title: "Product ID", value: event.product_id, short: true },
{ title: "License Key", value: event.key.substring(0, 8) + "...", short: true },
{ title: "Expires", value: event.expires_at ? new Date(event.expires_at).toLocaleDateString() : "Never", short: true }
];
break;
default:
// Handle any other events with a generic format
color = "warning";
emoji = "ℹ️";
title = webhook.eventType.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
fields = [
{ title: "Event Type", value: webhook.eventType, short: true },
{ title: "Timestamp", value: timestamp, short: true }
];
}
webhook.payload = {
attachments: [{
color: color,
title: `${emoji} ${title}`,
fields: fields,
footer: "Dodo Payments",
ts: Math.floor(new Date(webhook.payload.timestamp).getTime() / 1000)
}]
};
return webhook;
}
```
## Best Practices
To make your Slack notifications effective:
* Use rich message attachments with colors, fields, and formatting for clarity and actionability.
* Always include key data such as amounts, customer emails, and IDs for quick identification.
* Choose colors that match the event type: green (`good`) for success, red (`danger`) for disputes or failures, yellow (`warning`) for cancellations, and blue (`#36a64f`) for informational events.
* Add timestamps to help track when each event occurred.
**Handle Sensitive Data**: Be careful not to include sensitive information like full license keys or personal data in Slack messages. Consider truncating or masking sensitive values.
## Troubleshooting
* Verify that the Slack webhook URL is correct and active
* Check that the transformation code is valid JavaScript
* Ensure the selected event types are being triggered
* Verify that your Slack app has the necessary permissions
* Check the webhook management portal for transformation error logs
* Verify that the webhook payload structure matches your transformation code
* Test your transformation code with sample data
* Ensure all required fields are present in the webhook payload
* Confirm that the events you want to receive are enabled in your Dodo Payments webhook configuration
* Check that the event types are selected in your Slack connector configuration
* Verify that your endpoint is properly configured to receive the events
# Windmill
Source: https://docs.dodopayments.com/integrations/windmill
Trigger Windmill workflows and scripts based on Dodo Payments events for custom automation.
## Introduction
Execute custom workflows and scripts in Windmill when payment events occur. Run database operations, send notifications, process data, and automate complex business logic with Windmill's powerful workflow engine.
This integration requires your Windmill webhook URL from your workflow configuration.
## Getting Started
In your Dodo Payments dashboard, navigate to Webhooks → + Add Endpoint and expand the integrations dropdown.
Choose the Windmill integration card.
In Windmill, create a new workflow and copy the webhook URL from the trigger configuration.
Paste the Windmill webhook URL into the endpoint configuration.
Edit the transformation code to format events for your Windmill workflow.
Test with sample payloads and click Create to activate the integration.
🎉 Payment events will now trigger your Windmill workflows automatically.
## Transformation Code Examples
### Basic Workflow Payload
```javascript basic_workflow.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "payment.succeeded") {
const p = webhook.payload.data;
webhook.payload = {
event_type: webhook.eventType,
payment_id: p.payment_id,
amount: (p.total_amount / 100).toFixed(2),
currency: p.currency || "USD",
customer_email: p.customer.email,
customer_name: p.customer.name,
payment_method: p.payment_method || "unknown",
timestamp: webhook.payload.timestamp,
metadata: {
business_id: p.business_id,
product_id: p.product_id
}
};
}
return webhook;
}
```
### Subscription Workflow Handler
```javascript subscription_workflow.js icon="js" expandable theme={null}
function handler(webhook) {
const s = webhook.payload.data;
switch (webhook.eventType) {
case "subscription.active":
webhook.payload = {
event_type: "subscription_started",
subscription_id: s.subscription_id,
customer_email: s.customer.email,
customer_name: s.customer.name,
product_id: s.product_id,
amount: (s.recurring_pre_tax_amount / 100).toFixed(2),
frequency: s.payment_frequency_interval,
next_billing: s.next_billing_date,
customer_id: s.customer.customer_id,
timestamp: webhook.payload.timestamp
};
break;
case "subscription.cancelled":
webhook.payload = {
event_type: "subscription_cancelled",
subscription_id: s.subscription_id,
customer_email: s.customer.email,
cancelled_at: s.cancelled_at,
cancel_at_next_billing: s.cancel_at_next_billing_date,
customer_id: s.customer.customer_id,
timestamp: webhook.payload.timestamp
};
break;
}
return webhook;
}
```
### Dispute Workflow Handler
```javascript dispute_workflow.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType.startsWith("dispute.")) {
const d = webhook.payload.data;
webhook.payload = {
event_type: webhook.eventType,
dispute_id: d.dispute_id,
payment_id: d.payment_id,
amount: (d.amount / 100).toFixed(2),
status: d.dispute_status,
stage: d.dispute_stage,
remarks: d.remarks || "",
urgent: webhook.eventType === "dispute.opened",
business_id: d.business_id,
timestamp: webhook.payload.timestamp
};
}
return webhook;
}
```
## Common Windmill Use Cases
* Update customer records in PostgreSQL/MySQL
* Log payment events to data warehouses
* Sync data to external systems
* Update inventory levels
* Track analytics metrics
* Calculate revenue metrics
* Process refunds and adjustments
* Handle subscription lifecycle
* Generate reports and exports
* Validate payment data
* Send data to analytics platforms
* Update CRM systems
* Trigger email campaigns
* Create calendar events
* Send SMS notifications
## Tips
* Structure payload data for easy workflow processing
* Include all relevant metadata for business logic
* Use consistent field naming across events
* Include timestamps for workflow timing
* Leverage Windmill's built-in error handling
## Troubleshooting
* Verify webhook URL is correct and active
* Check that Windmill workflow is published and active
* Ensure payload structure matches workflow expectations
* Review Windmill execution logs for errors
* Check workflow input parameter mapping
* Verify data types match expected formats
* Test workflow with sample data
* Review Windmill script execution logs
# WooCommerce Plugin
Source: https://docs.dodopayments.com/integrations/woocommerce-plugin
Integrate Dodo Payments seamlessly with your WooCommerce store to accept global payments through UPI, cards, and more.
## Introduction
The Dodo Payments WooCommerce plugin provides a seamless integration with your store, enabling you to accept payments through multiple methods including UPI, cards, net banking, and digital wallets. This comprehensive guide will walk you through the installation and configuration process.
## Step-by-Step Installation Guide
1. Download the latest zip of the WooCommerce payment plugin.
Get the latest version of our WooCommerce plugin
Alternatively, you can install directly from the WordPress plugin directory:
Install Dodo Payments for WooCommerce from WordPress.org
2. Log in to your WordPress website as an administrator (we assume you already have WooCommerce set up)
3. Navigate to **Plugins → Add New Plugin**
4. Click on the **Upload Plugin** button and a file selection dialog will appear
5. Click **Browse…** and select the zip file you downloaded
6. Click the **Install Now** button to begin the installation process
7. After installation completes, click **Activate Plugin** or activate it from the **Installed Plugins** section
8. The plugin is now installed, but configuration is still required. Continue to the setup guide below.
## Configuration Guide
### Setting Up API Keys and Webhooks
1. Navigate to **WooCommerce → Settings → Payments** or click the **Payments** button in the left sidebar below the WooCommerce menu item
2. Enable the **Dodo Payments** payment provider if not already enabled, then click **Manage** to configure the plugin
3. You'll see various configuration options, each with helpful explanatory text. Begin by setting up your Live API Key
4. Log in to your Dodo Payments dashboard in **Live Mode**, then navigate to **Dodo Payments (Live Mode) > Developer > API Keys** or visit [this direct link](https://app.dodopayments.com/developer/api-keys) and click **Add API Key**
5. Give your API Key a descriptive name and click **Create**
6. Copy the generated API Key and paste it into the **Live API Key** field in your WooCommerce plugin settings
7. Next, set up the Webhook Signing Key to enable payment status synchronization between Dodo Payments and WooCommerce
8. Scroll to the bottom of the plugin settings page and copy the webhook URL displayed there
9. Return to your Dodo Payments dashboard and navigate to **Dodo Payments (Live Mode) > Developer > Webhooks** and click **Add Webhook**
10. Paste the URL you copied in step 8 into the dialog and click **Add Webhook**
11. After creating the webhook, click the eye icon next to the redacted **Signing Key** to reveal and copy it
12. Paste the Signing Key into the **Live Webhook Signing Key** field in your plugin settings and save the changes
13. Review the remaining settings such as **Global Tax Category** and **All Prices are Tax Inclusive**, as these options determine how products sync from WooCommerce to Dodo Payments. Note that **Test API Key** and **Test Webhook Signing Key** are only needed if you plan to use Test Mode
## You're All Set!
Your WooCommerce store is now integrated with Dodo Payments! Customers can select Dodo Payments at checkout to access all supported payment methods.
When customers choose Dodo Payments, they'll be seamlessly redirected to the Dodo Payments checkout page to complete their transaction.
## Key Features
* **Seamless Checkout Experience**: Redirect customers to a secure, optimized checkout page
* **Multiple Payment Methods**: Support for various local and international payment options
* **Real-time Status Updates**: Instant order status synchronization via webhooks
* **Detailed Transaction Records**: Comprehensive payment history in your WooCommerce dashboard
* **Multi-currency Support**: Accept payments in multiple currencies
## Troubleshooting
### Common Issues
1. **Payment Processing Failures**
* Verify API keys are correctly configured
* Ensure webhook URL is properly set up
* Check that your SSL certificate is valid
2. **Webhook Synchronization Problems**
* Confirm webhook signing key matches
* Review server logs for potential errors
* Verify your server can receive incoming webhook requests
### Support Resources
For additional assistance:
* Contact us at [support@dodopayments.com](mailto:support@dodopayments.com)
* Refer to our [comprehensive documentation](https://docs.dodopayments.com)
## Maintenance Best Practices
1. Regularly update the plugin for security patches and new features
2. Test all payment flows in sandbox mode before processing live transactions
3. Keep WordPress and WooCommerce updated to the latest versions
4. Implement regular site backups
5. Monitor transaction logs for any unusual activity
6. Store API keys securely and never share them publicly
# Zapier
Source: https://docs.dodopayments.com/integrations/zapier
Connect Dodo Payments to 5000+ apps through Zapier webhooks for unlimited automation possibilities.
## Introduction
Connect Dodo Payments to thousands of apps and services through Zapier. Automate workflows by triggering Zaps when payment events occur, from sending emails to updating spreadsheets, creating tasks, and much more.
This integration requires a Zapier webhook URL from your Zap configuration.
## Getting Started
In your Dodo Payments dashboard, navigate to Webhooks → + Add Endpoint and expand the integrations dropdown.
Choose the Zapier integration card.
In Zapier, create a new Zap with "Webhooks by Zapier" as the trigger. Copy the webhook URL.
Paste the Zapier webhook URL into the endpoint configuration.
Edit the transformation code to format data for your Zapier workflow.
Test with sample payloads and click Create to activate the integration.
🎉 Payment events will now trigger your Zapier workflows automatically.
## Transformation Code Examples
### Basic Webhook Payload
```javascript basic_webhook.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType === "payment.succeeded") {
const p = webhook.payload.data;
webhook.payload = {
event_type: webhook.eventType,
payment_id: p.payment_id,
amount: (p.total_amount / 100).toFixed(2),
currency: p.currency || "USD",
customer_email: p.customer.email,
customer_name: p.customer.name,
payment_method: p.payment_method || "unknown",
timestamp: webhook.payload.timestamp
};
}
return webhook;
}
```
### Subscription Event Handler
```javascript subscription_webhook.js icon="js" expandable theme={null}
function handler(webhook) {
const s = webhook.payload.data;
switch (webhook.eventType) {
case "subscription.active":
webhook.payload = {
event_type: "subscription_started",
subscription_id: s.subscription_id,
customer_email: s.customer.email,
customer_name: s.customer.name,
product_id: s.product_id,
amount: (s.recurring_pre_tax_amount / 100).toFixed(2),
frequency: s.payment_frequency_interval,
next_billing: s.next_billing_date,
timestamp: webhook.payload.timestamp
};
break;
case "subscription.cancelled":
webhook.payload = {
event_type: "subscription_cancelled",
subscription_id: s.subscription_id,
customer_email: s.customer.email,
cancelled_at: s.cancelled_at,
cancel_at_next_billing: s.cancel_at_next_billing_date,
timestamp: webhook.payload.timestamp
};
break;
}
return webhook;
}
```
### Dispute Alert Handler
```javascript dispute_webhook.js icon="js" expandable theme={null}
function handler(webhook) {
if (webhook.eventType.startsWith("dispute.")) {
const d = webhook.payload.data;
webhook.payload = {
event_type: webhook.eventType,
dispute_id: d.dispute_id,
payment_id: d.payment_id,
amount: (d.amount / 100).toFixed(2),
status: d.dispute_status,
stage: d.dispute_stage,
remarks: d.remarks || "",
urgent: webhook.eventType === "dispute.opened",
timestamp: webhook.payload.timestamp
};
}
return webhook;
}
```
## Popular Zapier Use Cases
* Send Gmail/Outlook emails for payment confirmations
* Create email sequences in Mailchimp/ConvertKit
* Send Slack/Discord notifications
* Create Google Sheets records
* Add contacts to HubSpot/Salesforce
* Create deals in Pipedrive/Close
* Update customer records in Airtable
* Log activities in Monday.com
* Create tasks in Asana/Trello
* Add to-do items in Notion
* Create calendar events
* Send SMS notifications via Twilio
## Tips
* Keep payload structure simple for easy Zapier parsing
* Use consistent field names across all events
* Include timestamps for workflow timing
* Test your Zap with sample data before going live
* Use Zapier's built-in filters for conditional logic
## Troubleshooting
* Verify webhook URL is correct and active
* Check that Zap is turned on in Zapier
* Ensure payload structure matches Zapier expectations
* Test webhook delivery in Zapier dashboard
* Check field names in Zapier action steps
* Verify data types match expected formats
* Use Zapier's test feature to debug mapping
* Ensure transformation code returns valid JSON
# Welcome to Dodo Payments
Source: https://docs.dodopayments.com/introduction
The complete payments & billing platform for SaaS, AI, and digital products. Accept payments globally, set up flexible billing models, and monetize your product in minutes - all without building your own infrastructure.
## What is Dodo Payments?
Dodo Payments is the **all-in-one engine** to launch, scale, and monetize worldwide. Designed for the new generation of digital SaaS and AI products, we handle everything from **payments to billing to distribution** - all without extra engineering.
**Built for SaaS and AI Products**: Handle one-time payments, subscriptions, credits, seats, tokens, or any usage metric with our optimized billing infrastructure.
Let Sentra write your integration code for you.
Use our AI assistant in VS Code, Cursor, or Windsurf to generate SDK/API code, webhooks, and more - just by describing what you want.
Try Sentra: AI-Powered Integration →
### Why Choose Dodo Payments?
One solution for payments, billing, and distribution—no more piecing tools together
Go global without complexity: built-in coverage in 150+ countries, 80+ currencies, and 30+ payment methods
Seamless APIs and SDKs for fast integration, plus Sentra for no-code/low-code workflows
Start collecting revenue in minutes, not weeks
Usage-based, subscriptions with addons, or one-time payments are built in
Handle credits, seats, tokens, or any usage metric
## Quick Start Guide
Get up and running with Dodo Payments in just a few steps:
Sign up at [app.dodopayments.com](https://app.dodopayments.com/signup) using your email.
You'll receive a confirmation email to verify your account.
Choose your billing model and add your product to start accepting payments.
Perfect for digital downloads, courses, or single purchases
Ideal for SaaS, memberships, or recurring services
Bill customers for API calls, credits, tokens, or any usage metric—ideal for AI and SaaS
Select the integration approach that best fits your needs and technical requirements.
**Perfect for getting started quickly:**
* Create **Payment Links** from your dashboard
* Share via email, social media, or embed in websites
* Start accepting payments immediately
**Best for seamless, on-brand payments:**
* Launch checkout as an overlay on your site or app
* Keep users on your page for a frictionless experience
* Customize appearance to match your brand
Add our overlay checkout with a single line of code
**Best for advanced, fully customized solutions:**
* Use our TypeScript/Python/Go/Java/Kotlin/C#/Ruby SDK for seamless integration
* Programmatic access to all payment, subscription, and customer portal APIs
* Deep integration with your backend, workflows, and data models
* Full support for webhooks, advanced features, and automation
Let Sentra write your integration code for you.
Use our AI assistant in VS Code, Cursor, or Windsurf to generate SDK/API code, webhooks, and more - just by describing what you want.
Try Sentra: AI-Powered Integration →
Learn how to install and use our official SDK
Explore the full API and SDK documentation
## Developer Integration
Let Sentra write your integration code for you.
Use our AI assistant in VS Code, Cursor, or Windsurf to generate SDK/API code, webhooks, and more - just by describing what you want.
Try Sentra: AI-Powered Integration →
### Framework Adapters
Integrate in under 10 lines of code with our framework adapters. Choose from our recommended frameworks or explore all supported options.
#### Recommended Frameworks
React-based full-stack framework with App Router support
Authentication framework with built-in integrations
Open source Firebase alternative with Postgres and Auth
Backend-as-a-Service with real-time capabilities
### Powerful SDKs
Speed up development with our official SDKs. Choose from our recommended languages or explore all supported options.
### Billing SDK
Stop reinventing the wheel. Use production-ready, accessible billing components, from pricing cards to subscription dashboards, built for React and ShadCN.
Beautiful, customizable billing components, pricing tables, subscription management, and usage meters - save development time with production-ready UI for billing and payments.
**Open Source & Free**: BillingSDK is completely free to use and modify for personal and commercial projects. Built by the Dodo Payments team for developers.
### In-App Purchases for Android and iOS
Build rich in-app payment experiences with our lightweight payment links and secure React Native SDKs optimized for iOS and Android.
Complete guide for integrating payments in mobile applications
Native mobile SDK for iOS and Android with seamless payment flows
## External Integrations
Connect Dodo Payments with your favorite tools and platforms to automate workflows, send notifications, and enhance your payment operations.
**Automate Everything**: Set up real-time notifications, trigger workflows, sync customer data, and more with our growing library of integrations.
### Popular Integrations
Quickly connect Dodo Payments to the most used design and productivity tools to streamline your workflow and automate payment operations.
Add payments to Framer websites
Accept payments on WooCommerce stores
Manage payments from Raycast
Access Dodo Payments in Figma
Stay informed about payment events with real-time notifications in your team communication tools.
Send payment notifications and alerts to Slack channels
Get instant payment updates in your Discord server
Receive payment notifications directly in Teams channels
Sync customer data and trigger email campaigns based on payment events.
Send transactional emails with Resend
Automate email campaigns with SendGrid
Trigger email sequences with Loops
Sync customer data to Customer.io
Connect your payment data with CRM platforms to streamline sales and customer management.
Sync customers and deals with HubSpot CRM
Automate sales workflows with Close CRM
Build powerful automations and data pipelines with workflow automation platforms.
Connect to 5,000+ apps with Zapier
Build custom workflows with Windmill
Create event-driven workflows with Inngest
Stream payment data to Segment CDP
Explore our complete library of integrations with detailed setup guides
## Need Help?
Explore comprehensive guides and API references
Full OpenAPI reference, endpoints, parameters, and example requests for all Dodo Payments APIs
Join our Discord community for help and discussions
Get direct help from our support team
# Migrate to Dodo Payments
Source: https://docs.dodopayments.com/migrate-to-dodo
Easily migrate your data from other payment providers to Dodo Payments using our CLI tool
**Switching from another payment provider to Dodo Payments?** Our migration tool makes it easy to move your **products, customers, and discount codes without losing any data.** The process is safe, guided, and can be done in just a few minutes.
We currently support migrations from **Lemon Squeezy, Stripe, Polar.sh, and Paddle**.
### Supported Migrations
Migrate all your products and pricing details.
Transfer your complete customer database, including contact details.
Move all discount codes and promotional offers seamlessly.
## Get Started
First, install the migration tool:
```bash theme={null}
npm install -g dodo-migrate
```
You'll need Node.js ≥ 18 installed on your computer.
## Migrate Your Data
### If you're coming from Lemon Squeezy:
```bash theme={null}
dodo-migrate lemonsqueezy
```
### If you're coming from Stripe:
```bash theme={null}
dodo-migrate stripe
```
### If you're coming from Polar.sh:
```bash theme={null}
dodo-migrate polar
```
### If you're coming from Paddle:
```bash theme={null}
dodo-migrate paddle
```
The tool will walk you through everything step by step. You'll need:
* Your provider's API key (Lemon Squeezy, Stripe, Polar.sh, Paddle, etc.)
* Your Dodo Payments API key
* Whether you want to test first (recommended) or go live
* Which Dodo Payments brand to migrate to
## Why Use Our Migration Tool?
* **No data loss** - Everything transfers safely
* **Test first** - Try it in test mode before going live
* **Guided process** - We walk you through each step
* **Resume if needed** - If something goes wrong, you can pick up where you left off
## Advanced Usage
If you prefer to run the migration without prompts, you can provide all the details upfront:
### Lemon Squeezy migration:
```bash theme={null}
dodo-migrate lemonsqueezy \
--provider-api-key=lsq_XXXXXXXXXXXXXXXX \
--dodo-api-key=dp_XXXXXXXXXXXXXXXX \
--mode=test_mode \
--dodo-brand-id=brand_XXXXXX
```
### Stripe migration:
```bash theme={null}
dodo-migrate stripe \
--provider-api-key=sk_test_XXXXXXXXXXXXXXXX \
--dodo-api-key=dp_XXXXXXXXXXXXXXXX \
--mode=test_mode \
--dodo-brand-id=brand_XXXXXX \
--migrate-types=products,coupons
```
### Polar.sh migration:
```bash theme={null}
dodo-migrate polar \
--provider-api-key=polar_org_XXXXXXXXXXXXXXXX \
--dodo-api-key=dp_XXXXXXXXXXXXXXXX \
--mode=test_mode \
--dodo-brand-id=brand_XXXXXX \
--migrate-types=products,discounts,customers
```
### Paddle migration:
```bash theme={null}
dodo-migrate paddle \
--provider-api-key=paddle_XXXXXXXXXXXXXXXX \
--dodo-api-key=dp_XXXXXXXXXXXXXXXX \
--mode=test_mode \
--dodo-brand-id=brand_XXXXXX
```
## Need Help?
* **Tool help**: `dodo-migrate --help`
* **Lemon Squeezy help**: `dodo-migrate lemonsqueezy --help`
* **Stripe help**: `dodo-migrate stripe --help`
* **Polar.sh help**: `dodo-migrate polar --help`
* **Paddle help**: `dodo-migrate paddle --help`
* **Community support**: [Discord](https://discord.gg/bYqAp4ayYh)
* **Report issues**: [GitHub](https://github.com/dodopayments/dodo-migrate)
## Update the Tool
```bash theme={null}
# Update to latest version
npm update -g dodo-migrate
# Remove the tool
npm uninstall -g dodo-migrate
```
# My Accounts
Source: https://docs.dodopayments.com/miscellaneous/accounts
Merchant can access and manage their personal account information such as Contact Name, Profile Image and phone number for a more personalised experience through the my account section.
## **Business Profile**
Go to Business → Business Profile.
Update the business details, such as the business logo and Brand display name (used on invoices), then click Save Changes.
## **User Account**
Click the icon in the top right, then select Profile.
Update your personal information (Contact Name, Profile Image, phone number) for a personalized experience, then click Save Changes.
## Managing Multiple Businesses
Merchants with more than one business can easily add and verify additional businesses through their Dodo Payments account.
* **How to Add Multiple Businesses**:
1. Navigate to the **Business Settings** section of the dashboard.
2. Click **Add Another Business** and provide the required details.
3. Submit the business registration documents for manual review.
4. After approval, merchants can switch between verified businesses to manage their products, payments, and reports.
# FAQs
Source: https://docs.dodopayments.com/miscellaneous/faq
Comprehensive answers to common questions about Dodo Payments, covering onboarding, payments, subscriptions, compliance, and technical integration to help you navigate and make the most of our platform.
## Getting Started & Account Setup
A: Visit our dashboard and enter the Login email and password shared with you in your welcome email. In case you face any issues please reach out to our support team.
A: The documents required for verification include a valid government-issued ID (for identity verification) and business registration documents (for business verification). For details check the [verification process page](/miscellaneous/verification-process).
A: If your verification is rejected, you will receive an email with the reason for rejection. You can contact our support team to resolve any issues, correct documentation, and reapply. The resubmit option will be enabled by our verification team if necessary.
A: The verification process typically takes **24-72 working hours**. However, if additional documents are required, the process may take longer.
A: We need your website to understand the kind of products you are selling and verify your business for live access. If you do not have a website or are in the process of building one please reach out to us at [support@dodopayments.com](mailto:support@dodopayments.com)
A: Yes, we support unregistered businesses as well. You do not need a registered business to use Dodo Payments. You can onboard as an individual and start receiving international payments without any hassle. You will still need to submit Identity Verification forms and Business Verification forms as an unregistered business for us to onboard you.
A: No, you can manage multiple businesses through a single account. To add a new business, click the + icon in the bottom left corner of the dashboard and select the Add Business option.
You can refer to /miscellaneous/accounts#managing-multiple-businesses for more details
A: No, there is no need for you to wait. You can start integrating using the Test mode and once your verification is successful you can start making Live payments in production. Learn more about Test and Live modes here,
/miscellaneous/test-mode-vs-live-mode
A: Make sure you have submitted the identity and business verification documents in the settings tab. If you have already submitted them and it has been more than 72 hours, check for the reason it is on hold on the same page and contact the support team.
A: Yes, we do offer an affiliate program!
Discover how to join our affiliate program, commission rates, and how to start earning.
A: Currently you cannot delete products or customers as these are need to maintain records for future compliance. We are currently working on an archive product feature which will allow you to remove the product from your active products section.
A: You can add your business logo and your business name to the payment page. To upload them, visit the business profile page on your dashboard.
A: Yes. You can:
* Go to Dashboard → Settings → Business Setting
* Update your logo, brand name, and support email.
* Changes will reflect on both static and dynamic links
A: No. The hosted checkout flow does not support embedding in an iframe.
Instead:
* Redirect users to the Dodo checkout URL
* Or use our Overlay Checkout Feature. [Overlay Checkout Docs](/developer-resources/overlay-checkout)
## Verification, Tax & Compliance
A: Dodo Payments typically takes 24–72 working hours to process verifications. However, delays can occur due to:
* High volume of applications
* Weekends and public holidays
* Incomplete or incorrect submissions
* Manual reviews for risk or compliance
If it's been more than 72 hours, please check your email or Dodo Payments dashboard for any required action. You can also DM your registered email to the team on Discord for a follow-up.
A: Use a proper PDF editor like:
* Adobe Acrobat
* Mac Preview (works for most)
* Smallpdf or PDFescape (online editors)
If there's a cross mark on the signature, make sure:
* You're saving the file after applying the signature.
* The tool is flattening the signature layer (some editors don't).
Avoid signing in browser previewers—they don't always embed digital signatures properly.
A: If you selected the wrong entity type (e.g., chose “Organization” but you’re an individual or a Sole Prop with GST), your verification might be On-hold, requiring you to change or update the documentation.
Steps to fix:
Step 1: Inform support via Intercom or Discord who will help you enable resubmission for the Payout Compliance form.
Step 2: Choose the right registration type, and resubmit the required documents.
A: If you selected the wrong entity type (e.g., chose “Organization” but you’re an individual or a Sole Prop with GST), your verification might be On-hold, requiring you to change or update the documentation.
Steps to fix:
Step 1: Inform support via Intercom or Discord who will help you enable resubmission for the Payout Compliance form.
Step 2: Choose the right registration type, and resubmit the required documents.
A: Yes. Once your earnings cross the payout threshold (\$100), Dodo Payments prioritizes your verification.
This does not guarantee instant approval — ensure your business model and documents are compliant.
If you've crossed \$100 and still see delays, reach out to our support.
A: No. The person signing up must match the legal owner of the business and all submitted documents.
If your parent is the actual business owner:
* They should sign up using their email and mobile number.
* All documents should be in their name.
Using someone else's name for verification while you manage the business may lead to compliance rejection.
A: You can find the reason in:
* The Dodo Payments dashboard, under the verification section.
* Email notifications (if enabled)
If it's not visible, contact [compliance@dodopayments.com](mailto:compliance@dodopayments.com) or DM your registered email ID to a team member.
A: Yes — but only if:
* You've fixed the issues that led to rejection.
* You're not attempting to bypass risk decisions (e.g., deboarding for fraud concerns).
Creating multiple accounts without resolving the underlying issue will likely result in the new account being rejected as well.
A: If you see a message stating your account was previously deboarded by the risk team:
* It means Dodo Payments found significant compliance or risk issues with your activity or business.
* Attempting to create new accounts using the same identity or business details will likely be rejected again.
If you believe this is a mistake, email [compliance@dodopayments.com](mailto:compliance@dodopayments.com) and request a review — include clear evidence to support your case.
A: To avoid delays:
* Submit clear, properly formatted documents
* Avoid repeated messages/tags in Discord — this slows the queue
* Mention if you've crossed the payout threshold or have urgent timelines
* DM your registered email ID if asked
Most payout reviews are completed within 72 working hours, unless there's an issue with your submission.
A: These forms are tied to your entity type:
* W-8BEN is for Individuals
* W-8BEN-E is for Organizations
If you submitted the wrong one:
* Contact support to reset your form.
* Fill and upload the correct form type.
* Ensure the PAN is in the correct field (Line 6a for individuals).
Refer to IRS official form guides if confused.
A: No. If you're registered with a GST as an individual (sole proprietor), you're still considered an Individual.
✅ Use W-8BEN
✅ Select "Individual" in the registration
❌ Do not fill W-8BEN-E or select "Organization" unless you're a registered legal entity
A: Creating a new account to bypass a previous deboarding is not allowed. If you attempt this:
* The new account may also be flagged or rejected
* Risk decisions carry over across signups if the underlying business or person is the same
If you believe you were wrongly deboarded, email [compliance@dodopayments.com](mailto:compliance@dodopayments.com) with your case.
A: Dodo Payments automatically calculates and applies **GST** for Indian merchants based on your customer's location.
A: Yes, Dodo Payments supports international sales and manages all associated compliances.
A: Yes they are mandatory as we are the Merchant of Record and generate invoices, file taxes and ensure all purchases are compliant we require the Merchant's billing address. You can implement our API based checkout flow where you can pre-fill the customer's details if you have already collected them to ensure a smoother experience for your customers but we will need these details for every purchase.
A: Not directly. Dodo Payments acts as the Merchant of Record, handles customer-side tax, and remits where applicable. You still need to:
* File income tax in your home country (e.g., India)
* Submit W-8BEN(-E) to avoid default 30% US withholding
A: Use the following logic:
| You are... | Form to Submit | Merchant Type |
| ---------------------------- | -------------- | ------------- |
| Sole Proprietor, Freelancer | W-8BEN | Individual |
| Pvt Ltd, LLP, Registered Org | W-8BEN-E | Organization |
Still unsure?
* If your bank account is under your name, you're likely an individual.
* If it's under a company, you're an organization.
A: All US Citizens, and entity holders in the US must fill the W-9 Tax Form in the name of the registered entity or the Individual.
A: For Individuals, PAN is a mandatory requirement (for KYC and tax purposes). For registered businesses, either Business PAN or GSTIN can be submitted. GST is optional unless you exceed the registration threshold or want input credit
A: Use the form based on your merchant type:
* **Individual/Sole Proprietor**: W-8BEN
* **Private Limited or LLP**: W-8BEN-E
If you have a Udyam/MSME certificate but operate with a personal bank account, you're still considered an Individual.
A: Dodo handles sales tax for your customers. W-8BEN(-E) is needed for withholding tax on payouts to you.
Filing W-8BEN correctly can help you avoid double taxation under India–US tax treaties.
## Payments & Transactions
A: Dodo Payments supports a variety of payment methods, including **credit cards**, **debit cards**, and **digital wallets**. We also support one-time payments and recurring subscription payments for digital products.
A: You can track the status of payments from the **Payments** section of your dashboard. Each transaction is listed with a status indicator: **In Progress**, **Failed**, or **Successful**.
A: If a payment fails, check the **Payment Details Page** for more information. Common reasons include incorrect payment details, insufficient funds, or network issues. You can resend the payment link to your customer to retry the payment.
Yes. Even if you state No refunds in your terms or website, chargebacks are largely governed by the cardholder's bank and card network rules (e.g., Visa, Mastercard). A No refund policy does not override the card network's dispute resolution process. If a customer files a dispute, your business is still subject to a chargeback investigation. This means you must respond with evidence (proof of delivery, usage logs, terms acceptance, etc.) to fight or manage the dispute. If the customer's bank decides in favor of the cardholder, the chargeback fee and transaction reversal will apply regardless of your stated policy. We recommend having clear communications, tracking customer usage, and providing compelling evidence when chargebacks arise.
A: payment.status can be null for a short time:
* Right after creation but before processing completes
* During subscription billing failures or retries
Eventually, it becomes one of: succeeded, failed, or cancelled.
Use webhook events like payment.succeeded or payment.failed for more reliable status tracking.
A:
* For subscription payments, product\_cart is empty because there's no one-time item being bought
* product\_cart is only populated for one-time payments
A: You likely tested in Live Mode instead of Test Mode.
Live payments:
* Use real money
* Go through real banks
* May be flagged as suspicious if volume is low or inconsistent
✅ Always test using:
* Test Mode in the dashboard
* Test card numbers provided in the documentation
A: Checklist:
* Confirm webhook is configured in Dashboard > Developers -> Webhooks
* Check if webhook is failing silently due to non-2xx response
* Check logs: was a 429 or timeout returned?
A: It's likely you're relying solely on redirect\_url query params and not handling:
* Webhooks
* API confirmation call
✅ Fix:
* Always listen for payment.succeeded webhook
* Or query the API to confirm transaction after redirect
A: This can happen for several reasons:
* The billing country wasn't set to IN
* UPI was not enabled for your business
If you're a Non Indian Merchant, UPI won't appear for your customers.
A: Not currently. UPI is supported for one-time payments, but subscriptions rely on card mandates — UPI mandates are still in progress.
A: If you want users to enter billing info themselves:
* Use static links, which automatically collect billing
* For dynamic flows, you must collect info manually and pass it in the API
There's no built-in UI yet for collecting billing during dynamic link creation.
A: **Static payment links** are no-code options meant for quick use. They:
* Are configured from the dashboard
* Automatically collect billing info
* Cannot be customized per customer/session
**Dynamic payment links** are created via API. They:
* Require you to pass billing info explicitly
* Can be customized in real-time (price, user ID, redirect URL, etc.)
If you don't want to write code, go with static links. If you need flexibility or want to build custom workflows, use dynamic links.
A: On the first successful subscription payment, you will typically receive these three webhook if no trial period is involved:
* `subscription.created` webhook
* `payment.succeeded` webhook
* `subscription.renewal` webhook
A: Dodo supports both immediate and scheduled cancellations.
When a cancellation request is made:
* **Immediate cancellation**: The subscription.cancelled\_at timestamp is set immediately and the subscription ends at that moment
* **Scheduled cancellation**: Use the `cancel_at_next_billing_date` parameter in the subscription patch API to cancel at the end of the current billing period
A: Dodo Payments charges the customer immediately upon subscription creation if the product does not have a trial period.
For Indian cards and if there is a free trial is configured for the product, the payment will take 24 hours to process after the trial ends. This is due to RBI regulation.
## Payouts & Bank Integration
A: Payouts are the transfer of your collected sales revenue to your linked bank account. Payouts are processed by the 15th of every month for previous month's billing cycle barring any special circumstances like bank holidays or emergencies
A: Minimum Payout Threshold is \$100 after platform fees & taxes deduction. You can see the exact wallet amount in your dashboard in the [payouts section](https://app.dodopayments.com/business/payouts).
A: To link your bank account, navigate to the **Payout Settings** section of your dashboard and click **Add Bank Account**. You'll need to enter your bank details and verify the account.
A: Yes, you can track your payout status from the **Payouts** section of the dashboard.
A: Payout fees refer to the transaction costs for transferring the money we've collected from your customers to your bank account. We constantly make improvements to reduce the payout fee by partnering with local partners in your country. Please refer to the [pricing and fee structure](/miscellaneous/pricing-and-fee-structure#2-payout-fees) for detailed information.
A: Payouts usually take 1–2 business days to reflect in your bank account.
Transfers don't settle on weekends or bank holidays. If your payout was processed on Friday, expect it by Monday or Tuesday.
A: Just enter your Payoneer bank details in the Bank Info Form under your dashboard. There's no separate Payoneer integration yet.
A: Yes. You can receive payouts via Payoneer by:
* Entering your Payoneer account details in the Bank Info Form on the Dodo dashboard
* Ensure your account is approved and supports USD or the currency you're withdrawing
There is no dedicated Payoneer integration — it is treated as a regular bank account.
A: Just wait. Our compliance team typically reviews forms within 1–2 working days. If it's urgent:
* mail to [support@dodopayments.com](mailto:support@dodopayments.com)
A: If your payout day is on a Friday, it is usually processed on the same day. However:
* If there's a bank holiday or weekend, it may be delayed to the next working day (usually Monday).
* The exact timing may depend on your bank's processing rules.
If in doubt, check your dashboard or ask support with your registered email.
A: If you've submitted everything and are awaiting payout compliance:
* Mention your urgency clearly in support chats (e.g., live launch, paid campaigns).
* Include your business ID and email.
* Avoid spamming or tagging multiple team members — this may slow down the queue.
A: If you've entered incorrect bank details:
* Contact support via Intercom or Discord or Mail.
* DM your email ID and request a manual reset of the bank verification section if you reach through Discord.
* Wait for the compliance team to reopen the section for you to re-submit.
Editing bank details directly in the dashboard may not be allowed post-submission.
A: No. If you're registered as a company or legal entity, you must use the entity's bank account.
A: If your country does not issue SWIFT codes, Dodo Payments may accept alternative documentation or generic SWIFT formats.
What to do:
* Contact support and explain the situation.
* Submit any official bank document confirming your account details and the absence of a SWIFT code.
A: No, but you must:
* Explain your situation to support
* Provide bank documents that confirm your account number, holder name, and that no SWIFT is issued
* Sometimes a generic placeholder SWIFT may be used (based on country)
This case will go through manual compliance review. DM your email ID or write to [compliance@dodopayments.com](mailto:compliance@dodopayments.com) for escalation.
## Invoicing & Reports
A: Yes, Dodo Payments automatically generates **invoices** for every successful transaction. These invoices include all necessary details such as tax breakdowns, customer information, and product details.
A: Yes, you can customize the look of your invoices by adding your **company logo and brand name.** You can manage these settings in the Business Profile section of your dashboard.
A: You can view or download invoices from the **Invoices** section of the dashboard. Each payment will have an associated invoice that can be downloaded as a **PDF**.
A: You can generate detailed reports from the **Reports** section of your dashboard. These reports include data on transactions, refunds, and payouts, which you can filter by date and export in CSV format.
A: Yes, Dodo Payments offers **API integration** for seamless integration with your website or platform. You can find API documentation and guides in the **Developer Section** of your dashboard.
## Refunds & Disputes
A: To issue a refund, go to the **Payment Details Page** of the transaction you want to refund and click the **Initiate Refund** button. You can issue a **full** or **partial** refund, provided the payment meets the refund eligibility rules.
A: Once a refund is initiated, the customer will typically receive the refunded amount within **3-5 business days**, depending on the payment method and their bank.
A: If a chargeback is filed against you:
* You may lose both the transaction amount and a dispute fee
* You can submit evidence of product delivery, usage logs, or customer communication
* However, the final decision lies with the customer's issuing bank
Email [support@dodopayments.com](mailto:support@dodopayments.com) with:
* Dispute ID
* Payment ID
* Relevant proof
A: Yes — you can:
* Share documentation (chat logs, usage records, delivery confirmation)
* Explain that the service was fulfilled
* Request a waiver of the dispute fee if it's a first-time issue or if fraud is provable
There's no guarantee the fee will be waived, but ou team reviews each case on merit.
## Webhooks & Integration
A: You're likely confusing the redirect URL with the webhook URL. A redirect URL is where the user is sent after a payment, and it may contain `?subscription_id=...&status=active` in a GET request. This is not your webhook.
To receive structured POST payloads from Dodo Payments:
* Set up a webhook under Dashboard → Developers → Webhooks
* Ensure the URL accepts POST requests and verifies the signature
You can have both a webhook and a redirect URL — they serve different purposes.
A: You likely forgot to pass the correct environment when initializing the DodoPayments client.
**Fix:**
```python theme={null}
client = DodoPayments(
bearer_token="your_api_key",
environment="live_mode" # or "test_mode"
)
```
A: You can do it through the webhook dashboard. Go to Dashboard -> Developers -> Webhooks
A:
* **Webhook URL**: Configured in dashboard. Receives POST payloads about events.
* **Redirect URL**: Sent in the payment API request. Sends user back to your app with query params (`?status=success`).
Use webhooks to automate things like DB updates, and redirects to show thank-you screens.
A: The payment.succeeded webhook event for subscription products does not include a product\_id. Instead, it provides the `subscription_id` associated with the subscription, along with other relevant details.
A: To verify that the webhooks you receive are genuinely from Dodo Payments and not from malicious actors, follow these steps:
1. **Check the Signature**: Each webhook sent by Dodo Payments includes a unique signature in the **`webhook-signature`** header. This signature is generated using your webhook's secret key and the payload of the event.
2. **Retrieve the Secret Key**: Ensure you have the secret key for your webhook, which can be found in your Dodo Payments dashboard under the Webhooks settings.
3. **Recompute the Signature**: Use the same signing algorithm (HMAC-SHA256) as Dodo Payments to compute the signature on your server. Use the payload of the webhook and your webhook secret key.
4. **Compare Signatures**: Compare the recomputed signature with the one provided in the **`webhook-signature`** header. If they match, the webhook is authentic; if not, it should be rejected.
5. **Use HTTPS**: Ensure your webhook endpoint uses HTTPS to secure communication and prevent tampering during data transmission.
By implementing these steps, you can confidently verify the authenticity of webhooks sent by Dodo Payments.
We recommend using packages provided by StandardWebhooks to verify signature and not construct it by yourself.
A: For subscriptions, `subscription.renew` will be triggered whenever the subscription amount is deducted. So yes, this is expected behavior.
A: To test webhooks locally, you can follow these steps:
1. **Use a Tunneling Tool**: Tools like [ngrok](https://ngrok.com/), [LocalTunnel](https://localtunnel.github.io/www/), or [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/) can expose your local server to the internet. This provides a public URL that Dodo Payments can use to send webhook events to your local environment.
2. **Set Up the Webhook Endpoint**: Create a webhook endpoint in your local application to handle incoming webhook events. Ensure it listens for HTTP POST requests.
3. **Configure the Webhook in Dodo Payments**: Go to your Dodo Payments dashboard, navigate to the Webhooks settings, and update the webhook URL to the public URL generated by the tunneling tool.
A: Check the following:
* You did not confuse the webhook URL with redirect URL
* The webhook is configured under Developers > Webhooks
* Your URL accepts POST requests and responds with 2xx
* Signature validation (if enabled) is implemented correctly
If you only see GETs with ?status=success, you are debugging the redirect URL, not the webhook.
A: No. While it's a quick win, URL query params can be tampered with.
If you're using redirect\_url?status=success:
* Double-validate the transaction with an API call
* Or wait for payment.succeeded webhook
Treat redirects as user-facing confirmations, not proof of payment.
## Testing & Local Development
A: Yes — subscriptions can be tested fully in Test Mode.
Just don't use real card numbers.
A:
* On your Dodo dashboard, look for a Test Mode toggle.
* Switch it ON to use test API keys and simulate payments.
You'll see "Test Mode" labels on all screens. Make sure to use the corresponding API key or environment when switching.
A: Yes, but keep in mind:
* No real money will be transferred
* You can simulate webhooks and flows
* Card details should still be test cards
This helps debug flows end-to-end without any financial impact.
A: It means:
* An Indian Card is used to make the payment but the billing country is not set as IN.
* You likely passed an incorrect billing country.
## Support & Communication
A: You can contact our support team by sending an email to [**support@dodopayments.com**](mailto:support@dodopayments.com). We are here to assist you with any issues or questions you may have.
A: If you've messaged in:
* Intercom with no response
* Discord without acknowledgement
Then:
* Escalate via [compliance@dodopayments.com](mailto:compliance@dodopayments.com) or [support@dodopayments.com](mailto:support@dodopayments.com) with subject line Urgent: No Response – \[Your Email]
## Security & Fraud Prevention
A: Dodo Payments uses **real-time fraud detection** to monitor suspicious transactions. We also comply with **PCI-DSS standards** to ensure that all payment information is encrypted and secure.
A: Yes, Dodo Payments uses **data encryption** and **tokenization** to protect sensitive information. We ensure that customer payment data is never stored directly and remains secure during transactions.
# Feature Prioritization Tools
Source: https://docs.dodopayments.com/miscellaneous/gtm-tools/feature-prioritization-tool
The **Feature Prioritization Tool** helps SaaS product teams prioritize feature development using the **RICE (Reach, Impact, Confidence, Effort) framework**. By assigning a RICE score to each feature, this tool ensures that high-impact, low-effort features are prioritized, maximizing development efficiency and business value.
## **Start Prioritizing Features for Maximum Impact**
Use the **Feature Prioritization Tool** to optimize product development and ensure your SaaS team focuses on features that drive engagement, user satisfaction, and revenue growth.
Link: [https://dodopayments.com/tools/product-prioritization-tool](https://dodopayments.com/tools/product-prioritization-tool)
## **Why Feature Prioritization is Essential for SaaS Businesses**
Prioritizing product features ensures that development teams focus on features with the highest user impact while efficiently managing resources. A structured prioritization process helps:
1. Improve customer satisfaction by delivering high-value features first
2. Maximize ROI on development efforts
3. Reduce time-to-market for essential features
4. Align product development with business objectives
By leveraging data-driven prioritization, SaaS companies can build products that drive engagement, retention, and revenue growth.
## **Key Input Metrics**
### **1. Reach Score (1-10)**
Represents the estimated number of users who will benefit from the feature within a specific period. High-reach features impact a large portion of the user base.
### **2. Impact Score (1-10)**
Measures the feature’s value to affected users. Higher impact scores indicate features that significantly enhance the user experience or solve a critical problem.
### **3. Confidence Level (%)**
Reflects the certainty of reach and impact estimates. A higher confidence percentage ensures that decisions are based on reliable data and user insights.
### **4. Effort Score (1-10)**
Represents the estimated resources required to implement the feature, including development time, personnel, and budget. Features requiring lower effort with high impact should be prioritized.
## **How the Feature Prioritization Tool Works**
Using the **RICE framework**, the tool ranks product features based on their potential value vs. effort. It provides:
1. A structured scoring system to evaluate feature importance
2. Automated ranking to ensure optimal resource allocation
3. Data-driven prioritization for faster decision-making
The tool generates a prioritized feature list, ensuring that the highest-impact features with the lowest resource investment are developed first.
## **Key Output Metrics**
### **1. Prioritized Feature List**
A feature ranking list is generated based on the RICE score, displayed in descending order. This list helps SaaS product teams focus on the most impactful features first, ensuring efficient roadmap planning.
## **Frequently Asked Questions (FAQs)**
### **FAQs:**
1. **What is the RICE framework?**
* The RICE framework is a prioritization method used to rank product features based on four factors: Reach (how many users it will affect), Impact (how much value it provides), Confidence (how sure you are of the estimates), and Effort (how much work it requires).
2. **How do I calculate Reach for a feature?**
* Reach is the estimated number of users who will benefit from the feature. For example, if a feature impacts 1,000 users per month, your reach score would reflect this number.
3. **Why is Confidence important in the RICE framework?**
* Confidence helps ensure that you’re making decisions based on reliable estimates. If you’re unsure about the impact or reach of a feature, a lower confidence score will prevent you from over-prioritizing a feature that may not deliver the expected value.
4. **How do I decide which features to prioritize?**
* Features with the highest RICE scores should be prioritized first because they offer the most value relative to the effort required. This ensures your team focuses on tasks that deliver maximum results quickly.
5. **What should I do if two features have the same RICE score?**
* If two features have the same RICE score, consider qualitative factors such as strategic importance, user feedback, or alignment with long-term business goals. You can also reassess confidence levels or adjust the impact estimate to break the tie.
# Freemium Model
Source: https://docs.dodopayments.com/miscellaneous/gtm-tools/freemium-model
The Freemium Model ROI Analysis Tool helps SaaS businesses evaluate the effectiveness and financial viability of a freemium strategy. It provides insights into the costs associated with supporting free users and the revenue generated from conversions to paid plans. This tool enables businesses to assess whether their freemium model is sustainable and profitable.
## **Start Optimizing Your Freemium Strategy Today**
Evaluate your freemium model’s profitability with the Freemium Model ROI Calculator.
Link: [https://dodopayments.com/tools/freemium-model-calculator](https://dodopayments.com/tools/freemium-model-calculator)
Make data-driven decisions to maximize conversions and revenue!
## Importance of ROI Analysis for Freemium Models
Freemium models attract a large user base by offering free access to basic features, with the expectation that a percentage of users will convert to paid plans. However, if conversion rates are too low or operational costs are too high, the free tier can become unsustainable. Understanding the ROI of a freemium strategy is critical for:
* Evaluating the financial impact of supporting free users.
* Optimizing conversion strategies to increase paid users.
* Identifying cost inefficiencies and areas for improvement.
* Enhancing pricing models to maximize revenue potential.
## **Key Metrics Analyzed**
This tool provides insights into the following key factors affecting freemium profitability:
### **1. Free User Base**
Understanding the size of the free user base is essential, as these users form the primary pool for potential paid conversions.
### **2. Operational Costs**
The costs associated with supporting free users, including server infrastructure, customer support, and product maintenance, directly impact the financial sustainability of a freemium model.
### **3. Free-to-Paid Conversion Rate**
The percentage of free users upgrading to paid plans is a critical indicator of monetization success. A low conversion rate may suggest issues with feature differentiation, pricing, or user engagement.
### **4. Paid User Revenue & Costs**
Revenue generated from paid subscribers must offset both acquisition costs and the cost of maintaining free users. Understanding the balance between these factors ensures long-term profitability.
## **How This Tool Helps SaaS Businesses**
By analyzing these metrics, the Freemium Model ROI Analysis Tool enables businesses to:
1. Assess the financial impact of free users and determine if the model is sustainable.
2. Optimize the free-to-paid conversion funnel to improve monetization.
3. Identify opportunities to reduce costs while maintaining product quality.
4. Fine-tune pricing and feature differentiation to increase revenue.
## **Strategic Considerations**
* **ROI (Return on Investment)**: The tool provides an ROI percentage, showing the profitability of your freemium model. A positive ROI indicates that your free users are converting into paying customers at a rate that covers your costs and generates profit.
If the analysis indicates a negative ROI, businesses may need to:
* Refine pricing models by adjusting free and premium feature allocations.
* Enhance product value to improve user retention and encourage upgrades.
* Reduce operational costs associated with maintaining free users.
* Optimize conversion tactics, such as targeted promotions and in-app nudges.
### **FAQs:**
1. **What is a good conversion rate for freemium models?**
* A typical conversion rate for freemium models is between 2% and 5%, but it can vary depending on your product and market. Higher conversion rates indicate that your free users are finding enough value to upgrade to a paid plan.
2. **How do I reduce the costs of supporting free users?**
* You can reduce costs by optimizing server resources, automating customer support for free users, and limiting the features available in the free tier to reduce usage costs.
3. **What are the risks of a freemium model?**
* The biggest risk is that too many users stay on the free plan without converting to paid plans. This can lead to high costs without generating sufficient revenue. Balancing the value of the free tier and making the upgrade path attractive is key.
4. **How can I improve my freemium conversion rate?**
* You can improve conversion by offering clear value in the paid tiers, such as premium features, better support, or advanced analytics. Offering timely nudges to ask the user to upgrade may push the conversion
5. **What should I do if my freemium ROI is negative?**
* If your freemium ROI is negative, it’s important to reevaluate the value you’re offering in the free tier. Consider reducing the free features or increasing the incentives to upgrade to a paid plan to make your strategy more profitable.
# SaaS KPI Measurement
Source: https://docs.dodopayments.com/miscellaneous/gtm-tools/saas-kpi-measurement
The SaaS KPI Measurement Tool enables SaaS businesses to monitor their key performance indicators (KPIs) such as Annual Recurring Revenue (ARR), Churn Rate, and Customer Lifetime Value (CLTV). By consolidating critical financial metrics in a single dashboard, businesses can gain insights into growth trends, customer retention, and long-term profitability.
## **Start Tracking Your SaaS KPIs Today**
Gain valuable business insights with the **SaaS KPI Measurement Tool**.
Link: [https://dodopayments.com/tools/saas-kpi-measurement](https://dodopayments.com/tools/saas-kpi-measurement)
**Optimize revenue, monitor churn, and scale with confidence!**
## **Why KPI Measurement is Essential for SaaS Businesses**
Accurate KPI tracking helps SaaS companies:
1. Understand monthly and annual revenue trends
2. Identify customer retention challenges and reduce churn
3. Forecast long-term financial performance
4. Optimize pricing and revenue models
5. Improve customer lifetime value (CLTV) and ROI
Tracking these KPIs consistently ensures better decision-making and business sustainability.
## **Key Input Metrics**
To generate accurate SaaS KPIs, the tool requires the following inputs:
### 1. Monthly Recurring Revenue (MRR)
The total recurring revenue generated per month from customer subscriptions. This metric is the foundation for calculating ARR and CLTV.
### 2. New Customers Added This Month
The number of new customers acquired during the current month. This input is essential for tracking growth and understanding customer acquisition effectiveness.
### 3. Customers Last Month
The total customer base at the start of the month. This metric is used to assess churn rate and retention trends.
### 4. Current Customer Base This Month
The number of active customers at the end of the month. This figure helps determine if the company is retaining or losing customers.
### 5. Average Customer Retention Period
The average length of time a customer remains subscribed (in months or years). This is crucial for calculating CLTV and evaluating long-term revenue potential.
## **How the SaaS KPI Measurement Tool Works**
The tool performs real-time calculations based on the provided inputs and generates key SaaS metrics:
1. **ARR Calculation** – Estimates Annual Recurring Revenue using MRR.
2. **Churn Rate Calculation** – Determines customer retention trends over time.
3. **CLTV Calculation** – Evaluates customer lifetime value to measure revenue potential per customer.
By consolidating these KPIs, the tool provides a comprehensive performance overview, allowing businesses to identify opportunities for growth and improvement.
## **Frequently Asked Questions (FAQs)**
1. **What is a good churn rate for a SaaS business?**
* A good churn rate for SaaS businesses varies depending on the industry but it typically falls below 10%. Lower churn means better customer retention, which directly impacts revenue stability. If your churn rate is higher, it may indicate issues with product fit or customer satisfaction.
2. **Why is tracking MRR important for SaaS companies?**
* MRR provides a clear, predictable revenue stream that you can count on month over month. It helps with financial forecasting, budgeting, and measuring the success of marketing and sales strategies.
3. **How can I increase my CLTV?**
* You can increase CLTV by improving customer retention, upselling customers to higher pricing tiers, and providing exceptional customer service. Happy customers are likely to stick around longer and spend more.
4. **What’s the difference between MRR and ARR?**
* MRR represents your monthly recurring revenue, while ARR is the annualized version of MRR. ARR provides a longer-term perspective on your recurring revenue, making it easier to assess yearly performance.
5. **Why should I monitor churn rate closely?**
* Churn rate directly affects your bottom line. High churn means that you’re losing customers faster than you’re acquiring new ones, which can lead to negative revenue growth. Monitoring churn helps you identify areas for improvement, like product enhancements or better customer support.
# SaaS Pricing calculator
Source: https://docs.dodopayments.com/miscellaneous/gtm-tools/saas-pricing-calculator
The SaaS Pricing Calculator is designed to help SaaS businesses determine the ideal pricing strategy for their software products. Setting the right price is critical for remaining competitive, covering operational costs, and maximizing profitability. This tool enables businesses to explore different pricing models, including competitor-based, cost-based, and value-based pricing, to develop a pricing structure that aligns with market trends and customer expectations.
## **Optimize Your SaaS Pricing Strategy Today**
Use the **SaaS Pricing Calculator** to develop a pricing model that maximises profitability, competitiveness, and customer acquisition.
Link: [https://dodopayments.com/tools/saas-pricing-calculator](https://dodopayments.com/tools/saas-pricing-calculator)
**Find the perfect balance between cost, value, and market positioning!**
## **Why Pricing Strategy is Crucial for SaaS Businesses**
Pricing directly impacts customer acquisition, revenue growth, and profitability. A well-defined pricing strategy ensures:
1. **Competitive positioning** – Aligns your product pricing with market expectations
2. **Sustainable profitability** – Covers costs while maintaining profit margins
3. **Value-based pricing** – Ensures customers pay according to the benefits they receive
Choosing the right pricing strategy allows SaaS businesses to balance affordability, perceived value, and business growth.
## **Key Input Metrics**
### **1. Competitor Pricing**
Understanding the price points of similar SaaS solutions in the market helps in positioning your product effectively. Competitor pricing acts as a reference, enabling businesses to decide whether to price above, below, or at market rates.
### **2. Product Value Addition (%)**
Represents the relative value your product provides compared to competitors. If your software offers 30% more features or efficiency, pricing can be adjusted accordingly.
### **3. Cost per User**
Includes operational costs such as cloud hosting, development, maintenance, and support services. This metric helps in setting a profitable baseline price to ensure sustainability.
### **4. Estimated User Base**
Defines the projected number of users subscribing to the SaaS product over a set period. This estimation helps determine revenue potential and optimal pricing.
### **5. Target Revenue**
Businesses must set annual or monthly revenue goals to ensure pricing strategies align with profitability objectives. This metric helps determine minimum pricing thresholds.
### **6. Estimated Value for the Customer**
Assesses the financial impact or time savings the product delivers to users. For example, if the software saves businesses \$10,000 annually, pricing should reflect this value-based approach.
## **How the SaaS Pricing Calculator Works**
This tool enables businesses to compare multiple pricing models and identify the most suitable one based on financial goals, competitive landscape, and customer expectations. It supports:
1. **Competitor-Based Pricing** – Align pricing with industry benchmarks
2. **Cost-Based Pricing** – Ensure pricing covers operational costs and profit margins
3. **Value-Based Pricing** – Charge based on customer-perceived value
By leveraging data-driven pricing strategies, SaaS companies can optimize revenue, enhance customer acquisition, and drive business growth.
## **Key Output Metrics**
### **1. Recommended Price Range**
The tool provides a flexible pricing range, allowing businesses to adjust prices based on market trends and customer feedback. Pricing recommendations typically range between 0.8x and 1.2x the suggested price, ensuring adaptability.
### **2. Tiered Pricing Structure**
A well-defined tiered pricing model maximizes revenue potential by catering to different customer segments. The tool recommends three tiers:
**Basic Tier** – Lower-priced plan for budget-conscious users with essential features.
**Pro Tier** – Standard pricing plan offering a balanced set of features.
**Enterprise Tier** – Premium pricing model with advanced functionalities and personalized support.
## **Frequently Asked Questions (FAQs)**
1. **What is the best pricing strategy for SaaS companies?**
* There is no one-size-fits-all pricing strategy for SaaS companies. The best approach depends on your product, market, and business goals. Competitor-based pricing helps you align with market rates, cost-based pricing ensures you cover your expenses, and value-based pricing lets you charge based on the perceived value your product delivers to customers.
2. **How do I decide whether to use competitor-based, cost-based, or value-based pricing?**
* If your market is saturated with similar products, competitor-based pricing can keep you competitive. If you want to ensure profitability, cost-based pricing guarantees your costs are covered. If your product offers significant value to users, value-based pricing allows you to charge based on the results your product delivers, which can lead to higher pricing.
3. **How can I make my SaaS pricing more competitive?**
* To stay competitive, evaluate your competitors regularly, ensure your pricing reflects the value you provide, and consider offering tiered pricing or freemium models. Adjust your pricing based on user feedback and market trends to ensure you remain competitive.
4. **Why should I offer tiered pricing for my SaaS product?**
* Tiered pricing allows you to cater to different customer segments, offering basic features at lower prices and premium features at higher prices. This flexibility maximizes your revenue by appealing to both budget-conscious users and those willing to pay for advanced features.
5. **How often should I reevaluate my SaaS pricing?**
* It’s a good practice to review your pricing strategy at least once a year, or when significant changes occur in your product offerings, market conditions, or customer needs. This ensures your pricing remains competitive and aligned with your business goals.
# SaaS Revenue & Profit Growth
Source: https://docs.dodopayments.com/miscellaneous/gtm-tools/saas-revenue-and-profit-growth
The **SaaS Revenue & Profit Growth Tool** is designed to help SaaS businesses accurately forecast their **financial performance** by analyzing revenue, costs, and growth rates. This tool enables businesses to make data-driven decisions about scaling, hiring, and long-term sustainability. By predicting future revenue and profit trends, SaaS companies can optimize growth strategies and maximize profitability.
Start Forecasting Your Business Growth with **SaaS Revenue and Profit Growth Tool**
**Link:** [https://dodopayments.com/tools/saas-revenue-profit-growth-tool](https://dodopayments.com/tools/saas-revenue-profit-growth-tool)
**Optimize revenue, increase profitability, and scale smarter!**
## **Why Revenue & Profit Growth Projections Matter for SaaS Businesses**
Accurate revenue and profit projections are crucial for SaaS companies to:
1. Plan long-term growth strategies
2. Set realistic revenue and expansion goals
3. Make informed decisions about scaling, hiring, and product development
4. Identify potential financial risks and opportunities
5. Ensure sustainable profitability in an evolving market
By leveraging financial forecasting, SaaS businesses can gain a competitive advantage and minimize unexpected financial hurdles.
## **Key Input Metrics**
To generate precise revenue and profit projections, the tool requires the following key metrics:
### **1. Estimated Annual Revenue**
This is the total revenue generated annually from customer subscriptions. Entering an accurate figure allows the tool to forecast revenue growth trends over multiple years.
### **2. Estimated Annual Costs**
All operational expenses, including software development, hosting, salaries, marketing, and customer support, should be included. Tracking these costs ensures an accurate profitability forecast.
### **3. Revenue Growth Rate**
This is the annual percentage increase in revenue based on customer acquisition, expansion, and pricing adjustments. For example, if you expect a 10% revenue increase yearly, enter this value to project long-term growth.
### **4. Cost Growth Rate**
Expenses grow as the business scales. The cost growth rate estimates how annual costs increase over time, accounting for infrastructure, hiring, and marketing investments.
### **5. Forecast Duration (Number of Years)**
Select the time frame for your financial projections (e.g., 3, 5, or 10 years). Longer projections provide a bigger picture of financial health but should be regularly reviewed for accuracy.
## **How the SaaS Revenue & Profit Growth Calculator Works**
This tool uses real-time calculations based on your revenue, costs, and growth rates to generate financial projections. It provides:
1. **Revenue Growth Forecasts** – Year-over-year revenue increase based on your expected growth rate.
2. **Profitability Analysis** – Calculates net profit by subtracting projected costs from revenue.
3. **Break-even Insights** – Identifies when your business becomes sustainably profitable.
4. **Data-driven Decision Support** – Helps refine pricing, reduce churn, and optimize cost management.
## **Key Output Metrics**
After entering your data, the tool provides detailed insights into your SaaS business’s financial future:
### **1. Revenue Projection**
A year-over-year revenue forecast, estimating how your business will grow based on your revenue growth rate. This projection helps track expansion progress and scalability.
### **2. Profit Projection**
A detailed profit analysis displaying how much revenue remains after accounting for growing operational costs. Monitoring profit growth ensures long-term business sustainability.
## **Frequently Asked Questions (FAQs)**
### **1. Why are revenue and profit projections important for SaaS businesses?**
Revenue and profit forecasts allow SaaS businesses to make informed financial decisions, set realistic goals, and plan growth strategies. Predicting financial trends helps **optimize operations, scale efficiently, and mitigate risks**.
### **2. How accurate are long-term SaaS revenue projections?**
Projections are based on current data and estimated growth rates, but factors like customer churn, market shifts, and operational costs can impact actual outcomes. Regularly updating forecasts ensures greater accuracy.
### **3. What is the difference between revenue growth and profit growth?**
* **Revenue Growth**: Measures total income increase from SaaS subscriptions and customer expansion.
* **Profit Growth**: Accounts for both revenue and costs, reflecting the actual net earnings after expenses. A company may experience revenue growth but lower profits if costs rise significantly.
### **4. How can I increase my SaaS revenue growth rate?**
Boost revenue by:
1. Enhancing customer acquisition strategies
2. Reducing churn with better retention efforts
3. Optimizing pricing models and offering upsells
4. Expanding into new markets or launching new features
### **5. How does cost growth affect SaaS profitability?**
As businesses scale, operational costs (e.g., support, hosting, infrastructure, and marketing) also increase. If costs outpace revenue growth, profitability declines. Monitoring expenses ensures financial stability.
# Help and Support
Source: https://docs.dodopayments.com/miscellaneous/help-support
We understand that navigating a new platform or resolving issues can sometimes require additional support. To ensure a seamless experience, we offer multiple support channels to address all your concerns.
## **Dodo Payments Centralized Support System**
The Dodo Payments Centralized Support System provides a unified support experience, offering multiple channels to assist merchants.
Merchants can access the support system through the "Get Support" icon on the Dodo Payments Dashboard, leading to a dedicated support landing page with four key options:
## 1. AI-Powered Chat Assistance
The Chat with AI feature provides instant, automated support using an AI-powered chatbot. It is designed to assist users by:
* Answering technical and business queries
* Providing guidance on API integration and setup
* Offering navigation support for product documentation
This feature ensures quick resolution of common queries, reducing the need for direct human support.
📌 Best for: Instant self-serve assistance and general troubleshooting.
## 2. Community Support via Discord
The Dodo Payments Discord Community enables merchants to engage with other users and the Dodo Payments team. Through this platform, users can:
* Discuss best practices and share experiences
* Seek peer-to-peer assistance from other merchants
* Engage with the Dodo Payments support team in an interactive environment
This feature fosters a collaborative community-driven support experience.
📌 Best for: Peer support, longer queries and networking with other merchants.
## 3. Dedicated Chat Support
The Chat Support feature provides a direct line to the Dodo Payments support team for more complex or account-specific inquiries. Key aspects include:
* Support is managed via Intercom, where merchants can submit detailed queries.
* Response time is within 24-48 business hours.
* Unresolved tickets will automatically close if there is no user response for 72 hours.
This feature ensures that merchants receive personalized assistance for critical issues requiring human intervention.
📌 Best for: Account-related concerns, technical escalations, and personalized troubleshooting.
## **4. Self-Service Documentation**
The Dodo Payments Documentation Portal serves as a comprehensive resource for merchants looking for detailed product and integration information. The portal includes:
* Detailed feature explanations
* Step-by-step implementation guides
* API documentation and integration manuals
* Frequently Asked Questions (FAQs)
This feature provides structured, self-paced learning for merchants to independently navigate the platform.
📌 Best for: In-depth learning, implementation guidance, and technical documentation.
## **Support Options Overview**
| **Support Channel** | **Purpose** | **Response Time** |
| --------------------- | ------------------------------------------- | ----------------- |
| **AI-Powered Chat** | Automated responses, documentation guidance | Instant |
| **Discord Community** | Peer support, community discussions | Community-driven |
| **Chat Support** | Personalized assistance for complex issues | 24-48 hours |
| **Self-Service Docs** | Product guides, API reference, tutorials | Self-paced |
Merchants can choose the most suitable support channel based on the complexity of their query and the required response time. For further assistance, visit the Dodo Payments Support Center via the dashboard.
# Countries Eligible for Payment Acceptance
Source: https://docs.dodopayments.com/miscellaneous/list-of-countries-we-accept-payments-from
The list below includes the countries from which we accept payments for our SaaS and digital products.
| # | ISO Code | Country Name |
| --- | -------- | -------------------------------------------- |
| 1 | AD | Andorra |
| 2 | AE | United Arab Emirates |
| 3 | AG | Antigua and Barbuda |
| 4 | AI | Anguilla |
| 5 | AL | Albania |
| 6 | AM | Armenia |
| 7 | AO | Angola |
| 8 | AR | Argentina |
| 9 | AS | American Samoa |
| 10 | AT | Austria |
| 11 | AU | Australia |
| 12 | AW | Aruba |
| 13 | AX | Åland Islands |
| 14 | AZ | Azerbaijan |
| 15 | BA | Bosnia and Herzegovina |
| 16 | BB | Barbados |
| 17 | BD | Bangladesh |
| 18 | BE | Belgium |
| 19 | BF | Burkina Faso |
| 20 | BG | Bulgaria |
| 21 | BH | Bahrain |
| 22 | BI | Burundi |
| 23 | BJ | Benin |
| 24 | BL | Saint Barthélemy |
| 25 | BM | Bermuda |
| 26 | BN | Brunei Darussalam |
| 27 | BO | Bolivia |
| 28 | BQ | Caribbean Netherlands |
| 29 | BR | Brazil |
| 30 | BS | Bahamas |
| 31 | BT | Bhutan |
| 32 | BV | Bouvet Island |
| 33 | BW | Botswana |
| 34 | BZ | Belize |
| 35 | CA | Canada |
| 36 | CC | Cocos (Keeling) Islands |
| 37 | CG | Republic of the Congo |
| 38 | CH | Switzerland |
| 39 | CI | Côte d'Ivoire |
| 40 | CK | Cook Islands |
| 41 | CL | Chile |
| 42 | CM | Cameroon |
| 43 | CN | China |
| 44 | CO | Colombia |
| 45 | CR | Costa Rica |
| 46 | CV | Cape Verde |
| 47 | CW | Curaçao |
| 48 | CX | Christmas Island |
| 49 | CY | Cyprus |
| 50 | CZ | Czech Republic |
| 51 | DE | Germany |
| 52 | DJ | Djibouti |
| 53 | DK | Denmark |
| 54 | DM | Dominica |
| 55 | DO | Dominican Republic |
| 56 | DZ | Algeria |
| 57 | EC | Ecuador |
| 58 | EE | Estonia |
| 59 | EG | Egypt |
| 60 | EH | Western Sahara |
| 61 | ER | Eritrea |
| 62 | ES | Spain |
| 63 | ET | Ethiopia |
| 64 | FI | Finland |
| 65 | FJ | Fiji |
| 66 | FK | Falkland Islands |
| 67 | FM | Micronesia |
| 68 | FO | Faroe Islands |
| 69 | FR | France |
| 70 | GA | Gabon |
| 71 | GB | United Kingdom |
| 72 | GD | Grenada |
| 73 | GE | Georgia |
| 74 | GF | French Guiana |
| 75 | GG | Guernsey |
| 76 | GH | Ghana |
| 77 | GI | Gibraltar |
| 78 | GL | Greenland |
| 79 | GM | Gambia |
| 80 | GN | Guinea |
| 81 | GP | Guadeloupe |
| 82 | GQ | Equatorial Guinea |
| 83 | GR | Greece |
| 84 | GS | South Georgia and the South Sandwich Islands |
| 85 | GT | Guatemala |
| 86 | GU | Guam |
| 87 | GW | Guinea-Bissau |
| 88 | GY | Guyana |
| 89 | HK | Hong Kong |
| 90 | HM | Heard Island and McDonald Islands |
| 91 | HN | Honduras |
| 92 | HR | Croatia |
| 93 | HU | Hungary |
| 94 | ID | Indonesia |
| 95 | IE | Ireland |
| 96 | IL | Israel |
| 97 | IM | Isle of Man |
| 98 | IN | India |
| 99 | IO | British Indian Ocean Territory |
| 100 | IQ | Iraq |
| 101 | IS | Iceland |
| 102 | IT | Italy |
| 103 | JE | Jersey |
| 104 | JM | Jamaica |
| 105 | JO | Jordan |
| 106 | JP | Japan |
| 107 | KE | Kenya |
| 108 | KG | Kyrgyzstan |
| 109 | KH | Cambodia |
| 110 | KI | Kiribati |
| 111 | KM | Comoros |
| 112 | KN | Saint Kitts and Nevis |
| 113 | KR | South Korea |
| 114 | KW | Kuwait |
| 115 | KY | Cayman Islands |
| 116 | KZ | Kazakhstan |
| 117 | LA | Laos |
| 118 | LB | Lebanon |
| 119 | LC | Saint Lucia |
| 120 | LI | Liechtenstein |
| 121 | LK | Sri Lanka |
| 122 | LR | Liberia |
| 123 | LS | Lesotho |
| 124 | LT | Lithuania |
| 125 | LU | Luxembourg |
| 126 | LV | Latvia |
| 127 | MA | Morocco |
| 128 | MC | Monaco |
| 129 | ME | Montenegro |
| 130 | MF | Saint Martin |
| 131 | MG | Madagascar |
| 132 | MH | Marshall Islands |
| 133 | MK | North Macedonia |
| 134 | MP | Northern Mariana Islands |
| 135 | MQ | Martinique |
| 136 | MR | Mauritania |
| 137 | MS | Montserrat |
| 138 | MT | Malta |
| 139 | MU | Mauritius |
| 140 | MV | Maldives |
| 141 | MW | Malawi |
| 142 | MX | Mexico |
| 143 | MY | Malaysia |
| 144 | MZ | Mozambique |
| 145 | MN | Mongolia |
| 146 | MO | Macau |
| 147 | NA | Namibia |
| 148 | NC | New Caledonia |
| 149 | NE | Niger |
| 150 | NF | Norfolk Island |
| 151 | NG | Nigeria |
| 152 | NI | Nicaragua |
| 153 | NL | Netherlands |
| 154 | NO | Norway |
| 155 | NP | Nepal |
| 156 | NR | Nauru |
| 157 | NU | Niue |
| 158 | NZ | New Zealand |
| 159 | OM | Oman |
| 160 | PA | Panama |
| 161 | PE | Peru |
| 162 | PF | French Polynesia |
| 163 | PG | Papua New Guinea |
| 164 | PH | Philippines |
| 165 | PK | Pakistan |
| 166 | PL | Poland |
| 167 | PM | Saint Pierre and Miquelon |
| 168 | PN | Pitcairn Islands |
| 169 | PR | Puerto Rico |
| 170 | PT | Portugal |
| 171 | PW | Palau |
| 172 | PY | Paraguay |
| 173 | QA | Qatar |
| 174 | RE | Réunion |
| 175 | RO | Romania |
| 176 | RS | Serbia |
| 177 | RW | Rwanda |
| 178 | SA | Saudi Arabia |
| 179 | SB | Solomon Islands |
| 180 | SC | Seychelles |
| 181 | SE | Sweden |
| 182 | SG | Singapore |
| 183 | SH | Saint Helena |
| 184 | SI | Slovenia |
| 185 | SJ | Svalbard and Jan Mayen |
| 186 | SK | Slovakia |
| 187 | SL | Sierra Leone |
| 188 | SM | San Marino |
| 189 | SN | Senegal |
| 190 | SR | Suriname |
| 191 | ST | São Tomé and Príncipe |
| 192 | SV | El Salvador |
| 193 | SX | Sint Maarten |
| 194 | SZ | Eswatini |
| 195 | TC | Turks and Caicos Islands |
| 196 | TD | Chad |
| 197 | TF | French Southern Territories |
| 198 | TG | Togo |
| 199 | TH | Thailand |
| 200 | TJ | Tajikistan |
| 201 | TK | Tokelau |
| 202 | TL | Timor-Leste |
| 203 | TM | Turkmenistan |
| 204 | TN | Tunisia |
| 205 | TO | Tonga |
| 206 | TR | Turkey |
| 207 | TT | Trinidad and Tobago |
| 208 | TV | Tuvalu |
| 209 | TW | Taiwan |
| 210 | TZ | Tanzania |
| 211 | UG | Uganda |
| 212 | UM | United States Minor Outlying Islands |
| 213 | US | United States |
| 214 | UY | Uruguay |
| 215 | UZ | Uzbekistan |
| 216 | VA | Vatican City |
| 217 | VC | Saint Vincent and the Grenadines |
| 218 | VG | British Virgin Islands |
| 219 | VI | U.S. Virgin Islands |
| 220 | VN | Vietnam |
| 221 | VU | Vanuatu |
| 222 | WF | Wallis and Futuna |
| 223 | WS | Samoa |
| 224 | YT | Mayotte |
| 225 | ZA | South Africa |
| 226 | ZM | Zambia |
You can only accept payments from the 226 countries listed above. We regularly update this list as we expand support to more countries.
# Merchant Acceptance Policy
Source: https://docs.dodopayments.com/miscellaneous/merchant-acceptance
This policy will help you understand the types of businesses we support, the types we restrict or prohibit, and how we enforce our standards.
## **Introduction**
Dodo Payments operates as a Merchant of Record (MoR), providing an all-in-one platform for digital businesses for merchants by acting as their official reseller. As MoR, Dodo Payments assumes responsibility for payment processing, tax compliance, and chargeback handling. To protect the integrity of our platform and our global partnerships, we maintain strict standards for the products we resell on our platform.
This policy outlines the kinds of businesses we support, the types we restrict or prohibit, and how we enforce our standards. It is intended to help builders understand how to align with our platform requirements from the start.
## **Businesses We Love**
We’re excited to support legit builders creating digital-first products that are valuable, trustworthy, and easy to deliver. In general, we welcome:
* **SaaS & AI products** – SaaS products, productivity software, AI tools and more.
* **Digital goods** – eBooks, PDFs, design assets, audio, stock media, etc.
* **Templates, Plugins & Apps** – Templates such Notion,Framer, Plugins such as Chrome Store, Wordpress, Apps such as Slack, Discord etc (that do not violate our policies).
* **Courses & learning material** – Digital education courses, self-paced courses, or content delivered via links, embeds, or gated access.
* **Indie creators** – Building cool, niche products that offer real value, and would make you proud to post on your LinkedIn/Twitter.
###
### **What We Look For**
If your product is digital, delivered easily, and meets these qualities, we'd love to work with you:
* **Legally Compliant:** Your product complies with relevant laws (like consumer protection and data privacy) in the regions you operate.
* **Honest and Helpful:** We appreciate products that provide real value to users - things that are helpful, not deceptive or exploitative.
* **Community-friendly:** Products should adhere to our policies and ensure a smooth experience for everyone.
## **Businesses We Can’t Support**
*This is not an exhaustive list. Dodo Payments reserves the right to update or enforce this policy at any time. We may place your account under review or suspend it immediately if we determine your business model is deceptive, harmful, high-risk, generates excessive refunds or chargebacks, or has been flagged by our payment partners.*
As your MoR, we’re legally and reputationally responsible for what’s being sold through our platform. That means we must stay clear of anything that’s:
1. **Human services packaged digitally** – Selling design, development, coaching, or marketing services as "digital" isn’t allowed unless it’s largely productized and automated.
2. **Physical goods or in-person services** – We only support digital delivery. No T-shirts, mugs, books, supplements, coaching calls, or “done-for-you” consulting. This includes merchandise packaged with digital bundles.
3. **Illegal or age-restricted** – Including but not limited to drugs, alcohol, tobacco, vapes, or content that violates local laws in any region you're targeting.
4. **Low-quality or exploitative digital products** – For example, AI-generated PDFs with little value, thin templates marked up heavily, or clearly unfinished websites sold at premium prices which may lead to customer dissatisfaction.
5. **Restricted by payment and compliance partners** – Some business types may be subject to restrictions under the policies of our payment and compliance partners. If your business falls within these categories, we may be unable to support processing due to external requirements or network guidelines.
6. **Adult content or services** – This includes explicit or suggestive content whether real or AI-generated (e.g., OnlyFans-style platforms, NSFW chatbots, webcam streams, erotic games, sex toys).
7. **Intimacy services** – Especially those with sexually charged positioning, escort-style features, or AI-generated relationship companions.
8. **Financial products & advice** – We can’t support unlicensed financial tools, investment strategies, wealth-building courses, tax calculators, or anything involving stored value, lending, or managing funds.
9. **Legal, accounting, and professional services** – We only support licensed professionals operating with valid credentials and authorization in their jurisdiction.
10. **Crypto-related offerings** – Wallets, NFTs, DeFi, crypto exchanges, or token launches aren’t permitted.
11. **“Miracle” or Misleading Claims** – No products making exaggerated or unverifiable claims – e.g., “cures cancer,” “reverses aging,” “burns fat while you sleep,” “make money while you sleep.”
12. **Tax Evasion, Shell Entities, or Regulatory Arbitrage** – We can’t support products, services, or setups designed to evade taxes, mask ownership, or bypass financial or legal regulations—this includes using fake business entities, misleading jurisdictions, or “gray market” setups.
13. **Gambling & games of chance** – Including casinos, lotteries, sports betting, sweepstakes, fantasy sports for cash prizes.
14. **Medical, wellness, and pharmaceutical products** – Including diagnostics, weight-loss programs, supplements, or biohacking kits without the required licenses.
15. **Cheating & manipulation tools** – That means hacks, mods, bots, engagement manipulation, fake reviews, or tools for bypassing gameplay or API limits.
16. **Piracy or IP violations** – Selling things you didn’t create or license (e.g., pirated books, software keys, copy-paste code packs, unauthorized media downloads).
17. **Mass outreach or scraping tools** – No lead scraping, cold email automation, or tools that spam the internet. Also applies to mass-generated AI content marketed as personalized.
18. **Proxy, cloaking, or anti-terms tools** – Anything that helps users bypass API rate limits, platform rules, or geolocation restrictions.
19. **IPTV and streaming access services** – This includes IPTV subscriptions, reseller panels, or tools that provide access to third-party streaming content.
20. **Donations without clear value** – If you’re collecting money without delivering something in return (unless as a pre-approved as a sponsorship), we likely can’t process it.
21. **Marketplaces or resale models** – If you're selling on behalf of others, operating a multi-vendor platform, or taking funds and forwarding them elsewhere, that’s not supported under our MoR structure.
22. **Travel services** – This includes commercial airlines, cruises, charter or private flights, and timeshare offerings.
23. **Political, social, or cause-based fundraising** – We don’t support campaign donations, activism platforms, or politically sensitive products.
24. **Software resale or license flipping** – Including selling software licenses at a discount, or reselling AI APIs, datasets, or tools without clear rights.
25. **Privacy Violations & Surveillance Tech** – We don’t allow products that violate user privacy or enable unauthorized surveillance – this includes stalkerware, keyloggers, location tracking without consent, facial recognition tech, or tools that scrape or collect personal data without a clear legal basis.
26. **Weapons, Self-Defense & Violence-Oriented Content** – We don’t allow content promoting weapons, combat training, DIY explosives, self-defense tools, or any content that glorifies violence or incites harm—digital or not.
Even if your business isn’t listed above, we may still decline to work with you if we believe it poses legal, financial, or reputational risk. This includes high refunds / chargebacks, low-effort, or exploitative offerings. We assess merchants holistically and reserve the right to refuse or revoke access at our discretion.
### **When in Doubt…**
If your product is pushing boundaries or you’re unsure whether it qualifies, we strongly recommend reaching out to us. We’d love to explore ways to help you stay compliant without compromising on your vision.
And remember: just because something is technically digital doesn’t mean it’s automatically okay. If it feels shady, scammy, exploitative, or like something you wouldn’t explain confidently to your mother— we probably can’t support it.
*Some business types may fall into a restricted zone — not outright prohibited, but not immediately approved either. These cases will require enhanced due diligence, and we’ll take a closer look before making a final decision. This is done on a case to case basis.*
## **Businesses We Might Support**
To meet our compliance obligations, some business types require additional review. If your business falls into one of the categories below, we may ask for extra details—such as licenses or information about how you operate.
While we aim to support a wide range of businesses, approval isn’t guaranteed. If approved, access may be limited or adjusted over time in line with the Terms & Conditions. Some types of businesses are not outright prohibited but do raise higher compliance or reputational risks. If you operate in one of these spaces, we may ask for extra details (like disclaimers, demo access, or policy links) before approving your account.
**Categories That Often Require Review:**
1. **AI tools (text, image, video, voice)** – No impersonation, scraping, or deepfakes
2. **VPNs / infrastructure tools** – Must prohibit illegal use and have takedown flows
3. **Marketing / scraping / outreach tools** – No spam, scraping, or fake engagement
4. **Resume, hiring, or exam tools** – No impersonation or cheating functionality
5. **Browser plugins / automation** – Must comply with our policy
6. **Sub-SaaS or white-label tools** – All downstream users must be known and vetted
7. **Spiritual & Astrology services** – Entertainment only; no claims or predictions
8. **Intimacy platforms or chat tools** – Must be safe-for-work and clearly moderated
9. **Audio / music / chatbot generators** – No voice cloning, NSFW, or fake claims
10. **Fake storefront / funnel templates** – Must be transparent and brand-safe
11. **PDF / AI content generators** – Must be original and clearly valuable
12. **Tipping / creator monetization** – Platforms or tools that facilitate tipping or monetization for creators.
**Unsure if your business qualifies?**\
*If your product operates near the edge of these categories, we encourage you to reach out before applying. We may still be able to support you — especially if you’re transparent about your model, actively mitigate risks, and are willing to work within platform guidelines. You can reach out to [compliance@dodopayments.com](mailto:compliance@dodopayments.com) to check if you qualify.*
## Accepted Payout Countries
Below is a list of merchant countries eligible for payout from Dodo Payments:
| # | ISO | Country |
| --- | --- | -------------------------------- |
| 1 | AL | Albania |
| 2 | DZ | Algeria |
| 3 | AD | Andorra |
| 4 | AO | Angola |
| 5 | AI | Anguilla |
| 6 | AG | Antigua and Barbuda |
| 7 | AR | Argentina |
| 8 | AW | Aruba |
| 9 | AU | Australia |
| 10 | AT | Austria |
| 11 | BS | Bahamas |
| 12 | BH | Bahrain |
| 13 | BD | Bangladesh |
| 14 | BB | Barbados |
| 15 | BE | Belgium |
| 16 | BZ | Belize |
| 17 | BJ | Benin |
| 18 | BO | Bolivia |
| 19 | BQ | Bonaire |
| 20 | BA | Bosnia and Herzegovina |
| 21 | BW | Botswana |
| 22 | BR | Brazil |
| 23 | VG | British Virgin Islands |
| 24 | BN | Brunei |
| 25 | BG | Bulgaria |
| 26 | BF | Burkina Faso |
| 27 | BI | Burundi |
| 28 | KH | Cambodia |
| 29 | CM | Cameroon |
| 30 | CA | Canada |
| 31 | CV | Cape Verde |
| 32 | KY | Cayman Islands |
| 33 | CF | Central African Republic |
| 34 | TD | Chad |
| 35 | CL | Chile |
| 36 | CN | China |
| 37 | CO | Colombia |
| 38 | KM | Comoros |
| 39 | CK | Cook Islands |
| 40 | CR | Costa Rica |
| 41 | HR | Croatia |
| 42 | CW | Curaçao |
| 43 | CY | Cyprus |
| 44 | CZ | Czech Republic |
| 45 | DK | Denmark |
| 46 | DJ | Djibouti |
| 47 | DM | Dominica |
| 48 | DO | Dominican Republic |
| 49 | EC | Ecuador |
| 50 | EG | Egypt |
| 51 | SV | El Salvador |
| 52 | GQ | Equatorial Guinea |
| 53 | ER | Eritrea |
| 54 | EE | Estonia |
| 55 | SZ | Eswatini |
| 56 | FJ | Fiji |
| 57 | FI | Finland |
| 58 | FR | France |
| 59 | GA | Gabon |
| 60 | GM | Gambia |
| 61 | DE | Germany |
| 62 | GH | Ghana |
| 63 | GR | Greece |
| 64 | GD | Grenada |
| 65 | GP | Guadeloupe |
| 66 | GT | Guatemala |
| 67 | GN | Guinea |
| 68 | GW | Guinea-Bissau |
| 69 | GY | Guyana |
| 70 | VA | Holy See (Vatican City) |
| 71 | HN | Honduras |
| 72 | HK | Hong Kong SAR |
| 73 | HU | Hungary |
| 74 | IS | Iceland |
| 75 | IN | India |
| 76 | ID | Indonesia |
| 77 | IE | Ireland |
| 78 | IL | Israel |
| 79 | IT | Italy |
| 80 | CI | Ivory Coast (Côte d'Ivoire) |
| 81 | JM | Jamaica |
| 82 | JP | Japan |
| 83 | JO | Jordan |
| 84 | KE | Kenya |
| 85 | KI | Kiribati |
| 86 | XK | Kosovo |
| 87 | KW | Kuwait |
| 88 | LA | Laos |
| 89 | LV | Latvia |
| 90 | LB | Lebanon |
| 91 | LS | Lesotho |
| 92 | LR | Liberia |
| 93 | LI | Liechtenstein |
| 94 | LT | Lithuania |
| 95 | LU | Luxembourg |
| 96 | MG | Madagascar |
| 97 | MW | Malawi |
| 98 | MY | Malaysia |
| 99 | ML | Mali |
| 100 | MT | Malta |
| 101 | MH | Marshall Islands |
| 102 | MQ | Martinique |
| 103 | MR | Mauritania |
| 104 | MU | Mauritius |
| 105 | MX | Mexico |
| 106 | FM | Micronesia |
| 107 | MD | Moldova |
| 108 | MC | Monaco |
| 109 | MN | Mongolia |
| 110 | ME | Montenegro |
| 111 | MS | Montserrat |
| 112 | MA | Morocco |
| 113 | MZ | Mozambique |
| 114 | MM | Myanmar |
| 115 | NA | Namibia |
| 116 | NR | Nauru |
| 117 | NP | Nepal |
| 118 | NL | Netherlands |
| 119 | NZ | New Zealand |
| 120 | NE | Niger |
| 121 | NG | Nigeria |
| 122 | NU | Niue |
| 123 | MK | North Macedonia |
| 124 | NO | Norway |
| 125 | OM | Oman |
| 126 | PW | Palau |
| 127 | PA | Panama |
| 128 | PG | Papua New Guinea |
| 129 | PY | Paraguay |
| 130 | PE | Peru |
| 131 | PH | Philippines |
| 132 | PL | Poland |
| 133 | PT | Portugal |
| 134 | QA | Qatar |
| 135 | CG | Republic of the Congo |
| 136 | RO | Romania |
| 137 | RW | Rwanda |
| 138 | BL | Saint Barthélemy |
| 139 | KN | Saint Kitts and Nevis |
| 140 | LC | Saint Lucia |
| 141 | MF | Saint Martin |
| 142 | PM | Saint Pierre and Miquelon |
| 143 | VC | Saint Vincent and the Grenadines |
| 144 | WS | Samoa |
| 145 | SM | San Marino |
| 146 | ST | São Tomé and Príncipe |
| 147 | SA | Saudi Arabia |
| 148 | SN | Senegal |
| 149 | RS | Serbia |
| 150 | SC | Seychelles |
| 151 | SL | Sierra Leone |
| 152 | SG | Singapore |
| 153 | SX | Sint Maarten |
| 154 | SK | Slovakia |
| 155 | SI | Slovenia |
| 156 | SB | Solomon Islands |
| 157 | ZA | South Africa |
| 158 | KR | South Korea |
| 159 | ES | Spain |
| 160 | LK | Sri Lanka |
| 161 | SR | Suriname |
| 162 | SE | Sweden |
| 163 | CH | Switzerland |
| 164 | TW | Taiwan |
| 165 | TZ | Tanzania |
| 166 | TH | Thailand |
| 167 | TL | Timor-Leste |
| 168 | TG | Togo |
| 169 | TO | Tonga |
| 170 | TT | Trinidad and Tobago |
| 171 | TR | Turkey |
| 172 | TC | Turks and Caicos Islands |
| 173 | TV | Tuvalu |
| 174 | UG | Uganda |
| 175 | UA | Ukraine |
| 176 | AE | United Arab Emirates |
| 177 | GB | United Kingdom |
| 178 | US | United States |
| 179 | UY | Uruguay |
| 180 | VI | US Virgin Islands |
| 181 | VU | Vanuatu |
| 182 | VN | Vietnam |
| 183 | ZM | Zambia |
| 184 | ZW | Zimbabwe |
## **Prohibited Countries**
Dodo Payments only supports merchants selling digital goods and services that align with our MoR responsibilities and card network requirements.We only work with merchants from jurisdictions not subject to active sanctions or high-risk flags issued by FATF, OFAC, EU, or UN bodies, etc.
We currently do not support merchants from the following geographies:
1. Belarus
2. Cuba
3. Haiti
4. Iran
5. Iraq
6. North Cyprus
7. North Korea
8. Pakistan
9. Russia
10. South Sudan
11. Sudan
12. Syria
13. Venezuela
14. Yemen
*Note: This list is regularly updated to reflect FATF, OFAC, UN, and EU AML/CFT directives. Our prohibited countries list applies to both the **location of the merchant** (where the business is registered or operates from) and the **jurisdictions involved in the transaction** (such as where the merchant, seller, beneficial owner, or platform users are based). All merchants are responsible for ensuring ongoing compliance.*
## **Enforcement**
We take violations of our merchant policy seriously. If we determine that a merchant has onboarded in breach of this policy or our terms — including by misrepresenting their business, attempting to bypass restrictions, or engaging in prohibited activities — Dodo Payments may take appropriate action, including:
### **Enforcement Actions May Include:**
* Permanent account suspension
* Immediate halt of transactions and platform access
* Withholding of payouts or reversal of settled funds
* Fines or penalties in proportion to losses, reputational harm, or downstream risk (up to USD 425,000)
* Reporting to card networks, financial partners, or regulators where required
These measures may be taken without prior notice in cases of material breach or imminent platform risk.
**Examples of Policy Breach**
The following may trigger investigation or enforcement:
* Misclassifying your business or products to bypass category restrictions
* Using proxy merchants or sub-accounts to avoid enforcement
* Selling or promoting products that are deceptive, harmful, or violate our prohibited content rules
* Failing to disclose essential onboarding information (e.g., website mismatch, incorrect entity type)
### **Appeals & Resolution**
If you believe enforcement was applied in error, you may submit a request for review. In some cases, we may allow corrective action or resubmission for review. Voluntary disclosure of potential issues may be considered favorably. You can reach out to **[compliance@dodopayments.com](mailto:compliance@dodopayments.com)** to initiate the review process.
# Review & Monitoring Policy
Source: https://docs.dodopayments.com/miscellaneous/review-monitoring-policy
How Dodo Payments monitors merchant activity to protect customers, partners, and the payment ecosystem.
## Introduction
At Dodo Payments, we act as a Merchant of Record (MoR), managing payment processing, invoicing, taxes, refunds, and disputes on behalf of the businesses that use our platform. To safeguard customers, businesses, and partners, we operate a structured monitoring program. This program ensures that accounts on our platform remain compliant with card network rules, applicable laws, and our \[Merchant Acceptance Policy].
## Purpose
Our monitoring framework is designed to protect both businesses and customers. By identifying and addressing potential risks early, we help ensure a trusted and reliable payment environment.
## Monitoring Approach
We combine automated checks with human review to maintain oversight of business activity. Reviews focus on:
1. Compliance with our [Merchant Acceptance Policy](/miscellaneous/merchant-acceptance)
2. Consistency between what is disclosed at onboarding and what is offered live
3. Compliance with card network rules, sanctions, and applicable laws
4. Customer experience signals such as refunds and disputes
5. Use of approved brands, domains, and product lines
## When Reviews Occur
Reviews take place at key points in the lifecycle:
1. **Activation & First Transaction** – to confirm that products and checkout experiences match what was disclosed.
2. **Before First Payout** – to complete compliance checks and validate fulfillment.
3. **Ongoing Triggers** – when significant changes occur, such as new domains, product shifts, unusual transaction patterns, or rising disputes / refunds.
4. **Periodic Reviews** – regular monitoring to help ensure continued compliance.
## Outcomes of Reviews
Following a review, possible outcomes include:
1. **Continued support** – your account remains active on the platform.
2. **Remediation** – we may ask you to correct issues or provide clarifications.
3. **Deboarding** – in cases of prohibited activity, policy violations, or non-response, the account may be closed and payouts withheld in line with our enforcement process.
## Customer Protection
We take customer complaints, fraud alerts, and refund requests seriously. In such cases, we may contact both the customer and your business. If we don’t receive a response within reasonable timelines, refunds may be processed automatically and your account may be restricted or closed.
## Transparency & Communication
If clarifications are required, we contact you at your registered email address. You’ll have the opportunity to respond and provide context. Where no response is received, accounts may be closed and funds held in line with card network and regulatory requirements.
## Enforcement
Violations of our policies, card network rules, or applicable laws may result in:
1. Suspension of payments
2. Account closure
3. Notifications to card networks, partners, or regulators as required
## Commitment to Fairness
Our monitoring standards are applied consistently across all businesses. We aim to work collaboratively to resolve issues where possible, while maintaining strong protections for customers and the payment ecosystem.
# Security Reporting Policy
Source: https://docs.dodopayments.com/miscellaneous/security-policy
Guidelines for responsible disclosure of security vulnerabilities at Dodo Payments.
## Introduction
At Dodo Payments, we take security seriously and appreciate responsible disclosure of vulnerabilities that help us improve our platform. If you discover a security vulnerability, we encourage you to report it to us following the guidelines below.
## Reporting Security Vulnerabilities
If you identify a security vulnerability in our platform, please contact us at **[pt-team@dodopayments.com](mailto:pt-team@dodopayments.com)** with the following details:
* **Description**: A clear and concise explanation of the vulnerability.
* **Steps to Reproduce**: Detailed steps to replicate the issue.
* **Impact**: The potential risk or harm that could arise from the vulnerability.
* **Suggested Mitigations**: (If available) Any recommendations on how to fix the issue.
## How We Handle Reports
1. **Acknowledgment**: Our team will confirm receipt of your report within a reasonable timeframe.
2. **Assessment**: We will review the report and prioritize it based on severity and impact.
3. **Resolution Timeline**: If the issue is already known and logged internally, we will provide an update on its expected resolution.
4. **Follow-up**: We may reach out for additional information or clarification as needed.
## No Monetary Rewards (Bug Bounty Program)
Currently, **Dodo Payments does not have an official bug bounty program**. While we deeply appreciate security researchers' efforts, we **do not offer monetary rewards** for vulnerability disclosures. This ensures fairness for all individuals who have reported similar issues in the past.
## Recognition & Appreciation
We highly value the contributions of ethical security researchers. While we do not offer financial compensation, we would be happy to send **Dodo Payments goodies/merch** as a token of appreciation for valid security reports. If you are interested, please let us know after your report has been reviewed.
## Responsible Disclosure
To ensure a responsible and ethical reporting process, please adhere to the following:
* **Do not publicly disclose** vulnerabilities until our team has had a reasonable time to address them.
* **Do not exploit** the vulnerability beyond what is necessary to demonstrate the issue.
* **Do not access, modify, or delete** data that does not belong to you.
* **Do not perform** any activity that could disrupt our services.
## Contact Us
If you have any security concerns or need further assistance, please reach out to us at **[pt-team@dodopayments.com](mailto:pt-team@dodopayments.com)**.
# Test Mode vs Live Mode
Source: https://docs.dodopayments.com/miscellaneous/test-mode-vs-live-mode
Learn the difference between Test Mode and Live Mode and how to switch between them.
Find the detailed breakdown of the features which will be available in test mode vs live mode
## Feature Comparison
| **Feature** | **Test Mode** | **Live Mode** | **Is Environment Dependent?** |
| ------------------------------------------- | ------------- | ------------- | ----------------------------- |
| **Create Products (One-Time/Subscription)** | ✓ | ✓ | YES |
| **Generate Payment Links** | ✓ | ✓ | YES |
| **Simulate Payments (Card, Wallet, etc.)** | ✓ | x | YES |
| **Process Real Payments** | x | ✓ | YES |
| **Issue Refunds** | ✓ (Simulated) | ✓ (Real) | YES |
| **Access Transaction History** | ✓ (Simulated) | ✓ (Real) | YES |
| **Download Invoices** | ✓ (Simulated) | ✓ (Real) | YES |
| **Link Bank Account** | ✓ | ✓ | Environment Independent |
| **Payouts to Bank Account** | x | ✓ | YES |
| **Simulate Failed Transactions** | ✓ | x | YES |
| **Analytics Section** | ✓ (Simulated) | ✓ (Real) | YES |
| **View Detailed Reports** | ✓ (Simulated) | ✓ (Real) | YES |
| **Customize Invoice Templates** | ✓ | ✓ | Environment Independent |
| **API Keys generation** | ✓ | ✓ | YES |
| **Real-time Fraud Detection** | x | ✓ | YES |
| **Access Live Customer Support** | ✓ | ✓ | Environment Independent |
| **Webhook Creation** | ✓ | ✓ | YES |
| **Business Profile section** | ✓ | ✓ | Environment Independent |
| **My Account section** | ✓ | ✓ | Environment Independent |
| **Create multiple businesses** | ✓ | ✓ | Environment Independent |
| **Payout Compliance** | ✓ | ✓ | Environment Independent |
| **Transaction Emails** | x | ✓ | YES |
### Environment Dependency
**Environment Dependent:** All Environment Dependent features will have independent data in Test Mode and Live Mode. Any changes made in one environment will not affect the other. Eg. API Keys generated in Test Mode will be independent of API keys generated in Live Mode and vice versa
**Environment Independent:** All Environment Independent features will have the same data in both live mode and test mode. Eg. My Account section will have the same data in test mode and live mode. Changes made in one environment will affect the other environment
Live Mode will be available only post successful completion of Identity verification and Business Verification. You can accept real payments only in \`live mode\`.
API Hostnames for Test Mode and Live Mode are below:
* Test Mode: [`https://test.dodopayments.com`](https://test.dodopayments.com/)
* Live Mode: [`https://live.dodopayments.com`](https://live.dodopayments.com/)
## Key Highlights
1. All products created in test mode or live mode will only be available in the respective environment. There is an option of copy to live mode in the test mode environment. This will copy the product with all relevant details and create the same in live mode
2. API keys and Webhooks created in test mode will only be limited to test mode environment and will not affect live mode deployment and vice versa
3. All documents submitted for verification and Bank account details submitted will be the same in test mode and live mode. A change in this information in any environment will also change it in the other environment
## Test Cards and UPI IDs
For detailed information about test cards, UPI IDs, and other testing methods, please refer to our comprehensive [Testing Process](/miscellaneous/testing-process) guide.
This guide includes:
* Test card numbers for different scenarios
* Test UPI IDs for simulating payments
* Instructions for testing various payment outcomes
* Best practices for testing your integration
Test payment methods are only valid in test mode. Never use them for real payments.
## Transaction in Test Mode - Tutorial
Please watch this video on how you can perform a transation in Test Mode.
# Testing Process
Source: https://docs.dodopayments.com/miscellaneous/testing-process
Learn how to test your Dodo Payments integration
## Test Card Details
Dodo Payments provides a set of Test Card numbers that you can use to simulate various payment scenarios. You can use these test card numbers to create simulated payments in test mode without processing actual payments or charges.
Test card numbers are only valid in test mode. Don't use them for real payments.
### Successful Payments (US)
| **Card Number** | **Expiry Date (MM/YY)** | **CVV/CVC** |
| ---------------- | ----------------------- | ----------- |
| 4242424242424242 | 06/32 | 123 |
### Successful Payments (India)
| **Card Number** | **Expiry Date (MM/YY)** | **CVV/CVC** |
| ---------------- | ----------------------- | ----------- |
| 6074825972083818 | 06/32 | 123 |
### Declined Payment (US)
| **Card Number** | **Expiry Date (MM/YY)** | **CVV/CVC** |
| ---------------- | ----------------------- | ----------- |
| 4000000000000002 | 06/32 | 123 |
### Declined Payment (India)
| **Card Number** | **Expiry Date (MM/YY)** | **CVV/CVC** |
| ---------------- | ----------------------- | ----------- |
| 4000000000000127 | 06/32 | 123 |
For complete information on various test cards for different scenarios visit the link below:
[https://docs.stripe.com/testing#cards](https://docs.stripe.com/testing#cards)
## Test UPI Details
[Know what UPI is here.](/features/upi)
| **Status** | **UPI ID** |
| ---------- | ------------ |
| SUCCESS | success\@upi |
| FAILURE | failure\@upi |
## Test Klarna Details
| **Status** | **Date of Birth** | **First Name** | **Last Name** | **Email** | **Street** | **House Number** | **City** | **State** | **Postal Code** | **Phone** |
| ---------- | ----------------- | -------------- | ------------- | ----------------------------------------------------------- | ------------- | ---------------- | -------- | --------- | --------------- | ------------ |
| APPROVED | 07-10-1970 | Test | Person-us | [customer@email.us](mailto:customer@email.us) | Amsterdam Ave | 509 | New York | New York | 10024-3941 | +13106683312 |
| DENIED | 07-10-1970 | Test | Person-us | [customer+denied@email.us](mailto:customer+denied@email.us) | Amsterdam Ave | 509 | New York | New York | 10024-3941 | +13106354386 |
**Note**: The minimum amount required for Klarna transactions is \$50.
## Test Afterpay Details
When testing your Checkout integration, select Afterpay as the payment method and click the Pay button.
### Successful Payment
* Use any valid email address and shipping address for successful payments.
### Failed Authentication
* To test the case where the user fails to authenticate, use your test API keys and view the redirect page. On the redirect page, close the Afterpay modal window and verify that payment failed. The PaymentIntent transitions from `requires_action` to `requires_payment_method`.
**Note**: The minimum amount required for Afterpay transactions is \$50.
For manual capture PaymentIntents in test mode, the uncaptured PaymentIntent auto-expires 10 minutes after successful authorization.
## Other Payment Methods
To test other payment methods like Amazon Pay and Cash App, follow the usual testing procedures. Please note that Apple Pay and Google Pay won't work in test mode.
# Verification Process
Source: https://docs.dodopayments.com/miscellaneous/verification-process
Learn how to successfully complete the onboarding verification process, including identity, business, and bank verification steps, to ensure compliance and facilitate quick approval.
At Dodo Payments, we require all businesses to complete a thorough verification process to ensure compliance with regulatory standards and platform policies. This process covers **Identity Verification**, **Business Verification**, and strict checks on **Anti-Money Laundering** and **Product Category** compliance.
For the most up-to-date and comprehensive details on supported and prohibited product categories, as well as restricted and accepted business locations, please refer to our [Merchant Acceptance Policy](/miscellaneous/merchant-acceptance).
## Verification Forms
The exact forms depend on your account type:
| Account Type | Required Forms |
| ----------------------- | --------------------- |
| **Individual** | KYC + Bank Form |
| **Registered Business** | KYC + KYB + Bank Form |
If you are a **UK registered business**, you must also submit a [Tax Form W-8BEN-E](#4-additional-compliance) as part of the verification process.
### 1. Identity Verification
We use **Persona**, a leading global verification provider following industry standard security measure and following global data privacy guidelines.
KYC ensures the authenticity of individual businesses through:
* **Government-issued ID verification** (passport, national ID, driver's license etc)
* **Selfie check** to confirm liveness and match with ID
Persona's AI-driven process completes verification within minutes while maintaining bank-grade security standards.
For more information about Persona's security measures, visit [withpersona.com/security](https://withpersona.com/security).
### 2. Bank Form
The Bank Form collects your **bank account details** and **contact information**.
**⚠️ Important:**
* Bank account details **must match** the verified identity or registered business name.
* For individuals, the account holder’s name should match the Identity Verification document.
* For registered businesses, it should match the company’s legal name as per Business Verification.
* Dodo Payments cannot process payouts to mismatched or third-party accounts.
### 3. Business Verification
Business Verification is required only for registered businesses. If you have an individual account, you can skip this section.
For registered businesses, verification ensures the legitimacy of the company and its authorised representatives.
#### Required Details
Provide your **Company Registration Number** and supporting document (Certificate of Incorporation or similar as per your jurisdiction).
If you want your tax number reflected on payout invoices for claiming tax benefits, please provide a valid Tax ID and documents.
Examples of tax identifiers across regions: EIN, CRN, VAT ID, GSTIN
Provide details about all Director's or UBO's of the company in the verification form.
**Beneficial Owner(s)**
The beneficial owner is any natural person who, whether acting alone or together, or through other companies, LLPs, or legal persons, owns or is entitled to 10% or more of the shares or capital or profits of the connected user, or who exercises control over the connected user through other means, including enjoying the right to appoint the majority of its directors or to control its management or policy decisions through shareholding or management rights or through shareholders agreements or voting agreements. If the connected user has more than 1 beneficial owner, please provide us with the requisite documents for all.
**Business Director(s)**
Individuals appointed and currently serving as directors on the board of directors for the connected user in accordance with your local laws. Please provide us with the government issued ID's of all Directors.
### 4. Additional Compliance
W-8BEN-E: Only required for UK registered businesses. Ensure you fill out this form if your company is based in the United Kingdom.
Registered businesses from the United Kingdom need to fill a **W-8BEN-E** form to comply with international tax reporting regulations. This ensures smooth payout processing and tax documentation accuracy.
Download the official IRS W-8BEN-E form (PDF) required for UK registered businesses. Tip: Complete the form digitally before printing and signing for best results.
#### What is Form W-8 BEN-E?
W-8 BEN-E forms are Internal Revenue Service (IRS) forms that foreign businesses must file to verify their country of residence for tax purposes, certifying that they qualify for a lower rate of tax withholding.
#### How to Fill Out Form W-8 BEN-E
**1. Name of Organization That is the Beneficial Owner**
This is your entity name.
**2. Country of Incorporation or Organization**
This is the country of residence (where the business is registered). For a corporation, it is the country of incorporation. If you are another type of entity, enter the country under whose laws you are created, organized, or governed.
**3. Chapter 3 Status**
In this section, the most commonly checked box is "Corporation". The majority of foreign entities that are doing business fall under "Corporation" or "Partnership" status. Other options include:
* Foreign government – Controlled Entity
* Foreign government – Integral Part
* Estate
* Simple trust (grantor and complex, too)
* Central Bank of Issue
* Tax-exempt organization
* Private foundation
* International organization
* Disregarded entity
For Private Limited company - check the Corporation box
For Limited Liability Partnership - check the Partnership box
**4. Chapter 4 Status (FATCA Status)**
For SaaS/Digital Products, the most common choice here is Active NFFE. This means the business is an Active Non-Financial Foreign Entity. If none of the other categories fit, Active NFFE is the best option.
**5. Permanent Residence Address**
This is your business's permanent resident address. Your permanent residence address is the address in the country where you claim to be a resident for purposes of that country's income tax.
**8-10. Tax Identification Information**
You may provide the FTIN issued to you by your jurisdiction of tax residence on line 9b for purposes of claiming treaty benefits.
In this part of the form, you simply need to check box 39 to certify that:
* The entity in Part I is a foreign entity that is not a bank or financial institution.
* Less than 50% of the gross income for the preceding calendar year is passive income.
* Less than 50% of assets held are assets that produce or are held for the production of passive income (calculated as a weighted average of the % of quarterly passive assets).
The most important part of the document is the certification and signature. Here, in writing, there needs to be a first name, last name, and date (on which the form was signed). The person signing the W-8BEN-E form must be authorized to sign for the beneficial owner.
#### View Sample W-8 BEN-E Forms
**Sample Profile:**
* Company: ABC Technologies PVT LTD
* Type: SaaS / Digital Products
* Country: India (example)
* TIN: PAN or equivalent tax ID in your country
**Page 1**
**Page 2**
**Page 8**
**Sample Profile:**
* Company: ABC Technologies LLP
* Type: SaaS / Digital Products
* Country: India (example)
* TIN: PAN or equivalent tax ID in your country
**Page 1**
**Page 2**
**Page 8**
## Additional Information Requests
As part of our **ongoing monitoring**, our compliance team may request additional information from time to time.
These requests can be completed directly from the dashboard and **do not block payments or payouts**, but failure to submit them may trigger pauses in payouts/payments processing, further compliance reviews or account restrictions.
For more details, refer to our [Review Monitoring Policy](/miscellaneous/review-monitoring-policy).
## Timeline & Support
* Typical verification time: **1–3 business days (excludes weekends and public holidays)**
* If additional review is needed, you will be notified via email and dashboard.
* For help, reach out to [support@dodopayments.com](mailto:support@dodopayments.com)
## Resubmission of Documents
If there are issues with the documents submitted during the onboarding process, the Dodo Payments Verification Team may request that you resubmit specific documents.
You can resubmit documents using the **resubmit** option in your dashboard.
### When Can You Resubmit Documents?
* **Missing or Incomplete Information**: If essential details such as identification numbers, business name, or other key data are missing.
* **Document Mismatch**: When the documents submitted do not match the business or personal information provided.
* **Unreadable or Expired Documents**: If the uploaded documents are unclear, illegible, or have expired.
* **Additional Clarification Needed**: If the verification team requires further documentation to clarify your identity or business information.
### How to Resubmit Documents
Check your Dodo Payments Dashboard for a resubmission request (you will also receive an email notification if resubmission is required).
The verification team will enable the **Resubmit** option, allowing you to upload new documents.
Navigate to the **Verification Section** in the dashboard and upload the correct documents.
Submit the documents for review by clicking **Submit for Review**.
Review our comprehensive acceptance standards, including supported and prohibited products, and ensure your business is eligible for onboarding.
Understand how ongoing compliance is monitored, learn about triggers for document review, and stay proactive with your account status.