Skip to content

Production-Shaped Inbound Subscription Billing Webhook Stub (Hard) #59

Description

@aji70

Task: Production-Shaped Inbound Subscription Billing Webhook Stub (Hard)

Overview

Subscriptions are manually toggled. Build a Stripe-style inbound webhook pipeline stub: signature verification, idempotent event store, transactional state transitions, dead-letter/retry metadata, and audit — not a single controller if-statement.


Scope (must all ship)

Endpoint

  • POST /api/v1/webhooks/billing — raw body preserved for HMAC (BILLING_WEBHOOK_SECRET)
  • Dev mode: secret optional with loud warning log; prod: secret required or boot fails / reject all

Event store

  • Entity BillingWebhookEvent: eventId (unique), type, payload, status (processed|ignored|failed), error, receivedAt, processedAt
  • Idempotency: duplicate eventId200 no-op with same outcome recorded

Supported events (minimum)

  • subscription.activated — create or reactivate local Subscription; map external customer/sub IDs
  • subscription.cancelled — cancel with cancelledAt; retain history
  • payment.failed — set past_due (or equivalent) without deleting
  • Unknown type → store as ignored, 200 (do not 500)

Processing

  • Verify signature before parse side effects
  • DB transaction: insert event + apply subscription mutation atomically where possible; on conflict of eventId short-circuit
  • Map external IDs via columns or side table BillingCustomerMap (document choice)
  • Structured audit log entries for each transition

Ops

  • Admin read-only list stub optional: GET /api/v1/admin/billing/webhook-events (RBAC)
  • Metrics/log fields: processing latency, failure reason codes

Security

  • Reject body > max bytes
  • Do not echo secret; do not store full signature header unnecessarily
  • Rate-limit webhook route separately if global auth rate limit would block provider retries

Testing (≥12)

  • Valid activate / cancel / payment.failed
  • Replay idempotency
  • Invalid signature → 401 when secret set
  • Unknown event ignored
  • Concurrent duplicate delivery (unique constraint)
  • Mapping missing external id → failed with actionable error, not silent success
  • Transaction rollback on subscription update failure leaves consistent event status

Deliverables

  • Webhook module, entities/migrations, signature helper, admin list (if included)
  • .env.example + docs/billing-webhooks.md
  • ≥12 tests

Acceptance Criteria

  • Signature + idempotency + three event types behave as specified
  • past_due does not delete subscription
  • Unknown events do not 500
  • Docs + tests; CI green

Out of scope

  • Real Stripe SDK / live money movement
  • Dunning emails (may emit domain event only)

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions