OpenBilling
Providers

Stripe

Set up the fetch-based Stripe adapter for hosted checkout, billing portal links, and normalized webhook verification.

Stripe

@openbilling/stripe is a fetch-based Stripe adapter for the current OpenBilling MVP.

Current implementation status

The adapter supports:

  • Hosted checkout creation through Stripe Checkout Sessions
  • Hosted billing portal links through Stripe Billing Portal
  • Webhook signature verification
  • Normalization for a small set of Stripe events

It does not attempt to cover all Stripe billing features.

Required configuration

import { createStripeProvider } from '@openbilling/stripe';

const billing = createStripeProvider({
  apiKey: process.env.STRIPE_API_KEY!,
  webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
});

Required values:

  • apiKey: Stripe restricted or secret API key
  • webhookSecret: Stripe webhook signing secret

API behavior

  • Base API URL is always https://api.stripe.com
  • Test and live mode use the same base URL
  • The authenticated key determines the environment
  • Outbound requests pin Stripe-Version: 2026-04-22.dahlia

Create a checkout

Stripe checkout currently requires priceId.

const checkout = await billing.createCheckout({
  customerEmail: 'demo@example.com',
  priceId: 'price_123',
  successUrl: 'https://example.com/success',
  cancelUrl: 'https://example.com/cancel',
  mode: 'subscription',
  metadata: {
    teamId: 'team_123',
  },
});

Notes:

  • productId is not treated as a Stripe substitute
  • mode can be payment or subscription
  • If customerId is provided, Stripe uses the existing customer
  • If customerId is not provided, Stripe can use customerEmail
const portal = await billing.createPortalLink({
  customerId: 'cus_123',
  returnUrl: 'https://example.com/account',
});

This creates a Stripe Billing Portal session and returns the hosted portal URL.

Verify a webhook

Stripe verification uses the raw Stripe-Signature header with HMAC-SHA256 verification and a 5-minute tolerance window.

const event = await billing.verifyWebhook({
  payload: rawBody,
  headers: {
    'stripe-signature': request.headers.get('stripe-signature') ?? undefined,
  },
});

You can also pass the signature directly:

const event = await billing.verifyWebhook({
  payload: rawBody,
  signature: request.headers.get('stripe-signature') ?? undefined,
});

Normalized Stripe event coverage

The current Stripe adapter maps these events:

Stripe eventNormalized event
checkout.session.completed with payment_intentpayment.succeeded
payment_intent.succeededpayment.succeeded
customer.subscription.created with active statussubscription.active
customer.subscription.updated with active statussubscription.active
customer.subscription.deletedsubscription.cancelled

Unsupported events, or partial payloads without the minimum required fields, return:

{
  type: 'unknown',
  provider: 'stripe',
}

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 Stripe event');
    break;
}

Known limitations

  • Checkout requires priceId
  • Product-only checkout input is rejected
  • Webhook normalization intentionally covers only a narrow MVP event set
  • Unsupported Stripe events are preserved in raw, but not normalized further

On this page