Webhook Debugging Guide

Verifying Polar webhooks (Standard Webhooks)

Short answer

Polar follows the Standard Webhooks spec rather than inventing its own scheme. Every request carries three headers:

The signed content is {webhook-id}.{webhook-timestamp}.{raw body}, HMAC-SHA256, base64-encoded.

Use Polar's SDK where you can

from polar_sdk.webhooks import validate_event, WebhookVerificationError

try:
    event = validate_event(
        payload=raw_body,
        headers=request.headers,
        secret=webhook_secret,
    )
except WebhookVerificationError:
    return Response(status_code=403)

validateEvent (Node) and validate_event (Python) implement the Standard Webhooks check for you, including the timestamp tolerance, and raise on any mismatch.

The secret format gotcha

Polar webhook secrets created on or after 8 September 2026 are Standard Webhooks secrets, prefixed whsec_, and are used exactly as issued. Secrets created before that date are plain strings and have to be base64-encoded before verification, because that's what the older Polar dashboard generated and what the Standard Webhooks libraries expect as input. In short: if it starts with whsec_, use it as-is; otherwise, base64-encode it first.

const secret = raw.startsWith('whsec_')
  ? raw
  : Buffer.from(raw, 'utf8').toString('base64');

Timestamp tolerance

Like Stripe, Standard Webhooks rejects a webhook-timestamp more than about 5 minutes from the current time. A payload you saved and are POSTing back later for testing will fail this check even with the correct secret — see the replay guide for how to re-sign it.

The easier way: WebhookMon

WebhookMon implements Standard Webhooks verification for Polar directly, handles both secret formats automatically, and states the verdict as a sentence: wrong secret, stale timestamp, or a header it doesn't recognize. Replay re-signs with a current timestamp when you need to test a handler again.

An endpoint's settings in WebhookMon with a signing secret field per provider; the Polar field notes that new endpoints use whsec_ secrets and older plain-string secrets work too
Download WebhookMon free trial Learn more

Related guides