Skip to main content
npm install @paykit-sdk/polar

Setup

import { PayKit } from '@paykit-sdk/core';
import { polar } from '@paykit-sdk/polar';

export const paykit = new PayKit(polar());
Required env vars:
POLAR_ACCESS_TOKEN=polar_oat_...
POLAR_WEBHOOK_SECRET=your-webhook-secret

How it works

Polar uses the official @polar-sh/sdk under the hood. Both createPayment and createCheckout create a Polar checkout session — the difference is that createCheckout is the hosted redirect flow. Polar auto-captures payments, so capturePayment simply returns the current payment state. Subscriptions are created through checkouts only — calling createSubscription directly throws ProviderNotSupportedError. When a customer purchases a subscription product via a checkout, Polar fires the subscription.created webhook event.

Webhooks

Enable these events in your Polar dashboard:
  • order.paid
  • order.created
  • customer.created
  • customer.updated
  • customer.deleted
  • subscription.created
  • subscription.updated
  • subscription.revoked
  • refund.created
const webhook = paykit.webhooks
  .setup({ webhookSecret: process.env.POLAR_WEBHOOK_SECRET! })
  .on('payment.created', async event => {
    /* order.created */
  })
  .on('payment.succeeded', async event => {
    /* order.paid */
  })
  .on('subscription.created', async event => {
    /* subscription.created */
  })
  .on('subscription.updated', async event => {
    /* subscription.updated */
  })
  .on('subscription.canceled', async event => {
    /* subscription.revoked */
  })
  .on('customer.created', async event => {
    /* customer.created */
  })
  .on('customer.updated', async event => {
    /* customer.updated */
  })
  .on('customer.deleted', async event => {
    /* customer.deleted */
  })
  .on('invoice.generated', async event => {
    /* emitted alongside payment.succeeded on order.paid */
  })
  .on('refund.created', async event => {
    /* refund.created */
  });

await webhook.handle({
  body: rawBody,
  headersAsObject: Object.fromEntries(request.headers),
  fullUrl: request.url,
});

Raw Polar events

Every incoming Polar event is also emitted as a polar.<event.type> raw event — typed via the @polar-sh/sdk webhook types:
paykit.webhooks
  .setup({ webhookSecret: process.env.POLAR_WEBHOOK_SECRET! })
  .on('polar.order.paid', async event => {
    // event.data is typed as Polar Order
  })
  .on('polar.subscription.revoked', async event => {
    // event.data is typed as Polar Subscription
  })
  .on('polar.refund.created', async event => {
    // event.data is typed as Polar Refund
  });
Polar event to PayKit event mappings:
Polar eventPayKit events emitted
polar.order.createdpayment.created
polar.order.paidpayment.succeeded + invoice.generated
polar.customer.createdcustomer.created
polar.customer.updatedcustomer.updated
polar.customer.deletedcustomer.deleted
polar.subscription.createdsubscription.created
polar.subscription.updatedsubscription.updated
polar.subscription.revokedsubscription.canceled
polar.refund.createdrefund.created

provider_metadata

provider_metadata for Polar is passed through directly to the underlying Polar SDK call, giving you full access to any field the Polar API accepts:
// checkout.provider_metadata is spread into Polar's CheckoutCreate
await paykit.checkouts.create({
  customer: { email: 'user@example.com' },
  item_id: 'product_abc123',
  session_type: 'one_time',
  quantity: 1,
  success_url: 'https://example.com/success',
  cancel_url: 'https://example.com/cancel',
  provider_metadata: {
    allowDiscountCodes: true,
    discountId: 'discount_xyz',
  },
});

// customer.provider_metadata is spread into Polar's CustomerCreate / CustomerUpdate
await paykit.customers.create({
  email: 'user@example.com',
  provider_metadata: {
    externalId: 'usr_123',
  },
});

// updateSubscription.provider_metadata is REQUIRED and must be one of these shapes:
await paykit.subscriptions.update('sub_123', {
  provider_metadata: { productId: 'product_456' },   // change product
});
await paykit.subscriptions.update('sub_123', {
  provider_metadata: { discountId: 'discount_789' }, // apply discount
});
await paykit.subscriptions.update('sub_123', {
  provider_metadata: { trialEnd: new Date('2025-12-31') }, // extend trial
});

// refund.provider_metadata is spread into Polar's refund create
await paykit.refunds.create({
  payment_id: 'order_abc123',
  amount: 1000,
  reason: 'customer_request',
});