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 is available for all accounts—no approval required. 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
Prerequisites
- Dodo Payments merchant account and API key
- Webhook secret configured and an endpoint to receive events
- A subscription product in your catalog
How on-demand works
- You create a subscription with the
on_demandobject to authorize a payment method and optionally collect an initial charge. - Later, you create charges against that subscription with custom amounts using the dedicated charge endpoint.
- You listen to webhooks (e.g.,
payment.succeeded,payment.failed) to update your system.
Create an on-demand subscription
Endpoint: POST /checkouts Key request fields (body):Please find them in Create Checkout Session
Create an on-demand subscription
- Node.js SDK
- Python SDK
- Go SDK
- cURL
Success
Charge an on-demand subscription
After the mandate is authorized, create charges as needed. Endpoint: POST /subscriptions/{subscription_id}/charge Key request fields (body):Charge request body parameters
Charge request body parameters
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.
- Node.js SDK
- Python SDK
- Go SDK
- cURL
Success
Payment retries
Our fraud detection system may block aggressive retry patterns (and can flag them as potential card testing). Follow a safe retry policy.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)
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 atT + X daysto 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_CARDDO_NOT_HONORFRAUDULENTPICKUP_CARDAUTHENTICATION_FAILURELOST_CARD
For a comprehensive list of decline reasons and whether they are user-correctable, see the
Transaction Failures documentation.
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 daysat the same HH:MM). - Maintain and reference the last successful payment timestamp
Tto 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.- 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
Testing and next steps
1
Create in test mode
Use your test API key to create the subscription with
payment_link: true, then open the link and complete the mandate.2
Trigger a charge
Call the charge endpoint with a small
product_price (e.g., 100) and verify you receive payment.succeeded.3
Go live
Switch to your live API key once you have validated events and internal state updates.
Troubleshooting
- 422 Invalid Request: Ensure
on_demand.mandate_onlyis provided on creation andproduct_priceis 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.