Skip to main content

Webhooks

Dacard.ai uses Stripe webhooks to handle subscription lifecycle events. When a billing event occurs in Stripe, it sends a signed webhook to the platform, which processes the event and updates account state accordingly.

Webhook endpoint

POST https://app.dacard.ai/api/stripe/webhook
This endpoint is public (no Clerk authentication required) but verifies the Stripe signature on every request.

Supported events

EventDescription
checkout.session.completedA customer completed a checkout session. Creates or upgrades the subscription.
customer.subscription.updatedA subscription was modified (plan change, renewal, payment method update).
customer.subscription.deletedA subscription was cancelled or expired. Downgrades the account to the free plan.
invoice.payment_succeededA payment was successfully processed. Resets monthly credit usage.
invoice.payment_failedA payment attempt failed. May trigger grace period or account restriction.

Signature verification

Every incoming webhook is verified using the Stripe webhook signing secret. Requests with invalid signatures are rejected with a 400 Bad Request. The signing secret is stored as an environment variable (STRIPE_WEBHOOK_SECRET) and is never exposed to the client.
Stripe-Signature: t=1234567890,v1=abc123...

Subscription lifecycle

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   Sign up    │────▸│   Checkout   │────▸│   Active     │
│   (Free)     │     │  (Stripe)    │     │  (Pro/Biz)   │
└──────────────┘     └──────────────┘     └──────┬───────┘

                                          ┌───────┴───────┐
                                          ▼               ▼
                                   ┌────────────┐  ┌────────────┐
                                   │  Renewed   │  │ Cancelled  │
                                   │ (credits   │  │ (→ Free)   │
                                   │  reset)    │  │            │
                                   └────────────┘  └────────────┘
  1. User signs up on the Free plan (no Stripe involvement)
  2. User initiates upgrade via POST /api/stripe/checkout to create a Stripe Checkout Session
  3. On successful payment, checkout.session.completed fires and the account is upgraded
  4. Monthly renewals trigger invoice.payment_succeeded, resetting credit counters
  5. Cancellation triggers customer.subscription.deleted, downgrading to Free

Retry behavior

Stripe retries failed webhook deliveries with exponential backoff for up to 3 days. The webhook handler is idempotent, so duplicate deliveries are safe.

Testing webhooks locally

Use the Stripe CLI to forward events to your local dev server:
stripe listen --forward-to localhost:3001/api/stripe/webhook
The CLI provides a temporary webhook signing secret for local development.