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 fromsecret_generated_at: if>= WEBHOOK_STANDARD_SIGNATURE_CUTOFF, it passessecretstraight intoStandardWebhook(key), assuming it's already valid base64 key material.create_endpoint,update_endpoint, andreset_endpoint_secret(server/polar/webhook/service.py) unconditionally stampsecret_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 (EndpointSecretis just a plainstr).- 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__'sbase64.b64decoderaises 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.
Source: polarsource/polar