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 keywebhookSecret: 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:
productIdis not treated as a Stripe substitutemodecan bepaymentorsubscription- If
customerIdis provided, Stripe uses the existing customer - If
customerIdis not provided, Stripe can usecustomerEmail
Create a portal link
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 event | Normalized event |
|---|---|
checkout.session.completed with payment_intent | payment.succeeded |
payment_intent.succeeded | payment.succeeded |
customer.subscription.created with active status | subscription.active |
customer.subscription.updated with active status | subscription.active |
customer.subscription.deleted | subscription.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