Dodo Payments
Configure the Dodo Payments provider — API key, test mode, webhook signing secret, capabilities, and the quirks of its unversioned API.
Dodo Payments is a merchant of record. Import the factory from revenue-sdk/dodo-payments.
import { createClient } from 'revenue-sdk';
import { dodoPayments } from 'revenue-sdk/dodo-payments';
const client = createClient({
provider: dodoPayments({ apiKey: process.env.DODO_PAYMENTS_API_KEY! }),
});
Factory options
apiKey?string
Dodo Payments API key, sent as a Bearer credential.
stringserver?'live' | 'test'
Selects live.dodopayments.com or test.dodopayments.com.
'live' | 'test'livebaseUrl?string
Overrides server; used verbatim.
stringfetch?typeof fetch
Custom fetch implementation.
typeof fetchAuthentication
Create an API key in the Dodo Payments dashboard under Developer → API keys. Test and live modes are separate hosts with separate keys and separate catalogs.
Test mode
dodoPayments({ apiKey: process.env.DODO_PAYMENTS_TEST_API_KEY!, server: 'test' });
Webhooks
Create the endpoint in the dashboard under Developer → Webhooks and copy the signing secret
(whsec_…).
import { parseWebhookEvent, verifyWebhook } from 'revenue-sdk/dodo-payments';
const headers = request.headers;
const body = await request.text();
if (!(await verifyWebhook({ headers, body, secret: env.DODO_PAYMENTS_WEBHOOK_SECRET }))) {
return new Response('invalid signature', { status: 401 });
}
const event = await parseWebhookEvent({ headers, body });
Dodo Payments uses Standard Webhooks headers (webhook-id,
webhook-timestamp, webhook-signature) with a 300-second tolerance.
Events worth subscribing to: subscription.active, subscription.updated, subscription.renewed,
subscription.plan_changed, subscription.on_hold, subscription.paused, subscription.cancelled,
subscription.expired, subscription.failed, payment.succeeded.
Capabilities
| Capability | Value |
|---|---|
cancellationReason |
true |
checkoutStatus |
true |
checkoutSuccessUrl |
true |
endTrial |
false |
hostedCheckout |
true |
listSubscriptionsByCustomer |
true |
portalReturnUrl |
true |
prorationBehaviors |
['invoice_now', 'none'] |
revoke |
true |
uncancel |
true |
Quirks
- The product is the purchasable unit. Dodo prices have no identifier of their own, so
Price.id,Price.checkoutRef, andProduct.idare all the product ID, and each product has at most one price. - No end-trial operation.
subscriptions.endTrialthrowsunsupported(endTrial: false). - No
prorateproration. There is no defer-to-next-invoice mode, onlyprorated_immediatelyanddo_not_bill. OmittingprorationBehaviorbehaves asinvoice_now. - Unversioned, additive API. There is no version header and fields are added without notice, so the
SDK’s wire types are tolerant, enums are treated as open, and ID prefixes are never validated.
Unrecognized subscription statuses fall back to
active. TimeIntervalis capitalized ('Month','Year') — the only enum in the SDK that is. It is normalized to the lowercase unifiedBillingInterval.- Pagination has no
has_more. Lists use zero-basedpage_numberwith an{ items }envelope; the SDK stops when a page comes back shorter than the requested size. See Pagination. POST /checkoutsis the current endpoint. The legacyPOST /paymentsandPOST /subscriptionsflows are deprecated and unused.POST /change-planreturns 200 with an empty body, sosubscriptions.changePlanre-fetches the subscription to return its updated state — one extra request.- Portal session parameters go in the query string on a POST
(
POST /customers/{id}/customer-portal/session?return_url=…). checkouts.getreturns no URL. The status endpoint omits it, so aCheckoutread back hasurl: ''. Persist the URL fromcheckouts.create.