# How Ghost Integrates with Stripe for Subscription Payments

> Discover how Ghost integrates with Stripe for subscription payments using its three-layer architecture. Secure checkouts, webhook handling, and subscription management are streamlined.

- Repository: [Ghost/Ghost](https://github.com/TryGhost/Ghost)
- Tags: how-to-guide
- Published: 2026-05-18

---

**Ghost integrates with Stripe through a modular three-layer architecture consisting of StripeService, StripeAPI, and WebhookManager, enabling secure checkout sessions, webhook handling, and subscription lifecycle management directly from the core server layer.**

Ghost's payment engine, located in the [TryGhost/Ghost](https://github.com/TryGhost/Ghost) repository, implements a comprehensive Stripe integration that handles everything from initial checkout to subscription renewals. The integration lives in the core server layer and uses the official Stripe Node.js SDK wrapped in domain-specific abstractions. This architecture allows Ghost to process membership payments while keeping the codebase testable and maintainable.

## Architecture Overview

The Stripe integration consists of three primary components that work together to orchestrate payment flows.

### StripeService

**StripeService** acts as the high-level orchestrator that coordinates between the Stripe API client, database models, and webhook handling. Instantiated with dependencies including labs, members service, and settings cache, this service exposes methods to connect, configure, disconnect, and perform subscription-related actions. The service is defined in [`ghost/core/core/server/services/stripe/stripe-service.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/services/stripe/stripe-service.js).

### StripeAPI

**StripeAPI** provides a thin wrapper around the official Stripe Node SDK. This component creates the Stripe client instance, applies Ghost-specific configuration including API version pinning and rate-limiting buckets, and implements all low-level Stripe API calls. It handles operations for customers, products, prices, checkout sessions, and subscription updates. The implementation resides in [`ghost/core/core/server/services/stripe/stripe-api.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/services/stripe/stripe-api.js).

### WebhookManager

**WebhookManager** manages the registration, verification, and parsing of Stripe webhook endpoints. It stores webhook IDs and secrets in the Ghost settings model and supports both "network" mode (creating live endpoints) and "local" mode (using secrets supplied by `stripe-cli`). The manager defines the specific Stripe events Ghost processes, including checkout completion and subscription lifecycle events. Find it in [`ghost/core/core/server/services/stripe/webhook-manager.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/services/stripe/webhook-manager.js).

## End-to-End Subscription Flow

Understanding how Ghost processes subscription payments requires examining the complete flow from configuration to webhook handling.

### Configuration and Setup

When a site administrator connects a Stripe account in the Ghost admin UI, Ghost persists the secret and public keys along with webhook configuration to the settings model. The `StripeService.configure()` method receives an `IStripeServiceConfig` object and passes it to `StripeAPI.configure()`, subsequently starting the webhook manager.

```javascript
// Simplified configuration flow
await stripeService.configure({
    secretKey: settings.stripe_secret_key,
    publicKey: settings.stripe_publishable_key,
    enablePromoCodes: true,
    enableAutomaticTax: false,
    checkoutSessionSuccessUrl: `${siteUrl}/members/thank-you/`,
    checkoutSessionCancelUrl: `${siteUrl}/members/`,
    webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
    webhookHandlerUrl: `${siteUrl}/members/webhooks/stripe/`,
    siteUrl
});

```

### Creating Checkout Sessions

When a visitor initiates a subscription, Ghost calls `StripeAPI.createCheckoutSession()`. This method constructs a `stripeSessionOptions` object that includes `payment_method_types`, success and cancel URLs, optional promotion codes, automatic tax settings, and a `subscription_data` block containing trial settings and custom metadata with Ghost attribution data.

```javascript
// Example from the members service
const session = await stripeService.api.createCheckoutSession(
    priceId,
    memberStripeCustomer,
    {
        successUrl: `${siteUrl}/members/thank-you/`,
        cancelUrl: `${siteUrl}/members/`,
        metadata: { attribution_id: member.id },
        trialDays: 14,
        coupon: 'WELCOME10'
    }
);
// Redirect browser to session.url

```

### Webhook Event Handling

After checkout completion, Stripe sends a `checkout.session.completed` event to the configured webhook URL. The `WebhookManager.start()` method registers or updates the endpoint on Stripe and stores the webhook secret. Incoming requests route to `StripeService.webhookController`, which uses `WebhookManager.parseWebhook()` to parse events and forward them to specific event services.

```javascript
// Inside ghost/core/core/server/services/stripe/webhook-controller.js
const event = webhookManager.parseWebhook(body, signature);
switch (event.type) {
    case 'checkout.session.completed':
        await subscriptionEventService.handleCheckoutCompleted(event);
        break;
    case 'invoice.payment_succeeded':
        await invoiceEventService.handlePaymentSucceeded(event);
        break;
}

```

### Subscription Lifecycle Management

Ghost interacts with the Stripe API to manage ongoing subscriptions through several key methods. The `StripeAPI.createSubscription()` and `StripeAPI.updateSubscriptionItemPrice()` methods handle creation and plan changes. Cancellation occurs via `StripeAPI.cancelSubscription()` for immediate termination or `StripeAPI.cancelSubscriptionAtPeriodEnd()` for end-of-period termination. Coupon management uses `StripeAPI.addCouponToSubscription()` and `StripeAPI.removeCouponFromSubscription()`.

### Billing Portal Integration

Members manage payment methods and view invoices through Stripe's hosted billing portal. Ghost generates portal sessions using `StripeAPI.createBillingPortalSession()`, passing a `return_url` that redirects members back to their account page.

```javascript
async function openBillingPortal(member) {
    const customer = await stripeService.api.getCustomerForMemberCheckoutSession(member);
    const portalSession = await stripeService.api.createBillingPortalSession(customer, {
        returnUrl: `${siteUrl}/members/account/`
    });
    return portalSession.url;
}

```

## Implementation Examples

### Initializing the Stripe Service

The StripeService requires dependency injection during instantiation before configuration.

```javascript
const StripeService = require('./stripe/stripe-service');
const stripeService = new StripeService({
    labs,
    membersService,
    donationService,
    giftService,
    staffService,
    StripeWebhook: require('./models/stripe-webhook'),
    settingsCache,
    models
});

await stripeService.configure({
    secretKey: process.env.STRIPE_SECRET_KEY,
    publicKey: process.env.STRIPE_PUBLISHABLE_KEY,
    enablePromoCodes: true,
    enableAutomaticTax: true,
    checkoutSessionSuccessUrl: `${siteUrl}/members/thank-you/`,
    checkoutSessionCancelUrl: `${siteUrl}/members/`,
    webhookHandlerUrl: `${siteUrl}/members/webhooks/stripe/`,
    siteUrl
});

```

### Handling Webhook Events in Express

The webhook endpoint parses signatures and delegates to the controller.

```javascript
// Express route for Stripe webhooks
router.post('/members/webhooks/stripe/', async (req, res) => {
    const sig = req.headers['stripe-signature'];
    try {
        const event = stripeService.webhookManager.parseWebhook(req.rawBody, sig);
        await stripeService.webhookController.handle(event);
        res.sendStatus(200);
    } catch (err) {
        console.error('Stripe webhook error', err);
        res.sendStatus(400);
    }
});

```

## Key Source Files

- **Service orchestrator**: [`ghost/core/core/server/services/stripe/stripe-service.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/services/stripe/stripe-service.js) - High-level integration and workflow management
- **Stripe API wrapper**: [`ghost/core/core/server/services/stripe/stripe-api.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/services/stripe/stripe-api.js) - Low-level Stripe SDK operations
- **Webhook management**: [`ghost/core/core/server/services/stripe/webhook-manager.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/services/stripe/webhook-manager.js) - Endpoint registration and event parsing
- **Webhook controller**: [`ghost/core/core/server/services/stripe/webhook-controller.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/services/stripe/webhook-controller.js) - Event dispatching logic
- **Event services**: `ghost/core/core/server/services/stripe/services/webhook/*` - Business logic for specific event types
- **Settings persistence**: [`ghost/core/core/server/models/setting.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/models/setting.js) - Stores Stripe keys and webhook configuration
- **Testing utilities**: [`ghost/core/test/utils/stripe-mocker.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/test/utils/stripe-mocker.js) - Mock Stripe API for unit tests

## Summary

- Ghost implements Stripe integration through three modular layers: StripeService (orchestration), StripeAPI (SDK wrapper), and WebhookManager (event handling).
- Checkout sessions are created via `StripeAPI.createCheckoutSession()` with support for trials, coupons, automatic tax, and attribution metadata.
- Webhook events are parsed by `WebhookManager.parseWebhook()` and routed through `StripeService.webhookController` to domain-specific event services like `SubscriptionEventService`.
- Subscription lifecycle operations include creation, cancellation (immediate or at period-end), and coupon management through dedicated StripeAPI methods.
- The billing portal is implemented using `StripeAPI.createBillingPortalSession()` with configurable return URLs.
- All Stripe configuration persists in Ghost's settings model, enabling dynamic reconfiguration through the admin UI.

## Frequently Asked Questions

### How does Ghost handle Stripe webhook security?

Ghost uses the `WebhookManager` class to verify webhook signatures using the secret stored in the settings model. The `parseWebhook()` method in [`ghost/core/core/server/services/stripe/webhook-manager.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/services/stripe/webhook-manager.js) validates the Stripe-Signature header against the raw request body before processing events. Invalid signatures result in a 400 response, preventing unauthorized access to payment workflows.

### Can Ghost process subscription payments without webhooks?

No, Ghost requires webhooks to maintain subscription state consistency. While the initial checkout session creation works via direct API calls, Ghost relies on webhook events like `checkout.session.completed` and `invoice.payment_succeeded` to update internal database tables including `members_stripe_customers` and `members_stripe_customers_subscriptions`. The `StripeService` explicitly starts the webhook manager during configuration to ensure event processing.

### Where does Ghost store Stripe API keys and webhook secrets?

Ghost stores Stripe configuration in the settings model ([`ghost/core/core/server/models/setting.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/models/setting.js)), accessed through the `settingsCache` dependency. The `StripeService` retrieves these values during initialization, passing them to `StripeAPI.configure()` and `WebhookManager.start()`. This architecture allows administrators to update Stripe credentials through the Ghost admin UI without code changes.

### How does Ghost support Stripe test mode?

Ghost detects test mode through the Stripe API keys provided during configuration. The `StripeAPI` wrapper initializes the Stripe client with the appropriate secret key, and the `WebhookManager` can operate in "local" mode using secrets supplied by `stripe-cli` for development environments. The [`ghost/core/test/utils/stripe-mocker.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/test/utils/stripe-mocker.js) utility provides additional mocking capabilities for unit testing payment flows without hitting Stripe's servers.