OpenBilling
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 key
  • webhookSecret: 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
  • baseUrl can 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:

  • priceId is not treated as a Dodo substitute
  • mode is still accepted by the shared interface, but Dodo determines recurring vs one-time behavior from the product itself
  • If customerId is provided, Dodo uses the existing customer
  • If customerId is not provided, Dodo can use customerEmail
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-id
  • webhook-signature
  • webhook-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 eventNormalized event
payment.succeededpayment.succeeded
subscription.activesubscription.active
subscription.cancelledsubscription.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

On this page