Files
simstudioai--sim/apps/sim/lib/billing/webhooks/idempotency.ts
T
wehub-resource-sync d25d482dc2
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

40 lines
1.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { IdempotencyService } from '@/lib/core/idempotency/service'
/**
* Idempotency service for Stripe webhook handlers.
*
* Stripe delivers webhook events at-least-once and retries failed
* deliveries for up to 3 days. Handlers that perform non-idempotent work
* (crediting accounts, removing credits, resetting usage trackers, etc.)
* must be wrapped in a claim so duplicate deliveries are collapsed to a
* single execution.
*
* Storage is **forced to Postgres** regardless of whether Redis is
* configured. Billing handlers mutate `user_stats` / `organization` /
* `subscription` rows via DB transactions — keeping the idempotency
* record in the same Postgres closes the narrow window where the
* operation commits but a Redis `storeResult` fails, which would cause
* Stripe's next retry to re-run the money-affecting work. The latency
* cost (15 ms per claim/store) is invisible on webhook responses, and
* volume is low enough (roughly one event per customer per billing
* cycle) that DB storage scales comfortably.
*
* `retryFailures: true` means a thrown handler releases the claim so
* Stripe's next retry runs from scratch — without it, one transient
* failure would poison the key for the whole TTL window.
*
* TTL of 7 days is slightly longer than Stripe's 3-day retry horizon so
* late retries still dedupe against completed work. Rows past their TTL
* are handled two ways: `atomicallyClaimDb` reclaims stale rows inline
* via `ON CONFLICT DO UPDATE WHERE created_at < expired_before` (so
* correctness does not depend on cleanup running), and the external
* cleanup cron (scheduled from the infra repo) hits
* `/api/webhooks/cleanup/idempotency` to bound table size.
*/
export const stripeWebhookIdempotency = new IdempotencyService({
namespace: 'stripe-webhook',
ttlSeconds: 60 * 60 * 24 * 7,
retryFailures: true,
forceStorage: 'database',
})