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

# Webhooks

> Stripe webhook endpoint, signature verification, and event handling.

ai-cmo.dev currently exposes one inbound webhook for Stripe billing. This page covers signature verification, event handling, and idempotency for developers integrating the webhook endpoint.

## Stripe webhook

`POST /api/v1/webhooks/stripe`

Public route. Stripe sends events to this endpoint after checkout sessions complete or expire.

```bash Simulate a Stripe webhook event (local/test only) theme={null}
curl -X POST http://127.0.0.1:3000/api/v1/webhooks/stripe \
  -H "Content-Type: application/json" \
  -H "stripe-signature: t=123456789,v1=test_signature" \
  -d '{"type": "checkout.session.completed", "data": {"object": {"id": "cs_test_..."}}}'
```

### Signature verification

The server verifies the Stripe signature against `STRIPE_WEBHOOK_SECRET`:

1. It reads the `stripe-signature` header from the request.
2. It calls `stripe.webhooks.constructEvent(rawBody, signature, webhookSecret)` (or the mock equivalent in local mode).
3. If verification fails, it returns `400 invalid_signature`.

<Warning>
  The endpoint fails closed when `STRIPE_WEBHOOK_SECRET` is unset, returning `503 billing_unavailable`.
</Warning>

### Events handled

| Event type                   | Action                                                                       |
| ---------------------------- | ---------------------------------------------------------------------------- |
| `checkout.session.completed` | Fulfil the top-up: record the credit grant and mark the receipt as succeeded |
| `checkout.session.expired`   | Mark the receipt as failed                                                   |
| Any other type               | Ignored (`"ignored"` status)                                                 |

### Idempotency

Each checkout session is fulfilled at most once. The webhook handler checks the receipt status and the `credit_entries` unique constraint on `(workspace, idem_scope, idempotency_key)`. Replayed events never double-credit.

### Stripe verification in local mode

When `STRIPE_MOCK_SENTINEL=enabled`, the server uses a deterministic mock client that verifies signatures with HMAC-SHA256 over the raw body using `STRIPE_WEBHOOK_SECRET` as the key. This mirrors Stripe's verification closely enough that a tampered body or wrong secret fails identically to production.

### Rate limits

The billing checkout endpoint is rate-limited to 12 requests per 60 seconds per workspace.
