Skip to content

Accept Stripe webhook events from any API version - #879

Open
KrzysztofPajak wants to merge 1 commit into
developfrom
fix/stripe-webhook-api-version
Open

KrzysztofPajak wants to merge 1 commit into
developfrom
fix/stripe-webhook-api-version

Conversation

@KrzysztofPajak

@KrzysztofPajak KrzysztofPajak commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Type: bugfix

Issue

The Stripe Checkout webhook (PaymentStripeCheckout/WebHook) did not mark orders as paid.

The webhook endpoint configured in the Stripe dashboard sends events in the API version set on that endpoint, which by default is the account's default version. That version often differs from the one pinned by Stripe.net 52.4.2 (2026-08-26.dahlia). EventUtility.ConstructEvent throws on the mismatch by default, so every payment_intent.succeeded event was rejected and no payment transaction was created.

The failure was also silent. WebHookProcessPayment caught the StripeException, logged it and returned false, and the controller still answered 200 OK. Stripe therefore considered the event delivered, showed no error and never retried it.

Fix by configuration (works without this PR)

Setting the webhook endpoint's API version to 2026-08-26.dahlia (Stripe dashboard → Developers → Webhooks → the endpoint → API version) makes the events match Stripe.net, and the webhook works on the current code. This was confirmed on a live store.

The configuration has to be repeated whenever a GrandNode release upgrades Stripe.net, and nothing tells the store owner when it falls out of step. This PR removes that dependency and makes such failures visible.

Solution

  • ConstructEvent is called with throwOnApiVersionMismatch: false. The signature is still verified. The fields read from the PaymentIntent (amount, currency, metadata with the order id) are stable across API versions.
  • A verification failure is logged and rethrown, so the existing catch in the controller answers 400. Stripe then shows the failed delivery and retries it.
  • The event handling moved out of the try block. Its logic is unchanged.

Breaking changes

None. The public interface IStripeCheckoutService is unchanged. The only behaviour change is that the webhook returns 400 instead of 200 when the signature cannot be verified.

Testing

  1. Configure the Stripe Checkout plugin with test keys and a webhook secret. Point a Stripe test webhook endpoint at /PaymentStripeCheckout/WebHook (or use stripe listen --forward-to). Set the endpoint's API version to something other than 2026-08-26.dahlia.
  2. Place an order and pay with the test card 4242 4242 4242 4242.
  3. Expected: the webhook answers 200 and the order's payment status becomes Paid. Without this PR the same step leaves the order Pending.
  4. Change the webhook secret in the plugin settings to a wrong value and resend the event from the Stripe dashboard.
  5. Expected: the webhook answers 400, the error is logged ("Stripe webhook event could not be verified") and Stripe marks the delivery as failed.

The webhook endpoint sends events in the account's default API version,
which rarely matches the version Stripe.net pins. ConstructEvent rejected
those events, so successful payments were never recorded. The fields read
from the PaymentIntent are stable across versions, so the version check is
turned off.

A failed verification was also swallowed: the controller still answered
200, Stripe considered the event delivered and never retried. The
exception is now rethrown, the webhook answers 400 and the failure is
visible and retried in the Stripe dashboard.
Copilot AI balanced review requested due to automatic review settings October 6, 2026 18:29

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

2 participants