---
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
