Authenticated API docs

Webhook behavior

Stripe webhook payloads update entitlements after signature verification, provider event idempotency, and fail-closed product mapping.

Implemented provider route

Stripe sends events to POST /api/stripe/webhook. This is a provider route, not a customer API route under /api/v1.

{
  "received": true,
  "status": "processed"
}

Payload shape

The handler processes Stripe event objects with an ID, event type, livemode flag, and data object.

{
  "id": "evt_example",
  "type": "customer.subscription.updated",
  "livemode": false,
  "data": {
    "object": {
      "customer": "cus_example",
      "subscription": "sub_example",
      "items": {
        "data": [
          {
            "price": {
              "id": "price_example",
              "product": "prod_example",
              "unit_amount": 2100
            }
          }
        ]
      },
      "metadata": {
        "webot_entitlement_code": "api_agent_top",
        "webot_account_id": "account_example"
      }
    }
  }
}

Signature verification

Configure STRIPE_WEBHOOK_SECRET in the runtime environment. The route rejects missing, malformed, stale, or mismatched signatures before trusting the payload.

import { createHmac, timingSafeEqual } from "node:crypto";

function verifyStripeSignature(payload: string, header: string, signingValue: string) {
  const timestamp = header.match(/t=([^,]+)/)?.[1];
  const signature = header.match(/v1=([^,]+)/)?.[1];

  if (!timestamp || !signature) {
    return false;
  }

  const expected = createHmac("sha256", signingValue)
    .update(timestamp + "." + payload, "utf8")
    .digest("hex");

  return timingSafeEqual(Buffer.from(signature, "hex"), Buffer.from(expected, "hex"));
}

Retry and idempotency

Duplicate event

The provider event ID is recorded. Replays return a duplicate status without granting access twice.

Ignored event

Unsupported but valid event types can be acknowledged as ignored.

Failed event

Unknown products, missing metadata, mismatched amounts, and unbound customers fail closed.

{
  "received": true,
  "status": "duplicate"
}

After webhook-backed entitlement updates, dashboard callers can confirm access with /api/v1/me, /api/v1/entitlements, and /api/v1/usage.

Checkout success, cancel, session, plan, and payment query parameters are display-only. They may explain UI state, but they never grant API access, dashboard access, or quota.