@xenterprises/fastify-xstripe
v1.3.0
Published
Fastify plugin for Stripe webhooks with simplified, testable handlers for subscription events.
Readme
@xenterprises/fastify-xstripe
Fastify 5 plugin for Stripe: registers a signature-verified webhook endpoint with 23 built-in event handlers you can override, and decorates the Fastify instance with a ready-to-use Stripe SDK client. For SaaS apps that need Stripe billing webhooks without boilerplate.
Install
npm install @xenterprises/fastify-xstripe fastify@5Minimal example
import Fastify from 'fastify';
import xStripe from '@xenterprises/fastify-xstripe';
const fastify = Fastify({ logger: true });
await fastify.register(xStripe, {
apiKey: process.env.STRIPE_API_KEY, // consumer owns env access
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
});
// Full Stripe SDK, ready to use
const customer = await fastify.xStripe.customers.create({ email: '[email protected]' });
await fastify.listen({ port: 3000 });The plugin never reads process.env itself — pass every value in via options. The webhook route parses the raw request body itself (required for signature verification), so no extra Fastify configuration is needed.
Options
| Name | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| apiKey | string | — | Yes | Stripe secret API key (sk_test_... or sk_live_...) |
| webhookSecret | string | — | Yes | Stripe webhook signing secret (whsec_...) |
| webhookPath | string | "/stripe/webhook" | No | Path where the webhook POST route is registered. Must start with /. |
| handlers | object | {} | No | Custom event handlers keyed by event type. Values must be async functions; they override the default handler for the same event type. |
| apiVersion | string | "2024-11-20.acacia" | No | Stripe API version passed to the SDK |
All options are validated at registration; invalid or missing options throw before the server starts.
Decorators
| Decorator | Type | Description |
|-----------|------|-------------|
| fastify.xStripe | Stripe | The initialized Stripe SDK client — use it for any Stripe API call |
Routes
| Method | Path | Description |
|--------|------|-------------|
| POST | webhookPath (default /stripe/webhook) | Verifies the stripe-signature header against the raw body before any processing, then dispatches the event to the matching handler. Responds 200 { received: true, processed: <bool> } on success, 200 { received: true, processed: false, error } when a handler throws (acknowledging receipt so Stripe does not retry), and 400 when the signature header is missing or verification fails. |
Handlers
Handlers receive (event, fastify, stripe):
event— the verifiedStripe.Eventfastify— the Fastify instance (access your other decorators)stripe— the Stripe client
await fastify.register(xStripe, {
apiKey: process.env.STRIPE_API_KEY,
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
handlers: {
'customer.subscription.created': async (event, fastify, stripe) => {
const subscription = event.data.object;
// your business logic — database updates, emails, provisioning
},
},
});23 default handlers (subscription, invoice, payment intent, customer, payment method, checkout, and charge events) log structured data via fastify.log; any of them can be overridden via handlers. Events with no matching handler are acknowledged with processed: false.
Helper utilities
Named export helpers (also at @xenterprises/fastify-xstripe/helpers): pure functions for common webhook work — formatAmount, getPlanName, isActiveSubscription, isInTrial, getDaysUntilTrialEnd, isRenewal, calculateMRR, getSubscriptionStatusText, getEventDescription, getCustomerEmail, isTestEvent, getMetadata, getPaymentMethodType, getInvoiceLineItems, isSubscriptionInvoice, getNextBillingDate, formatDate.
import { helpers } from '@xenterprises/fastify-xstripe';
helpers.formatAmount(2000, 'USD'); // "$20.00"Error behavior
Registration throws an Error naming the plugin, the option, and an example:
xstripe: missing required option \apiKey` (string), e.g. `app.register(xStripe, { apiKey: 'sk_test_...' })``xstripe: missing required option \webhookSecret` (string), e.g. `app.register(xStripe, { webhookSecret: 'whsec_...' })``xstripe: option \apiKey` must be a non-empty string, e.g. ...`xstripe: option \webhookSecret` must be a non-empty string, e.g. ...`xstripe: option \webhookPath` must be a string starting with '/', e.g. ...`xstripe: option \handlers` must be a plain object mapping event types to functions, e.g. ...`xstripe: option \apiVersion` must be a non-empty string, e.g. ...`
Webhook requests fail with HTTP 400 and a JSON { error } body when the stripe-signature header is missing or signature verification fails; unverified payloads are never dispatched to handlers. Handler exceptions are logged and acknowledged with HTTP 200 (processed: false) so Stripe does not immediately retry.
Testing webhooks locally
stripe listen --forward-to localhost:3000/stripe/webhook
stripe trigger customer.subscription.createdRequirements
- Node.js >= 20
- Fastify >= 5 (peer dependency)
- A Stripe account with API keys
License
Proprietary — All Rights Reserved X Enterprises. See LICENSE.
