--- title: "Stripe Plugin" aliases: [payload-stripe, plugin-stripe, stripe-payments-payload] tags: [payloadcms, stripe, payments, ecommerce, plugin, webhooks, sync] sources: [raw/plugins__stripe.md] created: 2026-05-15 updated: 2026-05-15 --- # Stripe Plugin Integrates [Stripe](https://stripe.com) billing into Payload CMS — two-way sync, webhook handling, and a proxied Stripe REST API behind Payload access control. ## Installation ```bash pnpm add @payloadcms/plugin-stripe ``` ## Basic Config ```ts import { stripePlugin } from '@payloadcms/plugin-stripe' buildConfig({ plugins: [ stripePlugin({ stripeSecretKey: process.env.STRIPE_SECRET_KEY, }), ], }) ``` ## Options | Option | Type | Default | Description | |--------|------|---------|-------------| | `stripeSecretKey` * | string | — | Stripe secret key | | `stripeWebhooksEndpointSecret` | string | — | Webhook signing secret | | `rest` | boolean | `false` | Open `/api/stripe/rest` proxy (dev only) | | `webhooks` | object \| function | — | Webhook event handlers | | `sync` | array | — | Auto-sync configs between collections and Stripe resources | | `logs` | boolean | `false` | Log sync events to console | ## Auto-Opened Endpoints | Endpoint | Method | Description | |----------|--------|-------------| | `/api/stripe/rest` | POST | Proxied Stripe REST API (behind Payload access control) | | `/api/stripe/webhooks` | POST | Stripe webhook receiver | ## Webhooks Setup **Dev:** ```bash stripe login stripe listen --forward-to localhost:3000/api/stripe/webhooks # paste the secret into .env as STRIPE_WEBHOOKS_ENDPOINT_SECRET ``` **Production:** Create webhook in Stripe dashboard → set URL to `YOUR_DOMAIN/api/stripe/webhooks` → paste secret into `.env`. **Handler config:** ```ts stripePlugin({ stripeSecretKey: process.env.STRIPE_SECRET_KEY, stripeWebhooksEndpointSecret: process.env.STRIPE_WEBHOOKS_ENDPOINT_SECRET, webhooks: { 'customer.subscription.updated': ({ event, stripe, stripeConfig }) => { // handle event }, }, }) ``` ### Serverless Warning Stripe expects a `2xx` response within 10–20 seconds. The plugin processes webhooks asynchronously — in serverless environments the function instance may close before processing completes, causing duplicate events. **Vercel fix:** install `@vercel/functions` — the plugin auto-detects it and wraps handlers in `waitUntil()`. ## Sync — Two-Way Auto-Sync Maps Payload collection fields to Stripe resource properties. Hooks + webhook handlers are created automatically. ```ts stripePlugin({ sync: [ { collection: 'customers', stripeResourceType: 'customers', stripeResourceTypeSingular: 'customer', fields: [ { fieldPath: 'name', stripeProperty: 'name' }, ], }, ], }) ``` **What `sync` does automatically:** - Adds read-only `stripeID` field (Stripe-generated cross-reference) - Adds direct link to resource on Stripe.com in admin - Adds read-only `skipSync` flag to prevent infinite loops - Attaches hooks: `beforeValidate: createNewInStripe`, `beforeChange: syncExistingWithStripe`, `afterDelete: deleteFromStripe` - Handles webhooks: `STRIPE_TYPE.created/updated/deleted` > **Limitation:** Only top-level fields supported (Stripe API constraint). ## Server-Side Usage Prefer direct Stripe SDK on server rather than the REST proxy: ```ts import Stripe from 'stripe' const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2022-08-01' }) const customer = await stripe.customers.create({ email: data.email }) ``` Or use `stripeProxy` from the plugin: ```ts import { stripeProxy } from '@payloadcms/plugin-stripe' const customer = await stripeProxy({ stripeSecretKey: process.env.STRIPE_SECRET_KEY, stripeMethod: 'customers.create', stripeArgs: [{ email: data.email }], }) ``` ## TypeScript ```ts import { StripeConfig, StripeWebhookHandler, StripeProxy } from '@payloadcms/plugin-stripe/types' ``` ## Key Takeaways - **Hides Stripe credentials** — client-side calls go through Payload's access control, never directly to Stripe - **REST proxy (`rest: true`) is dev-only** — opening it in production gives authenticated users full Stripe API access - **Two-way sync requires webhooks** — set `stripeWebhooksEndpointSecret` or changes from Stripe won't propagate to Payload - **Serverless + webhooks = `@vercel/functions`** — without it, async webhook handlers may be killed before completing - **`sync` only works on top-level Stripe fields** — nested fields require manual hooks - **`skipSync` flag prevents infinite loops** — sync hooks set it before triggering webhooks, webhooks check it before re-syncing - For e-commerce, pair with [[wiki/payloadcms/ecommerce|Ecommerce Plugin]] or [[wiki/payloadcms/ecommerce-payment-adapters|Payment Adapters]] ## Related - [[wiki/payloadcms/plugins|Plugins Overview + Official]] - [[wiki/payloadcms/ecommerce|Ecommerce Plugin]] - [[wiki/payloadcms/ecommerce-payment-adapters|Ecommerce — Payment Adapters]] - [[wiki/payloadcms/access-control|Access Control]] - [[wiki/payloadcms/hooks-collections|Collection Hooks]] ## Sources - `raw/plugins__stripe.md` - https://payloadcms.com/docs/plugins/stripe