Skip to content

fix(backend): verify Stripe webhooks with constructEventAsync - #2903

Closed
posthog[bot] wants to merge 1 commit into
mainfrom
posthog-self-driving/fixbilling-use-constructeventasync-to-e8e4d8
Closed

fix(backend): verify Stripe webhooks with constructEventAsync#2903
posthog[bot] wants to merge 1 commit into
mainfrom
posthog-self-driving/fixbilling-use-constructeventasync-to-e8e4d8

Conversation

@posthog

@posthog posthog Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Paying customers stay unpaid: every Stripe webhook returns HTTP 400, so no subscription created, changed, or canceled in Stripe reaches our database, and trials never convert server-side.
  • Bun matches the worker export condition of the stripe package, so it loads the SubtleCrypto provider. That provider computes an HMAC only in an async context, so the synchronous constructEvent throws CryptoProviderOnlySupportsAsyncError before the payload parses. processStripeEvent never runs.
  • The webhook controller now verifies with await getStripeClient().webhooks.constructEventAsync(...), which both the Node and SubtleCrypto providers support.
-      const event = getStripeClient().webhooks.constructEvent(
+      const event = await getStripeClient().webhooks.constructEventAsync(
         req.body,
         signature,
         CONFIG.STRIPE_WEBHOOK_SECRET,
       );

Closes #2902

Simplicity

  • One-line provider fix plus a focused test. No new abstractions.

Automated validation

  • The new test runs real signature verification against the true Stripe client (no client swap). On the old sync path under Bun it fails with SubtleCryptoProvider cannot be used in a synchronous context; with the fix it returns HTTP 200 and records the event.
  • Reverted the fix locally to confirm the test reproduces the failure, then restored it.

Independent review

  • Self-reviewed. The diff is minimal: the provider call and its verification test.

Test plan

  • bun packages/scripts/src/testing/test-mongo-env.ts backend -- './packages/backend/src/billing/**/*.test.ts' → 38 pass, 0 fail
  • bunx typescript@7.0.2 --noEmit → clean
  • bunx biome check on the changed files → clean

Created with PostHog Desktop from this inbox report.

Bun matches the `worker` export condition of the `stripe` package, so it
loads the SubtleCrypto provider. That provider computes an HMAC only in an
async context, so the synchronous `constructEvent` throws before the payload
parses. Every webhook delivery returned HTTP 400 and no subscription state
was written.

Switch to `await constructEventAsync`, which the Node and SubtleCrypto
providers both support. Add a controller test that runs real signature
verification, which fails on the old sync path under Bun and passes now.

Generated-By: PostHog Desktop
Task-Id: 78ad5519-9602-4a28-bb96-95bea3327c03
@tyler-dane

Copy link
Copy Markdown
Contributor

Superseded by #2905, which merged this exact change at 15:53Z today along with two tests that exercise the SDK's real crypto path (every prior webhook test stubbed the Stripe client wholesale, which is why nothing caught the bug).

The diagnosis here was correct and matches ours line for line. Closing as a duplicate, not a rejection.

Verified fixed rather than assumed: billing.webhook.controller.ts:38 calls constructEventAsync, no synchronous constructEvent/verifyHeader remains under packages/, and the PostHog fingerprint shows 1 occurrence ever with none since. #2902 is closed.

@tyler-dane tyler-dane closed this Aug 27, 2026
@tyler-dane
tyler-dane deleted the posthog-self-driving/fixbilling-use-constructeventasync-to-e8e4d8 branch August 27, 2026 19:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error

1 participant