CHURN METRICSPOLAR

Tracking churn with Polar webhooks

Which Polar webhook events mark a cancellation, a revocation, a failed payment or a recovery, how delivery and signatures work, and how to build a churn log.

Published 5 min read

Polar already shows churn in its dashboard. But if you want churn next to your own product data (usage, plan, signup source), you need the raw events in your own database. Polar’s webhooks give you everything, as long as you read them the way Polar intends.

This guide maps Polar’s subscription events to churn, explains the delivery and signature rules that trip up handlers, and shows how to build a churn log you can trust. It sticks to Polar’s own documentation: webhook events, handling and monitoring deliveries, Managing subscriptions, Recovering failed payments, Analytics and the Get Metrics API (all accessed November 2026).

The subscription events

Polar’s webhook events page describes these subscription events:

Event What Polar says it means Churn meaning
subscription.created A new subscription New or returning customer
subscription.active A subscription becomes active: a new paid subscription, or a recovered payment Paying
subscription.updated Any change to the subscription, including renewals Use to sync state
subscription.canceled Canceled; the customer may keep access until the end of the period Churn scheduled
subscription.uncanceled The customer reversed a pending cancellation Scheduled churn reversed
subscription.past_due A renewal payment failed and can still be recovered Involuntary churn risk
subscription.revoked Access ends now: the period of a canceled subscription ended, the merchant revoked it, or payment retries were exhausted Churn happened

Polar notes that subscription.updated fires for every change and suggests listening to the specific events when you only need certain transitions. For churn, the specific events are clearer.

The order of events

Polar documents the sequences, and they matter for counting:

  • Cancel at period end: subscription.updated and subscription.canceled arrive immediately. When the period ends, subscription.updated and subscription.revoked follow.
  • Immediate revocation: subscription.updated, subscription.canceled and subscription.revoked arrive together.

So every churned subscription produces both a subscription.canceled and a subscription.revoked, possibly a month or a year apart. Count churn on subscription.revoked. Count subscription.canceled separately as “cancellations scheduled”, a leading indicator.

Voluntary vs involuntary

subscription.revoked covers both voluntary churn (a cancellation reached its end) and involuntary churn (payment retries ran out). To tell them apart:

  • If the subscription had cancel_at_period_end set and a subscription.canceled without a later subscription.uncanceled, it is voluntary.
  • If it went through subscription.past_due without returning to subscription.active, it is involuntary.

Polar’s failed payments page explains the involuntary path: after a failed renewal the subscription moves to past_due and Polar emails the customer with a link to the Customer Portal. It retries 4 times, at 2, 5, 7 and 7 days after the previous attempt (2, 7, 14 and 21 days after the first failure), and revokes if all retries fail or if the decline code shows the payment method will never succeed, such as lost_card. A grace period for benefits can be set (immediate, 2, 7, 14 or 21 days from past_due_at); it does not change the retry schedule.

One inconsistency to be aware of: Polar’s revocation docs and its webhook reference describe the final status after exhausted retries differently (canceled in one, unpaid in the other). Do not hardcode either; rely on the subscription.revoked event and your log of what came before.

Reasons come with the subscription

When a cancellation has a reason, it is on the subscription: customer_cancellation_reason (values too_expensive, missing_features, switched_service, unused, customer_service, low_quality, too_complex, other) and customer_cancellation_comment (Managing subscriptions). Store them from the subscription.canceled payload so you can split churn by reason.

Delivery rules

Per Polar’s delivery page:

  • Retries: failed deliveries are retried up to 10 times with exponential backoff.
  • Timeout: 10 seconds, with a recommendation to respond within 2 seconds and do heavy work in a background job, since the timeout may be lowered.
  • Disabled endpoints: after 10 consecutive non-2xx responses, the endpoint is disabled and all organization members get an email. Re-enabling is manual, in the webhook settings.
  • Firewalls: Cloudflare Bot Fight Mode can cause 403 failures; allow Polar’s requests with a WAF rule.

That third point is the one to plan for. A deploy that breaks your handler for an afternoon can disable the endpoint, and nothing reaches you until someone turns it back on.

Signatures

The same page explains that the signing scheme depends on when the endpoint’s secret was created. Secrets created on or after 8 September 2026 (00:00 UTC) use the Standard Webhooks scheme and are passed as-is to a Standard Webhooks library. Older secrets use Polar’s original HMAC, where the full whsec_ secret string is base64-encoded before being given to the library. The endpoint’s uses_standard_webhook_signature field tells you which applies, and Polar’s SDKs from 1.0.0-alpha.19 try both.

If you are unsure, check that field before debugging a “signature mismatch” for hours.

Local testing

The Polar CLI’s polar webhook listen forwards your organization’s events to a local URL and shows the secret to use for verification (CLI webhooks).

Building a churn log

  1. One endpoint, specific events. Subscribe to the seven events in the table above.
  2. Verify, store, return. Check the signature, write the raw payload with the event type, subscription ID, customer ID and timestamp, and return a 2xx within a couple of seconds.
  3. Deduplicate. Retries mean repeats. If your deliveries carry a unique message ID, use it; otherwise build a key from the event type, the subscription ID and the subscription’s last-modified timestamp, and skip anything you have already stored.
  4. Process in the background. Update your customer records from the stored event, never in the request.
  5. Keep it append-only. Derive current status and metrics from the log, so you can replay after a bug.
  6. Watch the endpoint. Alert on non-2xx responses before Polar’s 10-failure limit is reached.
  7. Reconcile. Weekly, list subscriptions through the API and compare with your log.

Compare with Polar’s own numbers

Polar’s Analytics defines its metrics precisely, which makes it a good cross-check:

  • Canceled subscriptions: cancellations triggered by customers in the interval; the subscription may still be active.
  • Churned subscriptions: subscriptions whose paid period ended in the interval.
  • Churn rate: churned subscriptions in the interval divided by active subscriptions at the start of the interval.
  • MRR: excludes trialing subscriptions; includes paused and past-due ones until they actually end.

The Get Metrics API returns fields such as active_subscriptions, churned_subscriptions, canceled_subscriptions, churn_rate and monthly_recurring_revenue per interval, with a breakdown of cancellation reasons.

If you adopt the same definitions in your own log, your churn rate and Polar’s should match for the same period. When they do not, you have probably missed events, often because the endpoint was disabled for a while.

What your own log adds

Polar’s analytics answers “how much churn”. Your log, joined with your product data, answers “whose churn and why”: churn by signup source, by activation, by plan, by cancellation reason, by voluntary vs involuntary. That is where the actionable work is.

Doing this with NoChurn

Polar stays the source of truth for subscriptions, payments and its retry schedule, and its analytics remain useful. NoChurn subscribes to your Polar webhooks, stores and deduplicates the events, separates voluntary from involuntary churn, and joins them with custom data you send by API. It adds a churn score from 0 to 100 per customer and the reasons collected in its cancel flow. Disputes stay with Polar as the merchant of record and are shown read only. See NoChurn + Polar.

PROMPT

Read https://www.nochurn.sh/llms.txt,
then follow https://www.nochurn.sh/docs/quickstart/
to add NoChurn to this app. Use a test key
(nc_test_…) until I say we go live.

Stop leaking revenue.

  • Free until $300 MRR
  • No card needed
  • Live in 10 minutes
  • Never a cut of what we recover