> ## Documentation Index
> Fetch the complete documentation index at: https://docs.8space.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Stripe webhook

> Handle Stripe webhook events for subscription lifecycle management

The Stripe webhook endpoint receives and processes events from Stripe to keep your billing state synchronized.

## Endpoint

**POST** `/api/webhook/stripe`

This endpoint verifies the Stripe signature and handles various billing lifecycle events.

## Authentication

This endpoint uses Stripe webhook signature verification instead of user authentication. You must configure the `STRIPE_WEBHOOK_SECRET` environment variable with your webhook signing secret from the Stripe dashboard.

## Headers

<ParamField path="stripe-signature" type="string" required>
  The Stripe webhook signature header for event verification
</ParamField>

## Request body

The request body is the raw Stripe event payload. Stripe sends different event types with varying data structures.

## Supported events

The webhook currently handles these Stripe event types:

### checkout.session.completed

Triggered when a customer completes a checkout session.

```typescript packages/landing/app/api/webhook/stripe/route.ts theme={null}
case "checkout.session.completed": {
  const stripeObject: Stripe.Checkout.Session = event.data
    .object as Stripe.Checkout.Session;

  const session = await findCheckoutSession(stripeObject.id);
  const priceId = session?.line_items?.data[0]?.price?.id;
  const plan = configFile.stripe.plans.find(
    (p) => p.priceId === priceId
  );

  if (!plan) break;

  // TODO: Grant access to the product via Supabase
  // const userId = stripeObject.client_reference_id;
  // Update user in Supabase with hasAccess = true, customerId, priceId, etc.

  break;
}
```

<Note>
  The TODO comments indicate where you should integrate with your Supabase database to grant product access to the user.
</Note>

### checkout.session.expired

Triggered when a checkout session expires without completion.

```typescript theme={null}
case "checkout.session.expired":
  // Handle expired checkout sessions
  break;
```

### customer.subscription.updated

Triggered when a subscription is updated (plan change, renewal, etc.).

```typescript theme={null}
case "customer.subscription.updated":
  // TODO: Update subscription status in database
  break;
```

### customer.subscription.deleted

Triggered when a subscription is canceled or expires.

```typescript theme={null}
case "customer.subscription.deleted": {
  // TODO: Revoke access via Supabase
  break;
}
```

### invoice.paid

Triggered when an invoice payment succeeds (subscription renewal, etc.).

```typescript theme={null}
case "invoice.paid": {
  // TODO: Grant access via Supabase
  break;
}
```

### invoice.payment\_failed

Triggered when an invoice payment fails.

```typescript theme={null}
case "invoice.payment_failed":
  // Handle payment failures (email customer, retry logic, etc.)
  break;
```

## Response

### Success (200)

```json theme={null}
{}
```

Returns an empty JSON object to acknowledge receipt of the webhook event.

### Error (400)

```json theme={null}
{
  "error": "Webhook signature verification failed. Invalid signature."
}
```

Returned when the Stripe signature verification fails, indicating the request may not be from Stripe.

## Implementation notes

The webhook handler verifies the Stripe signature before processing any events:

```typescript packages/landing/app/api/webhook/stripe/route.ts theme={null}
const body = await req.text();
const signature = (await headers()).get("stripe-signature");

try {
  event = stripe.webhooks.constructEvent(body, signature!, webhookSecret);
} catch (err: any) {
  console.error(`Webhook signature verification failed. ${err.message}`);
  return NextResponse.json({ error: err.message }, { status: 400 });
}
```

<Warning>
  Always verify webhook signatures in production. This prevents unauthorized parties from triggering billing actions in your system.
</Warning>

## Setup

### 1. Configure webhook in Stripe

1. Go to the Stripe Dashboard → **Developers** → **Webhooks**
2. Click **Add endpoint**
3. Enter your webhook URL: `https://yourdomain.com/api/webhook/stripe`
4. Select the events you want to receive (or select "all events" for development)
5. Copy the **Signing secret**

### 2. Add environment variable

Add the webhook signing secret to your environment variables:

```bash theme={null}
STRIPE_WEBHOOK_SECRET=whsec_...
```

### 3. Test locally with Stripe CLI

Use the Stripe CLI to forward webhook events to your local development server:

```bash theme={null}
stripe listen --forward-to localhost:3000/api/webhook/stripe
```

The CLI will output a webhook signing secret for local testing. Use this in your `.env.local` file.

### 4. Trigger test events

```bash theme={null}
stripe trigger checkout.session.completed
stripe trigger customer.subscription.updated
stripe trigger invoice.paid
```

## Integration checklist

The current implementation includes TODO comments where you need to integrate with your database:

* [ ] **checkout.session.completed**: Grant product access in Supabase
* [ ] **customer.subscription.updated**: Update subscription status
* [ ] **customer.subscription.deleted**: Revoke access
* [ ] **invoice.paid**: Confirm or extend access
* [ ] **invoice.payment\_failed**: Notify customer and handle grace period

<Info>
  Store the Stripe `customer_id`, `subscription_id`, and `price_id` in your user profiles table to track billing status.
</Info>

## Security considerations

* **Signature verification**: Always verify the `stripe-signature` header
* **Idempotency**: Handle duplicate webhook deliveries gracefully
* **Logging**: Log webhook events for debugging and audit trails
* **Error handling**: Return 200 even if your internal processing fails (Stripe will retry)

## Related

<CardGroup cols={2}>
  <Card title="Billing configuration" icon="gear" href="/configuration/billing">
    Configure Stripe API keys and webhook secrets
  </Card>

  <Card title="Checkout API" icon="credit-card" href="/api/landing/billing">
    Create checkout sessions and customer portals
  </Card>
</CardGroup>
