Providers
Dodo Payments
Set up the fetch-based Dodo Payments adapter for hosted checkout, customer portal links, and normalized webhook verification.
Dodo Payments
@openbilling/dodo is a fetch-based Dodo Payments adapter for the current OpenBilling MVP.
Current implementation status
The adapter supports:
- Hosted checkout creation
- Hosted customer portal links
- Webhook signature verification with
standardwebhooks - Normalization for a small set of Dodo events
It does not attempt to invent unsupported Dodo capabilities.
Required configuration
import { createDodoProvider } from '@openbilling/dodo';
const billing = createDodoProvider({
apiKey: process.env.DODO_API_KEY!,
webhookSecret: process.env.DODO_WEBHOOK_SECRET!,
});Required values:
apiKey: Dodo secret API keywebhookSecret: Dodo webhook signing secret
Optional value:
baseUrl: override the default API host for test mode or custom hosts
API behavior
- Default base URL is
https://live.dodopayments.com baseUrlcan override the host- The demo app uses
https://test.dodopayments.com - The Dodo product determines whether checkout is one-time or recurring
Create a checkout
Dodo checkout currently requires productId.
const checkout = await billing.createCheckout({
customerEmail: 'demo@example.com',
productId: 'prod_123',
successUrl: 'https://example.com/success',
cancelUrl: 'https://example.com/cancel',
mode: 'subscription',
metadata: {
teamId: 'team_123',
},
});Notes:
priceIdis not treated as a Dodo substitutemodeis still accepted by the shared interface, but Dodo determines recurring vs one-time behavior from the product itself- If
customerIdis provided, Dodo uses the existing customer - If
customerIdis not provided, Dodo can usecustomerEmail
Create a portal link
const portal = await billing.createPortalLink({
customerId: 'cus_123',
returnUrl: 'https://example.com/account',
});This creates a Dodo customer portal session and returns the hosted portal URL.
Verify a webhook
Dodo verification uses standardwebhooks and requires three raw headers:
webhook-idwebhook-signaturewebhook-timestamp
const event = await billing.verifyWebhook({
payload: rawBody,
headers: {
'webhook-id': request.headers.get('webhook-id') ?? undefined,
'webhook-signature': request.headers.get('webhook-signature') ?? undefined,
'webhook-timestamp': request.headers.get('webhook-timestamp') ?? undefined,
},
});Normalized Dodo event coverage
The current Dodo adapter maps these events:
| Dodo event | Normalized event |
|---|---|
payment.succeeded | payment.succeeded |
subscription.active | subscription.active |
subscription.cancelled | subscription.cancelled |
Unsupported events return:
{
type: 'unknown',
provider: 'dodo',
}Example event handling
import { Payment, Subscription, Webhook } from '@openbilling/core';
switch (event.type) {
case Payment.Succeeded:
console.log('Payment ID:', event.paymentId);
break;
case Subscription.Active:
console.log('Subscription active:', event.subscriptionId);
break;
case Subscription.Cancelled:
console.log('Subscription cancelled:', event.subscriptionId);
break;
case Webhook.Unknown:
console.log('Unsupported Dodo event');
break;
}Known limitations
- Checkout requires
productId - Price-only checkout input is rejected
- Webhook normalization intentionally covers only a narrow MVP event set
- Unsupported Dodo events are preserved in
raw, but not normalized further