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.
- 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
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
- You create a subscription with the
on_demand
object 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 /subscriptions Key request fields (body):Request Body Parameters
Request Body Parameters
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
Set
payment_link: true
, redirect the customer to payment_link
to complete mandate authorization.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.
Success
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)
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 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 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.- 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
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_only
is provided on creation andproduct_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.