Skip to content

fix(payment-stripe): handle Stripe webhook signature errors - #16228

Open
calebcgates wants to merge 1 commit into
medusajs:developfrom
calebcgates:fix/stripe-webhook-construct-event-error-handling
Open

fix(payment-stripe): handle Stripe webhook signature errors#16228
calebcgates wants to merge 1 commit into
medusajs:developfrom
calebcgates:fix/stripe-webhook-construct-event-error-handling

Conversation

@calebcgates

Copy link
Copy Markdown

Summary

What — What changes are introduced in this PR?

StripeBase.constructWebhookEvent calls this.stripe_.webhooks.constructEvent(...) and returns its result without a try/catch. This PR wraps that call in a try/catch that rethrows through the existing buildError helper, so a failure during webhook construction produces the same normalized error as every other method in the provider. No behavior changes on the happy path.

Why — Why are these changes relevant or necessary?

Stripe's constructEvent throws a StripeSignatureVerificationError when the payload signature can't be verified (an invalid or spoofed stripe-signature header, or a missing/misconfigured webhookSecret). constructWebhookEvent is the only method in stripe-base.ts that lets a Stripe SDK error escape unwrapped. Every other method (cancelPayment, capturePayment, refundPayment, retrievePayment, updatePayment, the account-holder and payment-method methods, and so on) already wraps its Stripe call in try/catch and rethrows via this.buildError(...), producing a consistent "An error occurred in <method>: <detail>" message.

Because getWebhookActionAndData (which calls constructWebhookEvent) runs in the payment webhook subscriber, a verification failure currently surfaces as a raw Stripe error in the webhook processing/retry path, inconsistent with how the rest of the module reports Stripe failures. Wrapping it keeps the error shape uniform and easier to identify in logs.

How — How have these changes been implemented?

   constructWebhookEvent(data: ProviderWebhookPayload["payload"]): Stripe.Event {
     const signature = data.headers["stripe-signature"] as string

-    return this.stripe_.webhooks.constructEvent(
-      data.rawData as string | Buffer,
-      signature,
-      this.options_.webhookSecret
-    )
+    try {
+      return this.stripe_.webhooks.constructEvent(
+        data.rawData as string | Buffer,
+        signature,
+        this.options_.webhookSecret
+      )
+    } catch (error) {
+      throw this.buildError("An error occurred in constructWebhookEvent", error)
+    }
   }

The catch block mirrors the surrounding convention exactly: same catch-variable name, same terse message format, and the same untyped-error handoff to buildError that the other methods use.

Testing — How have these changes been tested, or how can the reviewer test the feature?

Added a unit test to packages/modules/providers/payment-stripe/src/core/__tests__/stripe-base.spec.ts that calls constructWebhookEvent with an invalid stripe-signature header and asserts the thrown error is the normalized "An error occurred in constructWebhookEvent" message rather than the raw Stripe error. constructEvent performs signature verification locally (no network), so the test is deterministic.


Examples

// Invalid or spoofed signature:
service.constructWebhookEvent({
  rawData: "{}",
  headers: { "stripe-signature": "invalid-signature" },
})
// Before: throws Stripe's raw StripeSignatureVerificationError
//   ("Unable to extract timestamp and signatures from header")
// After:  throws a normalized Error
//   ("An error occurred in constructWebhookEvent: Unable to extract timestamp and signatures from header.")

Checklist

Please ensure the following before requesting a review:

  • I have added a changeset for this PR
    • Every non-breaking change should be marked as a patch
    • To add a changeset, run yarn changeset and follow the prompts
  • The changes are covered by relevant tests
  • I have verified the code works as intended locally
  • I have linked the related issue(s) if applicable

Additional Context

Scoped intentionally to the single unhandled call site in constructWebhookEvent; no other behavior is changed. Reference: Stripe's constructEvent throws StripeSignatureVerificationError on signature-verification failure (https://docs.stripe.com/webhooks/signature).

On the issues-first guideline: I'm raising this directly as a PR because the change is single-method in scope and mirrors an existing convention already used by every other method in the file, so a written issue would be longer than the diff. Happy to open a tracking issue instead if you'd prefer.

Wrap the constructEvent call in constructWebhookEvent with a try/catch
that rethrows through buildError, matching the error-handling convention
used by every other method in this provider. Stripe's constructEvent
throws a StripeSignatureVerificationError on an invalid or missing
signature; normalizing it here keeps webhook failures consistent with
the rest of the module's error output.
@calebcgates
calebcgates requested a review from a team as a code owner July 28, 2026 20:01
@changeset-bot

changeset-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fcee74e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 79 packages
Name Type
@medusajs/payment-stripe Patch
@medusajs/medusa Patch
@medusajs/test-utils Patch
@medusajs/loyalty-plugin Patch
@medusajs/medusa-oas-cli Patch
integration-tests-http Patch
@medusajs/analytics Patch
@medusajs/api-key Patch
@medusajs/auth Patch
@medusajs/caching Patch
@medusajs/cart Patch
@medusajs/currency Patch
@medusajs/customer Patch
@medusajs/file Patch
@medusajs/fulfillment Patch
@medusajs/index Patch
@medusajs/inventory Patch
@medusajs/link-modules Patch
@medusajs/locking Patch
@medusajs/notification Patch
@medusajs/order Patch
@medusajs/payment Patch
@medusajs/pricing Patch
@medusajs/product Patch
@medusajs/promotion Patch
@medusajs/rbac Patch
@medusajs/region Patch
@medusajs/sales-channel Patch
@medusajs/settings Patch
@medusajs/stock-location Patch
@medusajs/store Patch
@medusajs/tax Patch
@medusajs/translation Patch
@medusajs/user Patch
@medusajs/workflow-engine-inmemory Patch
@medusajs/workflow-engine-redis Patch
@medusajs/draft-order Patch
@medusajs/oas-github-ci Patch
@medusajs/cache-inmemory Patch
@medusajs/cache-redis Patch
@medusajs/event-bus-local Patch
@medusajs/event-bus-redis Patch
@medusajs/analytics-local Patch
@medusajs/analytics-posthog Patch
@medusajs/auth-emailpass Patch
@medusajs/auth-github Patch
@medusajs/auth-google Patch
@medusajs/caching-redis Patch
@medusajs/file-local Patch
@medusajs/file-s3 Patch
@medusajs/fulfillment-manual Patch
@medusajs/locking-postgres Patch
@medusajs/locking-redis Patch
@medusajs/notification-local Patch
@medusajs/notification-sendgrid Patch
@medusajs/core-flows Patch
@medusajs/framework Patch
@medusajs/js-sdk Patch
@medusajs/modules-sdk Patch
@medusajs/orchestration Patch
@medusajs/query Patch
@medusajs/types Patch
@medusajs/utils Patch
@medusajs/workflows-sdk Patch
@medusajs/http-types-generator Patch
@medusajs/cli Patch
@medusajs/deps Patch
@medusajs/eslint-plugin Patch
@medusajs/telemetry Patch
@medusajs/admin-bundler Patch
@medusajs/admin-sdk Patch
@medusajs/admin-shared Patch
@medusajs/admin-vite-plugin Patch
@medusajs/dashboard Patch
@medusajs/icons Patch
@medusajs/toolbox Patch
@medusajs/ui-preset Patch
create-medusa-app Patch
@medusajs/ui Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@medusa-os-bot

medusa-os-bot Bot commented Jul 31, 2026

Copy link
Copy Markdown

Thanks for the contribution! Initial automated review looks good.

Small, well-scoped bug fix that wraps constructWebhookEvent's Stripe SDK call in a try/catch and rethrows via the existing buildError helper, matching the pattern used by every other method in the class. Changeset is present and correctly formatted as a patch. A unit test exercising the new error path is included and correct. No security, performance, or correctness issues found.

Triggered by: manual workflow dispatch

@NicolasGorga NicolasGorga left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for the contribution!

@shahednasser shahednasser left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@calebcgates Can you fix the following test error:

Error: src/core/__tests__/stripe-base.spec.ts(86,39): error TS2352: Conversion of type '{ rawData: string; headers: { "stripe-signature": string; }; }' to type '{ data: Record<string, unknown>; rawData: string | Buffer<ArrayBufferLike>; headers: Record<string, unknown>; }' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants