Stripe
@voltage/stripe registers a Stripe client as a global NestJS provider and handles webhook signature verification automatically via middleware. Verified events are emitted through @voltage/event-manager so any service can react to them with @OnEvent.
Installation
Section titled “Installation”yarn add @voltage/stripe @voltage/event-manager @voltage/zod zod stripeBasic usage
Section titled “Basic usage”register()
Section titled “register()”import { StripeModule } from '@voltage/stripe';
@Module({ imports: [ EventManagerModule.forRoot(), StripeModule.register({ apiKey: process.env.STRIPE_SECRET_KEY, }), ],})export class AppModule {}registerAsync()
Section titled “registerAsync()”import { StripeModule } from '@voltage/stripe';
StripeModule.registerAsync({ inject: [AppConfiguration], useFactory: (config: AppConfiguration) => ({ apiKey: config.stripe.secretKey, }),});The module is global — you do not need to import it in feature modules.
EventManagerModule must be imported before StripeModule. Webhook events are emitted during request handling, after bootstrap, so ordering is not critical — but it is a good habit.
Injecting the client
Section titled “Injecting the client”Use @Inject(Stripe) to inject the client:
import { Inject, Injectable } from '@nestjs/common';import Stripe from 'stripe';
@Injectable()export class PaymentService { constructor(@Inject(Stripe) private readonly stripe: Stripe) {}
async createPaymentIntent(amount: number, currency: string) { return this.stripe.paymentIntents.create({ amount, currency }); }}Webhooks
Section titled “Webhooks”The middleware intercepts requests at /stripe/event/snapshot and /stripe/event/thin before any NestJS guards, interceptors, or controllers run. Signature verification uses Stripe.webhooks.constructEvent — the route will never reach your application code unless the signature is valid.
Configure webhook secrets in the module options:
StripeModule.register({ apiKey: process.env.STRIPE_SECRET_KEY, webhookSecrets: { snapshot: process.env.STRIPE_WEBHOOK_SECRET, thin: process.env.STRIPE_THIN_WEBHOOK_SECRET, },});Both snapshot and thin are optional. The middleware only activates for the paths whose secret is configured.
Webhook verification requires the raw request body. Pass rawBody: true when creating your NestJS application:
const app = await NestFactory.create(AppModule, { rawBody: true });Handling events
Section titled “Handling events”Listen to StripeEvent with @OnEvent:
import { Injectable } from '@nestjs/common';import { OnEvent } from '@voltage/event-manager';import { StripeEvent } from '@voltage/stripe';
@Injectable()export class BillingService { @OnEvent(StripeEvent) async onStripeEvent({ event }: StripeEvent) { switch (event.type) { case 'payment_intent.succeeded': await this.handlePayment(event.data.object); break; case 'customer.subscription.deleted': await this.handleCancellation(event.data.object); break; } }}event is the object returned by Stripe.webhooks.constructEvent — a fully typed Stripe.Event discriminated union. All handlers receive the same event regardless of which endpoint it arrived on (snapshot or thin).
The middleware uses softEmit, so a handler error is logged but does not affect other handlers or the HTTP response.
Local testing
Section titled “Local testing”The Stripe CLI forwards live Stripe events to your local server and generates a temporary webhook secret.
Install the CLI and log in:
stripe loginForward events to your local snapshot endpoint:
stripe listen --forward-to localhost:3000/stripe/event/snapshotThe CLI prints a webhook signing secret on startup:
> Ready! Your webhook signing secret is whsec_... (^C to quit)Set that value as STRIPE_WEBHOOK_SECRET in your local environment. The secret changes each time stripe listen restarts.
Trigger a test event in a second terminal:
stripe trigger payment_intent.succeededAny event type from the Stripe events reference can be triggered this way. The CLI also supports forwarding connect events:
stripe listen --forward-to localhost:3000/stripe/event/snapshot \ --forward-connect-to localhost:3000/stripe/event/snapshotConfiguration
Section titled “Configuration”interface StripeConfiguration { /** Stripe secret key (sk_live_... or sk_test_...). */ apiKey: string; /** * Stripe API version. Defaults to the version bundled with the SDK. * Pin this explicitly in production — upgrading the stripe package will otherwise * silently change the API version and may alter the shape of webhook payloads. */ apiVersion?: string | null; /** Number of automatic retries on network errors. Defaults to 1. */ maxNetworkRetries?: number; /** Custom HTTP agent, e.g. for a proxy. */ httpAgent?: Agent | null; /** Request timeout in milliseconds. Defaults to 80000. */ timeout?: number; /** Stripe API host. Defaults to api.stripe.com. */ host?: string; /** Stripe API port. Defaults to 443. */ port?: number; /** Protocol to use. Defaults to https. */ protocol?: 'http' | 'https'; /** Whether to send telemetry to Stripe. Defaults to false. */ telemetry?: boolean; /** Webhook signature verification. */ webhookSecrets?: { /** Signing secret for the snapshot webhook endpoint (/stripe/event/snapshot). */ snapshot?: string; /** Signing secret for the thin webhook endpoint (/stripe/event/thin). */ thin?: string; /** * Maximum allowed age of a webhook event in seconds. * Applies to both snapshot and thin. Defaults to Stripe's default (300s). */ tolerance?: number; };}