Repository navigation
Conversation
Polar signs deliveries with Standard Webhooks for every endpoint secret generated on or after 2026-09-08: the key is the base64 part of the whsec_ secret. validateEvent from @polar-sh/sdk 0.x only knows the older key (the UTF-8 bytes of the whole secret), so registerRoutes answers 403 with "No matching signature found" for every delivery to a new endpoint, a new organization, or a regenerated secret. registerRoutes now tries the legacy key first, as before, and then the Standard Webhooks key when the secret starts with whsec_. A delivery that matches the Standard Webhooks key is re-signed with the legacy key and passed to validateEvent, which is the SDK's only public event parser, so event types and parsing stay the same. A delivery that matches neither key still throws the SDK's WebhookVerificationError and gets a 403.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (3)
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughWebhook requests now use Sequence Diagram(s)sequenceDiagram
participant WebhookRoute
participant validatePolarEvent
participant SDKvalidateEvent
participant StandardWebhooks
WebhookRoute->>validatePolarEvent: Pass body, headers, and secret
validatePolarEvent->>SDKvalidateEvent: Validate event
SDKvalidateEvent-->>validatePolarEvent: Raise WebhookVerificationError
validatePolarEvent->>StandardWebhooks: Verify when secret starts with whsec_
StandardWebhooks-->>validatePolarEvent: Return verification result
validatePolarEvent->>SDKvalidateEvent: Retry with replacement signature after successful verification
Priority: ⬇️ Low Change: Bug fix Merge Risk: ⚪ Minimal · up to The webhook compatibility change is ready to merge after normal checks; no actionable risk remains. 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Problem
Polar signs webhook deliveries with one of two keys, depending on when the endpoint secret was generated (Polar docs: Handle & monitor webhook deliveries):
whsec_secret.registerRoutesverifies withvalidateEventfrom@polar-sh/sdk/webhooks. In every 0.x release (checked 0.47.0 through 0.49.0) it only knows the first key. So any endpoint created since 2026-09-08 gets a 403 withNo matching signature foundon every delivery. That includes a new organization, a new endpoint, or a regenerated secret. Subscriptions never reach the component, and checkouts never activate anything.We hit this in production after moving to a new Polar organization. Every
subscription.*andorder.*delivery to/polar/eventsreturned 403.Why not bump the SDK
@polar-sh/sdk1.0 tries both keys, but it no longer exports@polar-sh/sdk/webhooksor@polar-sh/sdk/funcs/*, which this component imports. That is why the Renovate bump in #96 fails CI. Moving to 1.0 is the long-term fix. This PR keeps 0.x working until then.Fix
A new
validatePolarEventinsrc/client/webhooks.ts, used byregisterRoutes:validateEventwith the secret, exactly as before. Legacy secrets, including legacy ones that start withwhsec_, behave as they do today.WebhookVerificationErrorand the secret starts withwhsec_, verify with the Standard Webhooks key viastandardwebhooks, which is already a dependency.validateEvent. That is the SDK's only public event parser, soPolarWebhookEventand event parsing stay the same.WebhookVerificationError, so the route still answers 403, not 500.The Standard Webhooks key is only tried for
whsec_secrets. A legacy secret that happens to be valid base64 therefore does not gain a second key.The re-signing step is the unusual part. It exists only because SDK 0.x does not export its parser. Once the component moves to SDK 1.0, this file can go.
Tests
src/client/webhooks.test.ts, with a realcustomer.deletedpayload that the SDK parses:polar_whs_…)whsec_secret that is not base64whsec_secretwhsec_secret, and a changed body, each with the SDK'sWebhookVerificationErrornpm run build,npm run typecheck,npm run lintandnpm run testpass (75 tests).