#14354·polar

Custom webhook secrets get miscategorized as standard-scheme after the Sept 8 cutoff, breaking signing

Author: stilla[bot]Created Sep 10, 2026Updated Sep 10, 2026

Summary

After the Standard Webhooks signing cutoff (2026-09-08), webhook_event.send fails permanently for endpoints whose secret was set via the deprecated custom-secret field, with Invalid base64-encoded string: number of data characters (N) cannot be 1 more than a multiple of 4 (Sentry SERVER-51E).

Root cause

  • sign_webhook (server/polar/webhook/tasks.py) decides the signing scheme from secret_generated_at: if >= WEBHOOK_STANDARD_SIGNATURE_CUTOFF, it passes secret straight into StandardWebhook(key), assuming it's already valid base64 key material.
  • create_endpoint, update_endpoint, and reset_endpoint_secret (server/polar/webhook/service.py) unconditionally stamp secret_generated_at = utc_now() whenever a secret is set, including when the caller supplies an arbitrary custom secret via the deprecated field (DeprecatedWebhookEndpointCreateWithSecret.secret / WebhookEndpointUpdate.secret), which has no format validation (EndpointSecret is just a plain str).
  • Past the cutoff, any endpoint whose secret was set/reset through the custom-secret path is flagged as "standard scheme" even though its value isn't valid base64 key material, so StandardWebhook.__init__'s base64.b64decode raises and the delivery retries indefinitely.

Suggested fix

Only stamp secret_generated_at = utc_now() when the secret was actually produced by generate_webhook_secret(), not when a caller supplies a custom secret via the deprecated field. Alternatively, validate/normalize custom secrets to the expected base64 format before storing them.

Reported via Sentry SERVER-51E, investigated in Slack #sentry.

Sent by @joebon from Stilla investigation.