Gareth Wilson Gareth Wilson

Guide to Polar Webhooks Features and Best Practices

Published


Polar has become the go-to billing and monetization platform for developers and digital creators who want to sell software, subscriptions, and digital products without the complexity of traditional payment infrastructure. Beyond checkout and subscription management, Polar's webhook system enables developers to react to billing events in real-time, powering everything from access provisioning to usage tracking and customer lifecycle automation.

This guide covers everything you need to know about Polar webhooks: their features, how to configure them, best practices for production deployments, and the common pain points developers face along with solutions to address them.

What are Polar webhooks?

Polar webhooks are HTTP callbacks that deliver event notifications to your endpoints whenever something significant happens in your Polar organization. When a customer makes a purchase, a subscription renews, a benefit is granted, or a refund is issued, Polar sends a signed JSON payload to URLs you configure. This enables you to synchronize your application state, provision access, trigger downstream workflows, and integrate with any service that can receive HTTP requests.

Polar's webhook implementation follows the Standard Webhooks specification, an open standard for webhook delivery that provides consistent signing, verification, and retry semantics across platforms.

Polar webhook features

FeatureDetails
Webhook configurationPolar dashboard UI or API
Signing standardStandard Webhooks (HMAC-based)
Delivery timeout10 seconds (recommended: respond within 2 seconds)
Retry logicUp to 10 retries with exponential backoff
Delivery formatsRaw JSON, Slack, Discord
Manual retryAvailable via delivery log in dashboard
Browsable logDelivery history with payload inspection per endpoint
Endpoint auto-disableAfter 10 consecutive failed deliveries
SDK supportTypeScript, Python, Go, Ruby, PHP (beta)
Sandbox environmentFull sandbox for testing without real charges
IP allowlistingPublished production and sandbox IPs
Rate limit100 requests per second per IP

Supported webhook events

Polar provides over 25 webhook events organized into logical categories covering the entire billing and customer lifecycle.

Billing events

EventDescription
checkout.createdA new checkout session has been created
checkout.updatedA checkout session has been updated

Customer events

EventDescription
customer.createdA new customer has been created
customer.updatedA customer has been updated
customer.deletedA customer has been deleted
customer.state_changedA customer's state has changed, including active subscriptions and granted benefits

Subscription events

EventDescription
subscription.createdA new subscription has been created
subscription.activeA subscription has become active
subscription.updatedCatch-all for cancellations, un-cancellations, and other changes
subscription.canceledA subscription has been canceled
subscription.uncanceledA subscription cancellation has been reversed
subscription.past_dueA subscription payment has failed; customer can recover by updating payment method
subscription.revokedA subscription has been definitively revoked (billing stopped, benefits revoked)

Order events

EventDescription
order.createdA new order has been created. Use billing_reason to distinguish: purchase, subscription_create, subscription_cycle, subscription_update
order.paidAn order payment has been successfully processed
order.updatedAn order has been updated
order.refundedAn order has been refunded

Refund events

EventDescription
refund.createdA new refund has been created
refund.updatedA refund has been updated

Benefit grant events

EventDescription
benefit_grant.createdA benefit has been granted to a customer
benefit_grant.updatedA benefit grant has been updated
benefit_grant.revokedA benefit grant has been revoked

Organization events

EventDescription
benefit.createdA new benefit type has been created
benefit.updatedA benefit type has been updated
product.createdA new product has been created
product.updatedA product has been updated
organization.updatedThe organization settings have been updated

Subscription lifecycle sequences

Understanding how webhook events relate to each other during subscription lifecycle changes is critical for building reliable integrations.

End-of-period cancellation (default)

When a subscription is canceled by the customer or merchant, these events fire immediately:

  1. subscription.updated
  2. subscription.canceled

The subscription retains active status with cancel_at_period_end set to true. When the billing period ends:

  1. subscription.updated
  2. subscription.revoked

The subscription now has canceled status. Billing stops and benefits are revoked.

Immediate revocation

When a merchant cancels with immediate revocation, all three events fire at once:

  1. subscription.updated
  2. subscription.canceled
  3. subscription.revoked

Renewal sequence

When a subscription renews:

  1. subscription.updated — reflects the new current_period_start and current_period_end
  2. order.created — the invoice for the new cycle (status: pending)
  3. order.updated — once payment processes (status: paid)
  4. order.paid — confirmation of successful payment

Setting up Polar webhooks

Via the Polar dashboard

  1. Navigate to your organization's Settings page

  2. Click the Add Endpoint button

  3. Enter the URL where webhook events should be sent

  4. Choose a delivery format:

    • Raw (default) — standard JSON payload for custom integrations
    • Discord — automatically formatted for Discord channel webhooks
    • Slack — automatically formatted for Slack incoming webhooks

    Polar auto-detects Discord and Slack URLs and selects the appropriate format.

  5. Set your secret — either provide your own or let Polar generate a random one. This is used to cryptographically sign every delivery.

  6. Subscribe to events — select which events you want to receive

Webhook payload structure

Polar webhook payloads follow a consistent structure. Each delivery includes Standard Webhooks headers for verification:

  • webhook-id — unique identifier for the webhook delivery
  • webhook-timestamp — Unix timestamp of when the webhook was sent
  • webhook-signature — HMAC signature for verification

The body contains a JSON payload with the event type and relevant data. Here's an example subscription.created payload:

{
  "type": "subscription.created",
  "data": {
    "created_at": "2025-06-15T10:30:00.000Z",
    "modified_at": "2025-06-15T10:30:00.000Z",
    "id": "sub_1234567890",
    "status": "active",
    "current_period_start": "2025-06-15T10:30:00.000Z",
    "current_period_end": "2025-07-15T10:30:00.000Z",
    "cancel_at_period_end": false,
    "customer_id": "cust_abc123",
    "product_id": "prod_xyz789",
    "price_id": "price_def456"
  }
}

Best practices when working with Polar webhooks

Verifying webhook signatures

Polar cryptographically signs every webhook delivery using the Standard Webhooks specification. Always verify signatures to ensure payloads genuinely originated from Polar.

Using the TypeScript SDK (recommended):

import express, { Request, Response } from 'express';
import {
  validateEvent,
  WebhookVerificationError,
} from '@polar-sh/sdk/webhooks';

const app = express();

app.post(
  '/webhook',
  express.raw({ type: 'application/json' }),
  (req: Request, res: Response) => {
    try {
      const event = validateEvent(
        req.body,
        req.headers,
        process.env['POLAR_WEBHOOK_SECRET'] ?? '',
      );

      // Process the event based on type
      switch (event.type) {
        case 'subscription.created':
          // Handle new subscription
          break;
        case 'subscription.revoked':
          // Revoke access
          break;
        case 'order.paid':
          // Fulfill order
          break;
      }

      res.status(202).send('');
    } catch (error) {
      if (error instanceof WebhookVerificationError) {
        res.status(403).send('');
      }
      throw error;
    }
  },
);

Using the Python SDK:

import os
from flask import Flask, request

from polar_sdk.webhooks import validate_event, WebhookVerificationError

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def handle_webhook():
    try:
        event = validate_event(
            payload=request.data,
            headers=dict(request.headers),
            secret=os.environ['POLAR_WEBHOOK_SECRET'],
        )

        match event.type:
            case 'subscription.created':
                # Handle new subscription
                pass
            case 'subscription.revoked':
                # Revoke access
                pass
            case 'order.paid':
                # Fulfill order
                pass

        return '', 202
    except WebhookVerificationError:
        return '', 403

Custom verification (without SDK):

If you're not using an official SDK, remember that the webhook secret must be base64 encoded before generating the signature. The Standard Webhooks specification provides libraries in many languages for this purpose. The SDKs handle encoding automatically.

Respond quickly and process asynchronously

Polar times out webhook deliveries after 10 seconds, and recommends your endpoint respond within 2 seconds. Always acknowledge the webhook immediately and defer heavy processing to a background job.

Implement idempotent processing

Webhooks may be delivered more than once due to retries or network issues. Use the webhook-id header or a combination of event type and resource ID to ensure you don't process the same event twice:

async function processWebhook(event, webhookId) {
  const idempotencyKey = `polar:${webhookId}`;

  const alreadyProcessed = await redis.get(`processed:${idempotencyKey}`);
  if (alreadyProcessed) {
    console.log(`Webhook ${webhookId} already processed, skipping`);
    return;
  }

  await handleEvent(event);

  // Mark as processed with a 24-hour TTL
  await redis.setex(`processed:${idempotencyKey}`, 86400, '1');
}

Use customer.state_changed for access control

Rather than listening to individual subscription and benefit events, the customer.state_changed event provides a consolidated view of a customer's active subscriptions and granted benefits. This simplifies access control logic:

case 'customer.state_changed':
  const { customer_id, active_subscriptions, granted_benefits } = event.data;
  await syncCustomerEntitlements(customer_id, active_subscriptions, granted_benefits);
  break;

Handle subscription cancellation sequences correctly

Polar distinguishes between cancellation (intent to cancel) and revocation (access actually removed). Don't revoke access on subscription.canceled — wait for subscription.revoked.

Detect subscription renewals via order.created

Polar does not currently have a dedicated subscription.renewed event. To detect renewals, listen for order.created and check the billing_reason field.

Polar webhook limitations and pain points

Tight 10-second delivery timeout

The Problem: Polar times out webhook deliveries after 10 seconds. The documentation recommends responding within 2 seconds and warns the timeout may be lowered further in the future. Endpoints that perform any synchronous processing (database writes, third-party API calls, or complex validation) risk exceeding this window.

Why It Happens: Polar optimizes for fast delivery throughput across their infrastructure. A short timeout prevents slow endpoints from creating back-pressure that would delay deliveries to other customers.

Workarounds:

  • Acknowledge webhooks immediately with a 202 response and queue payloads for asynchronous processing via a background worker
  • Avoid any blocking I/O in your webhook handler before sending the response
  • Use a message queue (Hookdeck, Redis, SQS, RabbitMQ) between your endpoint and your processing logic

How Hookdeck Can Help: Hookdeck accepts webhook deliveries on your behalf and provides configurable delivery timeouts, giving your endpoint additional time to process without risking failed deliveries from Polar. Hookdeck's built-in queuing ensures reliable delivery even when your endpoint is temporarily slow.

Automatic endpoint disabling after consecutive failures

The Problem: Polar automatically disables your webhook endpoint after 10 consecutive failed deliveries (any non-2xx response). Once disabled, the endpoint silently stops receiving all events. An email notification is sent to the organization admin, but events that occur while the endpoint is disabled are lost.

Why It Happens: This is a protective mechanism to avoid wasting resources delivering webhooks to endpoints that are consistently failing, which could indicate a misconfigured or decommissioned URL.

Workarounds:

  • Implement robust error handling in your webhook endpoint to always return a 2xx status, even if downstream processing fails (acknowledge first, process later)
  • Monitor the email address associated with your Polar organization for disabling notifications
  • Periodically check your webhook endpoint status in the Polar dashboard
  • After fixing the issue, manually re-enable the endpoint in your organization's webhook settings

How Hookdeck Can Help: Hookdeck acts as a stable intermediary that always accepts deliveries from Polar, preventing your endpoint from being disabled. If your downstream service has issues, Hookdeck queues events and retries delivery with configurable backoff, so you never lose events due to temporary outages.

No dead letter queue for exhausted retries

The Problem: When all 10 retry attempts are exhausted, or when an endpoint is disabled, webhook events are effectively lost. Polar does not maintain a dead letter queue that preserves failed deliveries for later inspection or replay.

Why It Happens: Polar's delivery log allows you to view past deliveries and manually re-trigger them from the dashboard, which partially addresses this need. However, there is no automated mechanism to hold failed events for bulk replay or programmatic recovery.

Workarounds:

  • Use the Polar dashboard's delivery history to manually inspect and re-trigger failed deliveries
  • Implement your own logging at the webhook handler level to capture every incoming payload before processing
  • Build a reconciliation process that periodically queries the Polar API to check for any events your system may have missed

How Hookdeck Can Help: Hookdeck automatically preserves all failed webhook deliveries in a dead letter queue. You can inspect, debug, and replay them individually or in bulk once issues are resolved, ensuring no billing events are permanently lost.

No custom header support on outbound deliveries

The Problem: Polar webhook deliveries include Standard Webhooks headers for signing (webhook-id, webhook-timestamp, webhook-signature) but do not support adding arbitrary custom headers. You cannot configure headers like X-API-Key, X-Tenant-ID, or custom Authorization schemes.

Why It Happens: Polar's webhook system focuses on the Standard Webhooks specification, which defines a specific set of headers for signing and verification. Custom header support is outside this specification's scope.

Workarounds:

  • Use URL path segments or query parameters to encode tenant or routing information (e.g., https://your-api.com/webhooks/polar/tenant-123)
  • Deploy a lightweight proxy that adds required headers before forwarding to your final endpoint
  • Use the webhook secret to differentiate between multiple Polar organizations

How Hookdeck Can Help: Hookdeck's transformations allow you to add any custom headers to requests before forwarding them to your endpoint, eliminating the need for a custom proxy.

Redirects treated as delivery failures

The Problem: Polar does not follow HTTP redirects (301, 302, 307). Any redirect response is treated as a failed delivery, which counts toward the 10-failure auto-disable threshold.

Why It Happens: Following redirects on webhook deliveries introduces security risks (open redirect attacks) and adds unpredictable latency. Most webhook providers take this approach as a deliberate security measure.

Workarounds:

  • Ensure your webhook URL points directly to the final destination with no redirects in the chain
  • Check for www vs. non-www redirects on your hosting provider (a common issue with Vercel and similar platforms)
  • Test your endpoint URL with curl -vvv -X POST <url> to verify there are no redirects
  • If using a CDN or reverse proxy, configure it to handle the webhook path without redirects

How Hookdeck Can Help: Hookdeck provides stable, dedicated webhook URLs that never redirect. Your Polar configuration always points to Hookdeck, while Hookdeck reliably forwards to your actual endpoint regardless of any infrastructure changes on your side.

Base64 secret encoding gotcha

The Problem: The Standard Webhooks specification requires the webhook secret to be base64 encoded before generating the HMAC signature. Developers who implement custom verification logic (without using an official SDK) frequently encounter signature mismatches because they use the raw secret string.

Why It Happens: This is a requirement of the Standard Webhooks specification, not unique to Polar. However, it's a common stumbling block because it's easy to miss in the documentation, and most developers expect to use the secret as-is.

Workarounds:

  • Use Polar's official SDKs (TypeScript, Python) which handle encoding automatically
  • If implementing custom verification, base64 encode the secret before using it: Buffer.from(secret, 'base64') in Node.js or base64.b64decode(secret) in Python
  • Use the Standard Webhooks libraries available in many languages, which handle encoding correctly

How Hookdeck Can Help: Hookdeck supports Standard Webhooks signature verification natively. Configure your webhook secret in Hookdeck, and it handles signature validation before forwarding verified events to your endpoint.

No dedicated subscription renewal event

The Problem: There is no subscription.renewed event in Polar's webhook system. To detect subscription renewals, you must listen for order.created events and check the billing_reason field for the value subscription_cycle.

Why It Happens: Polar models renewals as new orders rather than subscription state changes, which aligns with their billing architecture but creates friction for developers who think in terms of subscription lifecycle events.

Workarounds:

  • Listen for order.created and filter on billing_reason === 'subscription_cycle'
  • Alternatively, use subscription.updated and compare current_period_start / current_period_end changes to detect when a new billing cycle has started
  • Combine both approaches for maximum reliability

How Hookdeck Can Help: Hookdeck's filter and transformation capabilities let you create a virtual "subscription renewed" event by filtering order.created events where billing_reason equals subscription_cycle and routing them to a dedicated handler, making your integration logic cleaner.

Cloudflare Bot Fight Mode blocks webhook deliveries

The Problem: If your endpoint sits behind Cloudflare with Bot Fight Mode enabled, Polar's webhook deliveries will be blocked with 403 errors. Standard mitigations like IP allowlisting and custom WAF rules do not override Bot Fight Mode.

Why It Happens: Cloudflare's Bot Fight Mode identifies and blocks automated HTTP requests, and Polar's webhook delivery infrastructure (correctly) appears as automated traffic.

Workarounds:

  • Disable Bot Fight Mode entirely in your Cloudflare dashboard under Security > Bots
  • Use a separate subdomain or path that bypasses Cloudflare for webhook endpoints
  • Add Polar's production IPs to your firewall allowlist if you're not using Bot Fight Mode

How Hookdeck Can Help: Point your Polar webhooks at Hookdeck's infrastructure, which is purpose-built for receiving webhooks. Hookdeck then delivers to your Cloudflare-protected endpoint using its own delivery mechanism, avoiding Bot Fight Mode conflicts.

Testing Polar webhooks

Use the sandbox environment

Polar provides a full sandbox environment that mirrors production. Use it to test your webhook handlers with realistic data without processing real payments:

  • Create test products and subscriptions
  • Simulate purchases, cancellations, and refunds
  • Verify your endpoint handles all event types correctly
  • Test failure scenarios by temporarily returning error codes

Inspect payloads before building handlers

Before implementing your handler logic, use a request inspection tool like Hookdeck Console to capture and examine real Polar payloads:

  1. Create a temporary Hookdeck URL
  2. Configure it as your webhook endpoint in Polar's sandbox
  3. Trigger events by creating test purchases
  4. Inspect the exact payload structure and headers

Review delivery history

Use Polar's built-in delivery log to monitor webhook activity:

  • View all past deliveries for each endpoint
  • Inspect the actual payloads that were sent
  • Manually re-trigger failed deliveries for debugging
  • Verify that your endpoint is returning appropriate status codes

Test common failure scenarios

Validate that your integration handles edge cases:

  • Duplicate deliveries (idempotency)
  • Out-of-order events (e.g., order.paid arriving before order.created)
  • Rapid successive events (e.g., create then immediate cancel)
  • Payload signature verification failures
  • Timeout behavior (ensure your handler responds within 2 seconds)

Conclusion

Polar webhooks provide a solid foundation for integrating billing events into your application. The Standard Webhooks specification ensures consistent signing and verification, the comprehensive event catalog covers the full subscription lifecycle, and official SDKs in multiple languages make implementation straightforward. The sandbox environment and delivery log give developers the tools they need to build and debug integrations with confidence.

However, the tight 10-second timeout, automatic endpoint disabling, lack of a dead letter queue, and absence of custom header support mean production deployments require careful architectural decisions. Processing webhooks asynchronously, implementing idempotency, and building reconciliation processes will address most common issues.

For straightforward integrations with reliable endpoints and moderate event volumes, Polar's built-in webhook system combined with proper error handling and the official SDKs works well. For mission-critical billing workflows where every event matters, high-volume scenarios, or complex multi-destination routing, webhook infrastructure like Hookdeck can address Polar's limitations — providing configurable timeouts, automatic queuing, dead letter preservation, payload transformation, and comprehensive delivery monitoring without modifying your Polar configuration.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.