Sentinel Signal

Stripe Billing + API Key Enforcement (v1)

Source: docs/stripe-billing-integration.md

Document Content

Stripe Billing + API Key Enforcement (v1)

This document defines the production billing path implemented in Sentinel Signal for trial-key issuance, checkout, plan activation, API key enforcement, usage batching, and free-tier gating.

Commercial model

  • Product: Sentinel Signal API
  • Billing model: usage-based metered pricing per scoring call
  • Trial/free tier: first 1,000 calls per month (enforced in API runtime)
  • Paid tiers:
  • Growth: first 1,000 calls/month included, then $0.003 per scoring call
  • Pro: $99/month includes first 50,000 calls, then graduated metered overage pricing

Design intent:

  • no seats
  • no contract sprawl
  • deterministic per-call billing for automation workloads

Billing Unit Definition (Authoritative)

Billing unit:

  • one successful scoring response from a scoring endpoint
  • scored endpoints in scope:
  • POST /v1/score
  • POST /v1/claims/denial/score
  • POST /v1/claims/prior-auth/predict
  • POST /v1/claims/reimbursement/estimate

Billable outcome policy:

  • billable: HTTP 2xx scoring responses
  • not billable: HTTP 4xx / 5xx / transport failures (including 401, 402, 409, 429)
  • not billable: telemetry endpoint calls such as POST /v1/feedback

Retry/idempotency billing policy:

  • retries are billed when they produce an additional successful 2xx scoring response
  • repeated submissions of the same claim payload are treated as distinct billable scoring calls
  • application-level deduplication (if desired) must be implemented by the calling system

1. Exact Stripe Checkout Integration Flow

  1. Create checkout session:
  • Endpoint: POST /v1/billing/checkout/session
  • Auth: X-Admin-Token
  • Inputs: email, plan (growth or pro), optional success_url, cancel_url
  • Behavior:
  • Upsert billing account in rcm.billing_accounts
  • Create/reuse Stripe customer
  • Create Stripe checkout session (mode=subscription)
  • Persist session metadata in rcm.billing_checkout_sessions
  • Return checkout_session_id and redirect checkout_url

Portal self-serve path (preferred for customers):

  • POST /v1/control-plane/workspace/billing/checkout/session (dashboard session auth)
  • GET /v1/control-plane/workspace/billing/status (dashboard session billing snapshot)
  • served via /portal/dashboard

Trial same-key upgrade path (no signup required):

  • POST /v1/keys/trial (no auth; returns ss_trial_...)
  • POST /v1/billing/checkout-session (Bearer trial API key; accepts price_id or plan)
  • Stripe webhook upgrades the same billing account/key in place (no key migration)
  1. Customer completes Stripe Checkout:
  • Stripe redirects browser to success_url
  • Stripe sends webhook events to POST /v1/billing/webhooks/stripe
  1. Webhook verification + idempotent processing:
  • Endpoint verifies Stripe-Signature via HMAC SHA256 and timestamp tolerance
  • Event id is inserted into rcm.billing_webhook_events (ON CONFLICT DO NOTHING) to guarantee idempotency
  • Duplicate event ids are acknowledged as already processed and skipped (no re-provisioning side effects)
  • Subscription/account updates persist Stripe event markers (stripe_last_event_created, stripe_last_event_id) and ignore stale/out-of-order events
  • Provisioning path never auto-mints multiple API keys for the same subscription lifecycle event
  • Supported events update account/subscription state:
  • checkout.session.completed
  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted
  • invoice.paid (and invoice.payment_succeeded alias)
  • invoice.payment_failed
  1. Issue API key after account activation:
  • Endpoint: POST /v1/apikeys/issue
  • Auth: X-Admin-Token
  • Validates plan/status policy for paid plans
  • Stores hashed key in rcm.api_keys
  • Returns cleartext key once (not recoverable after issuance)

2. API Key Database Schema + Enforcement Logic

Schema

  • rcm.billing_accounts
  • Plan, status, Stripe customer/subscription, period bounds, limit overrides
  • rcm.api_keys
  • Key hash, prefix, scopes, lifecycle fields (active, revoked_at, expires_at)
  • rcm.control_plane_setup_links
  • One-time setup-link token hashes for self-serve dashboard onboarding
  • rcm.control_plane_sessions
  • Revocable dashboard sessions scoped to a single billing account/workspace
  • rcm.billing_checkout_sessions
  • Checkout session lifecycle and Stripe mapping
  • rcm.billing_webhook_events
  • Webhook idempotency + processing audit
  • rcm.api_key_usage_monthly
  • Monthly per-workflow usage aggregates

Migrations:

  • sql/migrations/0004_add_billing_and_api_keys.sql
  • sql/migrations/0009_add_control_plane_setup_and_sessions.sql
  • sql/migrations/0010_add_control_plane_identities.sql
  • sql/migrations/0011_add_trial_keys.sql

Enforcement

Bearer auth now supports two modes:

  1. JWT mode (existing)
  • Signed JWT from token service (HS256)
  • Scope check remains unchanged
  1. API key mode (new)
  • If JWT decode fails, bearer token is evaluated as API key
  • API key is hashed with API_KEY_HASH_SECRET and matched against rcm.api_keys.key_hash
  • Enforced checks:
  • key active and not revoked
  • key not expired
  • billing account status is active/trialing
  • required scope present
  • non-active billing returns 402 Payment Required

3. Usage Batching + Reporting Service Design

Runtime batcher

Implemented in app/usage_metering.py.

  • In-memory reservation path per scoring call
  • Per-key rolling 1-second window for RPS enforcement
  • Pending usage is batched and flushed to Postgres periodically
  • Flush trigger:
  • batch size threshold (USAGE_BATCH_SIZE), or
  • periodic interval (USAGE_FLUSH_INTERVAL_SECONDS)
  • Optional Stripe usage reporting is batched separately and posted periodically
  • STRIPE_USAGE_REPORT_INTERVAL_SECONDS (default hourly behavior)
  • Meter Events are used for meter-backed Stripe prices; legacy subscription-item usage records are retained as a fallback for legacy metered prices without a meter

Source of truth and reconciliation

Operational source of truth:

  • for runtime gating and usage endpoints: Postgres aggregates in rcm.api_key_usage_monthly plus in-memory pending counters
  • for invoicing:
  • Stripe Meter Events for meter-backed prices (current Stripe Dashboard default)
  • legacy Stripe subscription-item usage records for older metered prices without meters (compatibility fallback)

Partial-failure behavior:

  • DB updated, Stripe usage post failed:
  • DB aggregate remains advanced
  • usage quantity remains queued for Stripe retry on subsequent report intervals
  • Stripe usage post succeeded, DB update delayed:
  • current implementation posts to Stripe only after DB upsert succeeds; this ordering avoids Stripe-ahead-of-DB under normal flow
  • crash windows can still produce drift and must be covered by reconciliation

Recommended reconciliation control:

  • daily reconciliation job compares rcm.api_key_usage_monthly.billable_count against Stripe period usage totals
  • alert on absolute or relative drift above threshold (example: > 1% or > 100 calls, whichever is larger)
  • investigate and replay missing usage batches before invoice finalization

Invoice finalization timing

  • reconciliation runs daily during the billing period
  • reconciliation runs again immediately before invoice finalization
  • any missing usage batches are replayed before invoices are closed

Storage model

Batched writes upsert into:

  • rcm.api_key_usage_monthly (api_key_id, usage_month, workflow)

Counters tracked:

  • request_count
  • billable_count
  • success_count
  • error_count

Usage posting idempotency (Stripe)

Implemented behavior:

  • usage posting now persists to rcm.usage_report_batches before Stripe submission
  • each batch has:
  • batch_id
  • deterministic stripe_idempotency_key
  • fixed stripe_usage_timestamp
  • Stripe posts use deterministic identifiers/idempotency keys (Meter Events or usage-record fallback), so timeout-boundary retries do not double-bill
  • batch status lifecycle is explicit:
  • pending -> posted on success
  • pending/failed -> retried by reporting worker
  • only unposted batches are selected for retry

Reporting endpoints

  • Caller-scoped report:
  • GET /v1/usage (API key auth)
  • Plan/remaining limits for caller:
  • GET /v1/limits
  • Admin account-level report:
  • GET /v1/billing/accounts/{email}/usage
  • DB-backed aggregate; reflects flushed usage batches (eventually consistent within USAGE_FLUSH_INTERVAL_SECONDS)
  • Admin account lifecycle snapshot:
  • GET /v1/billing/accounts/{email}/status
  • returns plan, subscription status, renewal date, last payment event result, and API key status summary

Self-Serve Dashboard Endpoints (Control-Plane V1)

  • POST /v1/control-plane/auth/signup
  • public email/password signup + workspace dashboard session
  • POST /v1/control-plane/auth/login
  • public email/password login + workspace dashboard session
  • POST /v1/control-plane/setup-links (admin token required)
  • creates one-time setup link for workspace onboarding
  • POST /v1/control-plane/setup-links/redeem
  • consumes setup link and returns short-lived dashboard session token
  • GET /v1/control-plane/workspace
  • returns workspace summary for current dashboard session
  • POST /v1/control-plane/workspace
  • creates/updates workspace display name for current session
  • required payload: {"workspace_name":"<name>"}
  • GET /v1/control-plane/workspace/api-keys
  • lists workspace keys (metadata only, never raw key material)
  • POST /v1/control-plane/workspace/api-keys
  • issues new key and returns cleartext once
  • POST /v1/control-plane/workspace/api-keys/{api_key_id}/rotate
  • replacement issuance + old-key revoke
  • POST /v1/control-plane/workspace/api-keys/{api_key_id}/revoke
  • explicit revoke
  • POST /v1/control-plane/sessions/logout
  • revokes current dashboard session token

4. Free-Tier Gating Logic

Free tier policy (trial):

  • Monthly limit: 1,000 scoring calls
  • RPS limit: 1 req/sec with burst 5
  • Concurrency limit: 1 in-flight scoring request per trial key

Enforcement path:

  1. Before scoring, API reserves one usage unit in batcher
  2. If projected monthly usage exceeds plan limit:
  • trial: HTTP 402 with structured quota payload and upgrade_url
  • non-trial free/admin-managed plans: HTTP 429 with monthly-limit message
  1. If per-second request window exceeds plan RPS:
  • HTTP 429 with Retry-After: 1
  1. If trial per-key concurrency limit is exceeded:
  • HTTP 429 with Retry-After: 1

Applied to scoring endpoints:

  • POST /v1/score
  • POST /v1/claims/denial/score
  • POST /v1/claims/prior-auth/predict
  • POST /v1/claims/reimbursement/estimate

Explicitly out of billing scope:

  • POST /v1/feedback (feedback telemetry only, no scoring compute charge)

Additional abuse controls:

  • Global scoring concurrency cap via API_GLOBAL_SCORING_CONCURRENCY_CAP
  • Daily emergency shutdown threshold via API_DAILY_EMERGENCY_REQUEST_THRESHOLD

5. Plan Change and Collection Lifecycle

Plan upgrade/downgrade behavior:

  • Stripe handles billing-cycle proration according to subscription configuration
  • API access scope/limits follow the latest processed subscription webhook state
  • practical effect: plan/scope changes are applied when webhook processing commits

Cancellation behavior:

  • on customer.subscription.deleted, account status is set to canceled and API keys are deactivated
  • no end-of-period grace is applied in current runtime policy

Payment failure behavior:

  • on invoice.payment_failed, account status transitions to past_due
  • API keys are deactivated immediately
  • protected scoring requests return HTTP 402 Payment Required until account returns to active/trialing

6. Billing Observability

Token-service exports Prometheus metrics at GET /metrics with billing/webhook counters and timers:

  • token_service_stripe_webhook_received_total{event_type}
  • token_service_stripe_webhook_verified_total{event_type}
  • token_service_stripe_webhook_failed_total{stage,reason}
  • token_service_stripe_webhook_processed_total{event_type,outcome}
  • token_service_stripe_webhook_processing_seconds{event_type,outcome}
  • token_service_billing_provisioning_total{event_type,result}
  • token_service_billing_revocations_total{source,result}
  • token_service_billing_active_subscriptions

7. Stripe Customer Portal

Current status:

  • self-serve Stripe Customer Portal is not implemented in v1
  • card updates, invoice requests, and plan/cancellation support are handled through support channels

Support contact:

  • support@sentinelsignal.io

8. Security Hardening Callouts

Webhook verification requirements:

  • verify Stripe signature using the raw request body bytes and Stripe-Signature
  • do not reserialize JSON before verification; raw-body integrity is required for valid signature checks

IP allowlisting note:

  • IP allowlisting for Stripe webhooks is optional, not a substitute for signature verification
  • Stripe IP ranges can change; if allowlisting is used, it must be continuously maintained from Stripe-published ranges

Environment Variables

Core:

  • API_KEY_HASH_SECRET
  • API_KEY_AUTH_ENABLED
  • USAGE_BATCHING_ENABLED
  • USAGE_BATCH_SIZE
  • USAGE_FLUSH_INTERVAL_SECONDS
  • API_GLOBAL_SCORING_CONCURRENCY_CAP
  • API_DAILY_EMERGENCY_REQUEST_THRESHOLD

Stripe:

  • STRIPE_SECRET_KEY
  • STRIPE_WEBHOOK_SECRET
  • STRIPE_PRICE_ID_GROWTH
  • STRIPE_PRICE_ID_PRO
  • STRIPE_CHECKOUT_SUCCESS_URL
  • STRIPE_CHECKOUT_CANCEL_URL
  • STRIPE_WEBHOOK_TOLERANCE_SECONDS
  • STRIPE_USAGE_REPORTING_ENABLED
  • STRIPE_USAGE_REPORT_INTERVAL_SECONDS
  • STRIPE_USAGE_REPORT_BATCH_LIMIT
  • STRIPE_USAGE_REPORT_TIMEOUT_SECONDS

Notes

  • API keys are stored as non-reversible hashes; plaintext key is returned only at issue time.
  • Webhook processing is idempotent by Stripe event id.
  • Batching is eventually consistent for persisted counters, but reservation checks account for pending in-memory usage.
  • GET /portal/spec now merges all token-service /v1/... endpoints so the portal console exposes checkout, key lifecycle, and billing usage operations.
  • /portal/dashboard now includes self-serve Stripe Checkout for growth/pro using dashboard-session-auth control-plane endpoints.