# Create Benefit Source: https://polar.sh/docs/api-reference/benefits/create post /v1/benefits/ Create a benefit. **Scopes**: `benefits:write` # Delete Benefit Source: https://polar.sh/docs/api-reference/benefits/delete delete /v1/benefits/{id} Delete a benefit. > [!WARNING] > Every grants associated with the benefit will be revoked. > Users will lose access to the benefit. **Scopes**: `benefits:write` # Get Benefit Source: https://polar.sh/docs/api-reference/benefits/get get /v1/benefits/{id} Get a benefit by ID. **Scopes**: `benefits:read` `benefits:write` # List Benefits Source: https://polar.sh/docs/api-reference/benefits/list get /v1/benefits/ List benefits. **Scopes**: `benefits:read` `benefits:write` # List Benefit Grants Source: https://polar.sh/docs/api-reference/benefits/list-grants get /v1/benefits/{id}/grants List the individual grants for a benefit. It's especially useful to check if a user has been granted a benefit. **Scopes**: `benefits:read` `benefits:write` # Update Benefit Source: https://polar.sh/docs/api-reference/benefits/update patch /v1/benefits/{id} Update a benefit. **Scopes**: `benefits:write` # Create Checkout Link Source: https://polar.sh/docs/api-reference/checkout-links/create post /v1/checkout-links/ Create a checkout link. **Scopes**: `checkout_links:write` Looking to create a single use checkout session? Checkout Links are probably **not** what you're looking for. Checkout Links are shareable links that generate checkout sessions when opened. They are very handy to start a purchase from your website or social media. However, if you want to start a checkout for one of your user **inside** your product, you should use the [Checkout Sessions API](/api-reference/checkouts/create-session). # Delete Checkout Link Source: https://polar.sh/docs/api-reference/checkout-links/delete delete /v1/checkout-links/{id} Delete a checkout link. **Scopes**: `checkout_links:write` # Get Checkout Link Source: https://polar.sh/docs/api-reference/checkout-links/get get /v1/checkout-links/{id} Get a checkout link by ID. **Scopes**: `checkout_links:read` `checkout_links:write` # List Checkout Links Source: https://polar.sh/docs/api-reference/checkout-links/list get /v1/checkout-links/ List checkout links. **Scopes**: `checkout_links:read` `checkout_links:write` # Update Checkout Link Source: https://polar.sh/docs/api-reference/checkout-links/update patch /v1/checkout-links/{id} Update a checkout link. **Scopes**: `checkout_links:write` # Confirm Checkout Session from Client Source: https://polar.sh/docs/api-reference/checkouts/confirm-session-from-client post /v1/checkouts/client/{client_secret}/confirm Confirm a checkout session by client secret. Orders and subscriptions will be processed. # Create Checkout Session Source: https://polar.sh/docs/api-reference/checkouts/create-session post /v1/checkouts/ Create a checkout session. **Scopes**: `checkouts:write` # Get Checkout Session Source: https://polar.sh/docs/api-reference/checkouts/get-session get /v1/checkouts/{id} Get a checkout session by ID. **Scopes**: `checkouts:read` `checkouts:write` # Get Checkout Session from Client Source: https://polar.sh/docs/api-reference/checkouts/get-session-from-client get /v1/checkouts/client/{client_secret} Get a checkout session by client secret. # List Checkout Sessions Source: https://polar.sh/docs/api-reference/checkouts/list-sessions get /v1/checkouts/ List checkout sessions. **Scopes**: `checkouts:read` `checkouts:write` # Update Checkout Session Source: https://polar.sh/docs/api-reference/checkouts/update-session patch /v1/checkouts/{id} Update a checkout session. **Scopes**: `checkouts:write` # Update Checkout Session from Client Source: https://polar.sh/docs/api-reference/checkouts/update-session-from-client patch /v1/checkouts/client/{client_secret} Update a checkout session by client secret. # Create Custom Field Source: https://polar.sh/docs/api-reference/custom-fields/create post /v1/custom-fields/ Create a custom field. **Scopes**: `custom_fields:write` # Delete Custom Field Source: https://polar.sh/docs/api-reference/custom-fields/delete delete /v1/custom-fields/{id} Delete a custom field. **Scopes**: `custom_fields:write` # Get Custom Field Source: https://polar.sh/docs/api-reference/custom-fields/get get /v1/custom-fields/{id} Get a custom field by ID. **Scopes**: `custom_fields:read` `custom_fields:write` # List Custom Fields Source: https://polar.sh/docs/api-reference/custom-fields/list get /v1/custom-fields/ List custom fields. **Scopes**: `custom_fields:read` `custom_fields:write` # Update Custom Field Source: https://polar.sh/docs/api-reference/custom-fields/update patch /v1/custom-fields/{id} Update a custom field. **Scopes**: `custom_fields:write` # Get Customer Meter Source: https://polar.sh/docs/api-reference/customer-meters/get get /v1/customer-meters/{id} Get a customer meter by ID. **Scopes**: `customer_meters:read` # List Customer Meters Source: https://polar.sh/docs/api-reference/customer-meters/list get /v1/customer-meters/ List customer meters. **Scopes**: `customer_meters:read` # null Source: https://polar.sh/docs/api-reference/customer-portal/downloadables/get get /v1/customer-portal/downloadables/{token} # List Downloadables Source: https://polar.sh/docs/api-reference/customer-portal/downloadables/list get /v1/customer-portal/downloadables/ **Scopes**: `customer_portal:read` `customer_portal:write` # Get Customer Source: https://polar.sh/docs/api-reference/customer-portal/get-customer get /v1/customer-portal/customers/me Get authenticated customer. **Scopes**: `customer_portal:read` `customer_portal:write` # Get Organization Source: https://polar.sh/docs/api-reference/customer-portal/get-organization get /v1/customer-portal/organizations/{slug} Get a customer portal's organization by slug. # Activate License Key Source: https://polar.sh/docs/api-reference/customer-portal/license-keys/activate post /v1/customer-portal/license-keys/activate Activate a license key instance. > This endpoint doesn't require authentication and can be safely used on a public > client, like a desktop application or a mobile app. > If you plan to validate a license key on a server, use the `/v1/license-keys/activate` > endpoint instead. You only need to use this endpoint if you have device **activations** enabled on the license key benefit. You then use this endpoint to reserve an allocation for a specific device. Storing the unique activation ID from the response on the device and using it as extra validation in the [/validate](/api-reference/customer-portal/license-keys/validate) endpoint. Not using **activations**? Just use the [/validate](/api-reference/customer-portal/license-keys/validate) endpoint directly instead. # Deactivate License Key Source: https://polar.sh/docs/api-reference/customer-portal/license-keys/deactivate post /v1/customer-portal/license-keys/deactivate Deactivate a license key instance. > This endpoint doesn't require authentication and can be safely used on a public > client, like a desktop application or a mobile app. > If you plan to validate a license key on a server, use the `/v1/license-keys/deactivate` > endpoint instead. # Get License Key Source: https://polar.sh/docs/api-reference/customer-portal/license-keys/get get /v1/customer-portal/license-keys/{id} Get a license key. **Scopes**: `customer_portal:read` `customer_portal:write` # List License Keys Source: https://polar.sh/docs/api-reference/customer-portal/license-keys/list get /v1/customer-portal/license-keys/ **Scopes**: `customer_portal:read` `customer_portal:write` # Validate License Key Source: https://polar.sh/docs/api-reference/customer-portal/license-keys/validate post /v1/customer-portal/license-keys/validate Validate a license key. > This endpoint doesn't require authentication and can be safely used on a public > client, like a desktop application or a mobile app. > If you plan to validate a license key on a server, use the `/v1/license-keys/validate` > endpoint instead. # Get Order Source: https://polar.sh/docs/api-reference/customer-portal/orders/get get /v1/customer-portal/orders/{id} Get an order by ID for the authenticated customer. # Get Order Invoice Source: https://polar.sh/docs/api-reference/customer-portal/orders/get-invoice get /v1/customer-portal/orders/{id}/invoice Get an order's invoice data. The invoice must be generated first before it can be retrieved. You should call the [`POST /v1/customer-portal/orders/{id}/invoice`](/api-reference/customer-portal/orders/post-invoice) endpoint to generate the invoice. If the invoice is not generated, you will receive a `404` error. # List Orders Source: https://polar.sh/docs/api-reference/customer-portal/orders/list get /v1/customer-portal/orders/ List orders of the authenticated customer. # Update Order Source: https://polar.sh/docs/api-reference/customer-portal/orders/patch patch /v1/customer-portal/orders/{id} Update an order for the authenticated customer. # Generate Order Invoice Source: https://polar.sh/docs/api-reference/customer-portal/orders/post-invoice post /v1/customer-portal/orders/{id}/invoice Trigger generation of an order's invoice. Once the invoice is generated, it's permanent and cannot be modified. Make sure the billing details (name and address) are correct before generating the invoice. You can update them before generating the invoice by calling the [`PATCH /v1/customer-portal/orders/{id}`](/api-reference/customer-portal/orders/patch) endpoint. After successfully calling this endpoint, you get a `202` response, meaning the generation of the invoice has been scheduled. It usually only takes a few seconds before you can retrieve the invoice using the [`GET /v1/customer-portal/orders/{id}/invoice`](/api-reference/customer-portal/orders/get-invoice) endpoint. If you want a reliable notification when the invoice is ready, you can listen to the [`order.updated`](/api-reference/webhooks/order.updated) webhook and check the [`is_invoice_generated` field](/api-reference/webhooks/order.updated#schema-data-is-invoice-generated). # Assign Seat Source: https://polar.sh/docs/api-reference/customer-portal/seats/assign post /v1/customer-portal/seats # List Seats Source: https://polar.sh/docs/api-reference/customer-portal/seats/list get /v1/customer-portal/seats **Scopes**: `customer_portal:read` `customer_portal:write` # List Claimed Subscriptions Source: https://polar.sh/docs/api-reference/customer-portal/seats/list-subscriptions get /v1/customer-portal/seats/subscriptions List all subscriptions where the authenticated customer has claimed a seat. **Scopes**: `customer_portal:read` `customer_portal:write` # Resend Invitation Source: https://polar.sh/docs/api-reference/customer-portal/seats/resend post /v1/customer-portal/seats/{seat_id}/resend # Revoke Seat Source: https://polar.sh/docs/api-reference/customer-portal/seats/revoke delete /v1/customer-portal/seats/{seat_id} # Create Customer Session Source: https://polar.sh/docs/api-reference/customer-portal/sessions/create post /v1/customer-sessions/ Create a customer session. For organizations with `member_model_enabled`, this will automatically create a member session for the owner member of the customer. **Scopes**: `customer_sessions:write` # Cancel Subscription Source: https://polar.sh/docs/api-reference/customer-portal/subscriptions/cancel delete /v1/customer-portal/subscriptions/{id} Cancel a subscription of the authenticated customer. **Scopes**: `customer_portal:write` # Get Subscription Source: https://polar.sh/docs/api-reference/customer-portal/subscriptions/get get /v1/customer-portal/subscriptions/{id} Get a subscription for the authenticated customer. **Scopes**: `customer_portal:read` `customer_portal:write` # List Subscriptions Source: https://polar.sh/docs/api-reference/customer-portal/subscriptions/list get /v1/customer-portal/subscriptions/ List subscriptions of the authenticated customer. **Scopes**: `customer_portal:read` `customer_portal:write` # Update Subscription Source: https://polar.sh/docs/api-reference/customer-portal/subscriptions/update patch /v1/customer-portal/subscriptions/{id} Update a subscription of the authenticated customer. **Scopes**: `customer_portal:write` # Assign Seat Source: https://polar.sh/docs/api-reference/customer-seats/assign post /v1/customer-seats **Scopes**: `customer_seats:write` # Claim Seat Source: https://polar.sh/docs/api-reference/customer-seats/claim post /v1/customer-seats/claim # Get Claim Info Source: https://polar.sh/docs/api-reference/customer-seats/get-claim-info get /v1/customer-seats/claim/{invitation_token} # List Seats Source: https://polar.sh/docs/api-reference/customer-seats/list get /v1/customer-seats **Scopes**: `customer_seats:write` # Resend Invitation Source: https://polar.sh/docs/api-reference/customer-seats/resend post /v1/customer-seats/{seat_id}/resend **Scopes**: `customer_seats:write` # Revoke Seat Source: https://polar.sh/docs/api-reference/customer-seats/revoke delete /v1/customer-seats/{seat_id} **Scopes**: `customer_seats:write` # Create Customer Source: https://polar.sh/docs/api-reference/customers/create post /v1/customers/ Create a customer. **Scopes**: `customers:write` # Delete Customer Source: https://polar.sh/docs/api-reference/customers/delete delete /v1/customers/{id} Delete a customer. This action cannot be undone and will immediately: - Cancel any active subscriptions for the customer - Revoke all their benefits - Clear any `external_id` Use it only in the context of deleting a user within your own service. Otherwise, use more granular API endpoints to cancel a specific subscription or revoke certain benefits. Note: The customers information will nonetheless be retained for historic orders and subscriptions. **Scopes**: `customers:write` # Delete Customer by External ID Source: https://polar.sh/docs/api-reference/customers/delete-external delete /v1/customers/external/{external_id} Delete a customer by external ID. Immediately cancels any active subscriptions and revokes any active benefits. **Scopes**: `customers:write` # Get Customer Source: https://polar.sh/docs/api-reference/customers/get get /v1/customers/{id} Get a customer by ID. **Scopes**: `customers:read` `customers:write` # Get Customer by External ID Source: https://polar.sh/docs/api-reference/customers/get-external get /v1/customers/external/{external_id} Get a customer by external ID. **Scopes**: `customers:read` `customers:write` # List Customers Source: https://polar.sh/docs/api-reference/customers/list get /v1/customers/ List customers. **Scopes**: `customers:read` `customers:write` # Get Customer State Source: https://polar.sh/docs/api-reference/customers/state get /v1/customers/{id}/state Get a customer state by ID. The customer state includes information about the customer's active subscriptions and benefits. It's the ideal endpoint to use when you need to get a full overview of a customer's status. **Scopes**: `customers:read` `customers:write` # Get Customer State by External ID Source: https://polar.sh/docs/api-reference/customers/state-external get /v1/customers/external/{external_id}/state Get a customer state by external ID. The customer state includes information about the customer's active subscriptions and benefits. It's the ideal endpoint to use when you need to get a full overview of a customer's status. **Scopes**: `customers:read` `customers:write` # Update Customer Source: https://polar.sh/docs/api-reference/customers/update patch /v1/customers/{id} Update a customer. **Scopes**: `customers:write` # Update Customer by External ID Source: https://polar.sh/docs/api-reference/customers/update-external patch /v1/customers/external/{external_id} Update a customer by external ID. **Scopes**: `customers:write` # Create Discount Source: https://polar.sh/docs/api-reference/discounts/create post /v1/discounts/ Create a discount. **Scopes**: `discounts:write` # Delete Discount Source: https://polar.sh/docs/api-reference/discounts/delete delete /v1/discounts/{id} Delete a discount. **Scopes**: `discounts:write` # Get Discount Source: https://polar.sh/docs/api-reference/discounts/get get /v1/discounts/{id} Get a discount by ID. **Scopes**: `discounts:read` `discounts:write` # List Discounts Source: https://polar.sh/docs/api-reference/discounts/list get /v1/discounts/ List discounts. **Scopes**: `discounts:read` `discounts:write` # Update Discount Source: https://polar.sh/docs/api-reference/discounts/update patch /v1/discounts/{id} Update a discount. **Scopes**: `discounts:write` # Get Event Source: https://polar.sh/docs/api-reference/events/get get /v1/events/{id} Get an event by ID. **Scopes**: `events:read` `events:write` # Ingest Events Source: https://polar.sh/docs/api-reference/events/ingest post /v1/events/ingest Ingest batch of events. **Scopes**: `events:write` # List Events Source: https://polar.sh/docs/api-reference/events/list get /v1/events/ List events. **Scopes**: `events:read` `events:write` # Complete File Upload Source: https://polar.sh/docs/api-reference/files/complete-upload post /v1/files/{id}/uploaded Complete a file upload. **Scopes**: `files:write` # Create File Source: https://polar.sh/docs/api-reference/files/create post /v1/files/ Create a file. **Scopes**: `files:write` # Delete File Source: https://polar.sh/docs/api-reference/files/delete delete /v1/files/{id} Delete a file. **Scopes**: `files:write` # List Files Source: https://polar.sh/docs/api-reference/files/list get /v1/files/ List files. **Scopes**: `files:read` `files:write` # Update File Source: https://polar.sh/docs/api-reference/files/update patch /v1/files/{id} Update a file. **Scopes**: `files:write` # API Overview Source: https://polar.sh/docs/api-reference/introduction Base URLs, authentication, pagination, rate limits, and the difference between the Core API and the Customer Portal API `https://api.polar.sh/v1` `https://sandbox-api.polar.sh/v1` Use an **Organization Access Token (OAT)** in the `Authorization: Bearer` header Use a **Customer Access Token** created via `/v1/customer-sessions/` ## Base URLs | Environment | Base URL | Purpose | | ----------- | --------------------------------- | ------------------------------- | | Production | `https://api.polar.sh/v1` | Real customers & live payments | | Sandbox | `https://sandbox-api.polar.sh/v1` | Safe testing & integration work | The sandbox environment is fully isolatedโ€”data, users, tokens, and organizations created there do not affect production. Create separate tokens in each environment. Read more: [Sandbox Environment](/integrate/sandbox) ## Authentication ### Organization Access Tokens (OAT) Use an **OAT** to act on behalf of your organization (manage products, prices, checkouts, orders, subscriptions, benefits, etc.). ```http theme={null} Authorization: Bearer polar_oat_xxxxxxxxxxxxxxxxx ``` Create OATs in your organization settings. See: [Organization Access Tokens](/integrate/oat) Never expose an OAT in client-side code, public repos, or logs. If leaked, it will be revoked automatically by our secret scanning integrations. ### Customer Access Tokens Do **not** use OATs in the browser. For customer-facing flows, [generate a **Customer Session**](/api-reference/customer-portal/sessions/create) server-side, then use the returned **customer access token** with the **Customer Portal API** to let a signed-in customer view their own orders, subscriptions, and benefits. ## Core API vs Customer Portal API | Aspect | Core API | Customer Portal API | | -------------------- | ------------------------------------------------------------------------ | ---------------------------------------------- | | Audience | Your server / backend | One of your customer | | Auth Type | Organization Access Token (OAT) | Customer Access Token | | Scope | Full org resources (products, orders, subscriptions, benefits, checkout) | Only the authenticated customerโ€™s data | | Typical Use | Admin dashboards, internal tools, automation, provisioning | Building a custom customer portal or gated app | | Token Creation | Via dashboard (manual) | Via `/v1/customer-sessions/` (server-side) | | Sensitive Operations | Yes (create/update products, issue refunds, etc.) | No (read/update only what the customer owns) | The Customer Portal API is a *restricted* surface designed for safe exposure in user-facing contexts (after exchanging a session). It cannot perform privileged org-level mutations like creating products or issuing refunds. ## Quick Examples ```bash curl (Production - Core API) theme={null} curl https://api.polar.sh/v1/products/ \ -H "Authorization: Bearer $POLAR_OAT" \ -H "Accept: application/json" ``` ```bash curl (Sandbox - Core API) theme={null} curl https://sandbox-api.polar.sh/v1/products/ \ -H "Authorization: Bearer $POLAR_OAT_SANDBOX" \ -H "Accept: application/json" ``` ```bash curl (Customer Portal API) theme={null} curl https://api.polar.sh/v1/customer-portal/orders/ \ -H "Authorization: Bearer $POLAR_CUSTOMER_TOKEN" \ -H "Accept: application/json" ``` ## Using SDKs All official SDKs accept a `server` parameter for sandbox usage: ```ts TypeScript theme={null} import { Polar } from "@polar-sh/sdk"; const polar = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN!, server: "sandbox", // omit or use 'production' for live }); ``` ```py Python theme={null} from polar import Polar client = Polar( access_token=os.environ["POLAR_ACCESS_TOKEN"], server="sandbox", ) ``` ```go Go theme={null} s := polargo.New( polargo.WithServer("sandbox"), polargo.WithSecurity(os.Getenv("POLAR_ACCESS_TOKEN")), ) ``` ```php PHP theme={null} $sdk = Polar\Polar::builder() ->setServer('sandbox') ->setSecurity(getenv('POLAR_ACCESS_TOKEN')) ->build(); ``` ## Pagination List endpoints in the Polar API support pagination to help you efficiently retrieve large datasets. Use the `page` and `limit` query parameters to control pagination. ### Query Parameters | Parameter | Type | Default | Max | Description | | --------- | ------- | ------- | ----- | ------------------------------------------------ | | `page` | integer | `1` | - | Page number, starting from 1 | | `limit` | integer | `10` | `100` | Number of items to return per page (window size) | The `page` parameter works as a window offset. For example, `page=2&limit=10` means the API will skip the first 10 elements and return the next 10. ### Response Format All paginated responses include a `pagination` object with metadata about the current page and total results: | Field | Type | Description | | ------------- | ------- | ---------------------------------------------------------------- | | `total_count` | integer | Total number of items matching your query across all pages | | `max_page` | integer | Total number of pages available, given the current `limit` value | ### Example Let's say you want to fetch products with a limit of 100 items per page: ```bash Request theme={null} curl https://api.polar.sh/v1/products/?page=1&limit=100 \ -H "Authorization: Bearer $POLAR_OAT" \ -H "Accept: application/json" ``` ```json Response theme={null} { "items": [ { "id": "...", "name": "Product 1", ... }, ... ], "pagination": { "total_count": 250, "max_page": 3 } } ``` In this example: * `total_count=250` indicates there are 250 total products * `limit=100` means each page contains up to 100 products * `max_page=3` means you need to make 3 requests to retrieve all products (pages 1, 2, and 3) To retrieve all pages, increment the `page` parameter from `1` to `max_page`. Our SDKs provide built-in pagination helpers to automatically iterate through all pages. ## Rate Limits Polar API has rate limits to ensure fair usage and maintain performance. The limits are as follows: * **300 requests per minute** per organization/customer or OAuth2 Client. * **3 requests per second** for unauthenticated license key [validation](/api-reference/customer-portal/license-keys/validate), [activation](/api-reference/customer-portal/license-keys/activate), and [deactivation](/api-reference/customer-portal/license-keys/deactivate) endpoints. If you exceed the rate limit, you will receive a `429 Too Many Requests` response. The response will include a `Retry-After` header indicating how long you should wait before making another request. Organizations requiring higher rate limits for production workloads may contact our support team to discuss elevated limits. # Activate License Key Source: https://polar.sh/docs/api-reference/license-keys/activate post /v1/license-keys/activate Activate a license key instance. **Scopes**: `license_keys:write` # Deactivate License Key Source: https://polar.sh/docs/api-reference/license-keys/deactivate post /v1/license-keys/deactivate Deactivate a license key instance. **Scopes**: `license_keys:write` # Get License Key Source: https://polar.sh/docs/api-reference/license-keys/get get /v1/license-keys/{id} Get a license key. **Scopes**: `license_keys:read` `license_keys:write` # Get Activation Source: https://polar.sh/docs/api-reference/license-keys/get-activation get /v1/license-keys/{id}/activations/{activation_id} Get a license key activation. **Scopes**: `license_keys:read` `license_keys:write` # List License Keys Source: https://polar.sh/docs/api-reference/license-keys/list get /v1/license-keys/ Get license keys connected to the given organization & filters. **Scopes**: `license_keys:read` `license_keys:write` # Update License Key Source: https://polar.sh/docs/api-reference/license-keys/update patch /v1/license-keys/{id} Update a license key. **Scopes**: `license_keys:write` # Validate License Key Source: https://polar.sh/docs/api-reference/license-keys/validate post /v1/license-keys/validate Validate a license key. **Scopes**: `license_keys:write` # Create Meter Source: https://polar.sh/docs/api-reference/meters/create post /v1/meters/ Create a meter. **Scopes**: `meters:write` # Get Meter Source: https://polar.sh/docs/api-reference/meters/get get /v1/meters/{id} Get a meter by ID. **Scopes**: `meters:read` `meters:write` # Get Meter Quantities Source: https://polar.sh/docs/api-reference/meters/get-quantities get /v1/meters/{id}/quantities Get quantities of a meter over a time period. **Scopes**: `meters:read` `meters:write` # List Meters Source: https://polar.sh/docs/api-reference/meters/list get /v1/meters/ List meters. **Scopes**: `meters:read` `meters:write` # Update Meter Source: https://polar.sh/docs/api-reference/meters/update patch /v1/meters/{id} Update a meter. **Scopes**: `meters:write` # Get Metrics Source: https://polar.sh/docs/api-reference/metrics/get get /v1/metrics/ Get metrics about your orders and subscriptions. Currency values are output in cents. **Scopes**: `metrics:read` # Get Metrics Limits Source: https://polar.sh/docs/api-reference/metrics/get-limits get /v1/metrics/limits Get the interval limits for the metrics endpoint. **Scopes**: `metrics:read` # Authorize Source: https://polar.sh/docs/api-reference/oauth2/connect/authorize get /v1/oauth2/authorize # Get User Info Source: https://polar.sh/docs/api-reference/oauth2/connect/get-user-info get /v1/oauth2/userinfo Get information about the authenticated user. # Introspect Token Source: https://polar.sh/docs/api-reference/oauth2/connect/introspect-token post /v1/oauth2/introspect Get information about an access token. # Request Token Source: https://polar.sh/docs/api-reference/oauth2/connect/request-token post /v1/oauth2/token Request an access token using a valid grant. # Revoke Token Source: https://polar.sh/docs/api-reference/oauth2/connect/revoke-token post /v1/oauth2/revoke Revoke an access token or a refresh token. # Get Order Source: https://polar.sh/docs/api-reference/orders/get get /v1/orders/{id} Get an order by ID. **Scopes**: `orders:read` # Get Order Invoice Source: https://polar.sh/docs/api-reference/orders/get-invoice get /v1/orders/{id}/invoice Get an order's invoice data. **Scopes**: `orders:read` The invoice must be generated first before it can be retrieved. You should call the [`POST /v1/orders/{id}/invoice`](/api-reference/orders/post-invoice) endpoint to generate the invoice. If the invoice is not generated, you will receive a `404` error. # List Orders Source: https://polar.sh/docs/api-reference/orders/list get /v1/orders/ List orders. **Scopes**: `orders:read` # Update Order Source: https://polar.sh/docs/api-reference/orders/patch patch /v1/orders/{id} Update an order. **Scopes**: `orders:write` # Generate Order Invoice Source: https://polar.sh/docs/api-reference/orders/post-invoice post /v1/orders/{id}/invoice Trigger generation of an order's invoice. **Scopes**: `orders:read` Once the invoice is generated, it's permanent and cannot be modified. Make sure the billing details (name and address) are correct before generating the invoice. You can update them before generating the invoice by calling the [`PATCH /v1/orders/{id}`](/api-reference/orders/patch) endpoint. After successfully calling this endpoint, you get a `202` response, meaning the generation of the invoice has been scheduled. It usually only takes a few seconds before you can retrieve the invoice using the [`GET /v1/orders/{id} /invoice`](/api-reference/orders/get-invoice) endpoint. If you want a reliable notification when the invoice is ready, you can listen to the [`order.updated`](/api-reference/webhooks/order.updated) webhook and check the [`is_invoice_generated` field](/api-reference/webhooks/order.updated#schema-data-is-invoice-generated). # Create Organization Source: https://polar.sh/docs/api-reference/organizations/create post /v1/organizations/ Create an organization. **Scopes**: `organizations:write` # Get Organization Source: https://polar.sh/docs/api-reference/organizations/get GET /v1/organizations/{id} Get an organization by ID. **Scopes**: `organizations:read` `organizations:write` # List Organizations Source: https://polar.sh/docs/api-reference/organizations/list get /v1/organizations/ List organizations. **Scopes**: `organizations:read` `organizations:write` # Update Organization Source: https://polar.sh/docs/api-reference/organizations/update patch /v1/organizations/{id} Update an organization. **Scopes**: `organizations:write` # Create Product Source: https://polar.sh/docs/api-reference/products/create post /v1/products/ Create a product. **Scopes**: `products:write` # Get Product Source: https://polar.sh/docs/api-reference/products/get get /v1/products/{id} Get a product by ID. **Scopes**: `products:read` `products:write` # List Products Source: https://polar.sh/docs/api-reference/products/list get /v1/products/ List products. **Scopes**: `products:read` `products:write` # Update Product Source: https://polar.sh/docs/api-reference/products/update patch /v1/products/{id} Update a product. **Scopes**: `products:write` # Update Product Benefits Source: https://polar.sh/docs/api-reference/products/update-benefits post /v1/products/{id}/benefits Update benefits granted by a product. **Scopes**: `products:write` # Create Refund Source: https://polar.sh/docs/api-reference/refunds/create post /v1/refunds/ Create a refund. **Scopes**: `refunds:write` # List Refunds Source: https://polar.sh/docs/api-reference/refunds/list get /v1/refunds/ List refunds. **Scopes**: `refunds:read` `refunds:write` # Create Subscription Source: https://polar.sh/docs/api-reference/subscriptions/create post /v1/subscriptions/ Create a subscription programmatically. This endpoint only allows to create subscription on free products. For paid products, use the checkout flow. No initial order will be created and no confirmation email will be sent. **Scopes**: `subscriptions:write` # Get Subscription Source: https://polar.sh/docs/api-reference/subscriptions/get get /v1/subscriptions/{id} Get a subscription by ID. **Scopes**: `subscriptions:read` `subscriptions:write` # List Subscriptions Source: https://polar.sh/docs/api-reference/subscriptions/list get /v1/subscriptions/ List subscriptions. **Scopes**: `subscriptions:read` `subscriptions:write` # Revoke Subscription Source: https://polar.sh/docs/api-reference/subscriptions/revoke delete /v1/subscriptions/{id} Revoke a subscription, i.e cancel immediately. **Scopes**: `subscriptions:write` # Update Subscription Source: https://polar.sh/docs/api-reference/subscriptions/update patch /v1/subscriptions/{id} Update a subscription. **Scopes**: `subscriptions:write` # benefit.created Source: https://polar.sh/docs/api-reference/webhooks/benefit.created # benefit.updated Source: https://polar.sh/docs/api-reference/webhooks/benefit.updated # benefit_grant.created Source: https://polar.sh/docs/api-reference/webhooks/benefit_grant.created # benefit_grant.cycled Source: https://polar.sh/docs/api-reference/webhooks/benefit_grant.cycled # benefit_grant.revoked Source: https://polar.sh/docs/api-reference/webhooks/benefit_grant.revoked # benefit_grant.updated Source: https://polar.sh/docs/api-reference/webhooks/benefit_grant.updated # checkout.created Source: https://polar.sh/docs/api-reference/webhooks/checkout.created # checkout.updated Source: https://polar.sh/docs/api-reference/webhooks/checkout.updated # customer.created Source: https://polar.sh/docs/api-reference/webhooks/customer.created # customer.deleted Source: https://polar.sh/docs/api-reference/webhooks/customer.deleted # customer.state_changed Source: https://polar.sh/docs/api-reference/webhooks/customer.state_changed # customer.updated Source: https://polar.sh/docs/api-reference/webhooks/customer.updated # customer_seat.assigned Source: https://polar.sh/docs/api-reference/webhooks/customer_seat.assigned # customer_seat.claimed Source: https://polar.sh/docs/api-reference/webhooks/customer_seat.claimed # customer_seat.revoked Source: https://polar.sh/docs/api-reference/webhooks/customer_seat.revoked # Create Webhook Endpoint Source: https://polar.sh/docs/api-reference/webhooks/endpoints/create post /v1/webhooks/endpoints Create a webhook endpoint. **Scopes**: `webhooks:write` # Delete Webhook Endpoint Source: https://polar.sh/docs/api-reference/webhooks/endpoints/delete delete /v1/webhooks/endpoints/{id} Delete a webhook endpoint. **Scopes**: `webhooks:write` # Get Webhook Endpoint Source: https://polar.sh/docs/api-reference/webhooks/endpoints/get get /v1/webhooks/endpoints/{id} Get a webhook endpoint by ID. **Scopes**: `webhooks:read` `webhooks:write` # List Webhook Endpoints Source: https://polar.sh/docs/api-reference/webhooks/endpoints/list get /v1/webhooks/endpoints List webhook endpoints. **Scopes**: `webhooks:read` `webhooks:write` # Update Webhook Endpoint Source: https://polar.sh/docs/api-reference/webhooks/endpoints/update patch /v1/webhooks/endpoints/{id} Update a webhook endpoint. **Scopes**: `webhooks:write` # order.created Source: https://polar.sh/docs/api-reference/webhooks/order.created # order.paid Source: https://polar.sh/docs/api-reference/webhooks/order.paid # order.refunded Source: https://polar.sh/docs/api-reference/webhooks/order.refunded # order.updated Source: https://polar.sh/docs/api-reference/webhooks/order.updated # organization.updated Source: https://polar.sh/docs/api-reference/webhooks/organization.updated # product.created Source: https://polar.sh/docs/api-reference/webhooks/product.created # product.updated Source: https://polar.sh/docs/api-reference/webhooks/product.updated # refund.created Source: https://polar.sh/docs/api-reference/webhooks/refund.created # refund.updated Source: https://polar.sh/docs/api-reference/webhooks/refund.updated # subscription.active Source: https://polar.sh/docs/api-reference/webhooks/subscription.active # subscription.canceled Source: https://polar.sh/docs/api-reference/webhooks/subscription.canceled # subscription.created Source: https://polar.sh/docs/api-reference/webhooks/subscription.created # subscription.revoked Source: https://polar.sh/docs/api-reference/webhooks/subscription.revoked # subscription.uncanceled Source: https://polar.sh/docs/api-reference/webhooks/subscription.uncanceled # subscription.updated Source: https://polar.sh/docs/api-reference/webhooks/subscription.updated # API Changelog Source: https://polar.sh/docs/changelog/api Stay up to date with the latest changes, improvements and deprecations to the Polar API. ## Checkout API and Customer Session API changes To be more consistent across our API, we've renamed `customer_external_id` field to `external_customer_id` in the Checkout API and Customer Session API. * **Deprecated**: `customer_external_id` field in the Checkout API and Customer Session API. Use `external_customer_id` instead. ## Benefit metadata in Customer State The customer state now includes the [benefit metadata](/api-reference/customers/state#response-benefit-metadata) in the `granted_benefits` list. ## Webhook API endpoints are now documented The API endpoints for managing webhooks are now documented in the API reference, and fully supported in our SDK. [Read more](/api-reference/webhooks/endpoints/create) ## Rate limits To ensure fair usage and maintain performance, we've introduced rate limits for the API. The limits are as follows: * **100 requests per second** per IP address. ## Order invoice generation and retrieval Until now, the invoice was generated automatically when the order was created, allowing you to call [`GET /v1/orders/{id}/invoice`](/api-reference/orders/get-invoice) and [`GET /v1/customer-portal/orders/{id}/invoice`](/api-reference/customer-portal/orders/get-invoice) endpoints without any prior action. We now require you to explicitly generate the invoice by calling the [`POST /v1/orders/{id}/invoice`](/api-reference/orders/post-invoice) or [`POST /v1/customer-portal/orders/{id}/invoice`](/api-reference/customer-portal/orders/post-invoice) endpoints. This change allows us to better handle the invoice generation process, and to allow the customer to change the billing details (name and address) before generating the invoice. This can be done through the [`PATCH /v1/orders/{id}`](/api-reference/orders/patch) or [`PATCH /v1/customer-portal/orders/{id}`](/api-reference/customer-portal/orders/patch) endpoints. ## Benefit metadata support and floating point numbers in metadata * **Added**: Benefits now support [metadata](/api-reference/benefits/create#body-metadata). * **Added**: Metadata values now support floating-point numbers. Before, only strings, integers and booleans were supported. ## Checkout amount fields changes and depreciations To be more consistent with the [Order schema changes](#2025-03-14), we've made some changes to the field related to amounts in the Checkout schema. * **Added**: [`checkout.discount_amount`](/api-reference/checkouts/get-session#response-discount-amount). * **Added**: [`checkout.net_amount`](/api-reference/checkouts/get-session#response-net-amount). * **Deprecated**: `checkout.subtotal_amount`, use [`checkout.net_amount`](/api-reference/checkouts/get-session#response-net-amount) instead. ## New order status and webhooks Until now, Polar only kept track of fully processed, **paid** orders. To help you keep track of the order lifecycle, we've added a new status `pending`, which is a transitive state meaning the order is created but not paid yet. In most cases, the order will transition from `pending` to `paid` in a short time. * When receiving `order.created` event, the order status might not be `paid`. * **Added**: [`order.updated`](/api-reference/webhooks/order.updated) webhook, sent when the order status changes or when it's partially or fully refunded. * **Added**: [`order.paid`](/api-reference/webhooks/order.paid) webhook, sent when the order is fully processed and paid. * **Added**: [`Order.paid`](/api-reference/orders/get#response-paid) property to the order schema. If you were relying on the `order.created` webhook to keep track of succesful orders, we recommend you to switch to `order.paid`. ## Subscriptions and Orders schema changes To prepare our next move to support usage-based billing, we've made some changes to the [`Subscription`](/api-reference/subscriptions/get) and [`Order`](/api-reference/orders/get) schemas. The main reason behind those is that we need to support multiple prices and items in a single subscription or order. * **Deprecated**: `Subscription.price_id` and `Subscription.price`. Use the `Subscription.prices` array instead. * **Deprecated**: `Order.product_price_id` and `Order.product_price`. Use the `Order.items` array instead. * **Deprecated**: `Order.amount`. Use the `Order.net_amount` instead. It has the same value and meaning, but the new name is more explicit. * **Added**: `Order.subtotal_amount`, `Order.discount_amount`, and `Order.total_amount` fields to provide a more detailed breakdown of the order amount. # Product Updates Source: https://polar.sh/docs/changelog/recent Stay up to date with the latest changes and improvements to Polar. ## Ability to update subscription to an updated price of the product Merchants can now [update existing subscriptions](https://polar.sh/docs/api-reference/subscriptions/update#subscriptionupdateproduct) from archived pricing schemes to current ones within the same product. * Enables migration from grandfathered pricing to current pricing schemes * Prorations calculated using active subscription prices * In the dashboard, a small badge **Upgrade pricing** will indicate that you can update to the same product, but with the new pricing scheme. ## New IP ranges From **October 27th, 2025**, [new IP ranges](https://polar.sh/docs/integrate/webhooks/delivery#ip-allowlist) will be added. ## Improved Subscription Cancellation Flow [Benefits](https://polar.sh/docs/features/benefits/introduction) attached to the subscription [are now automatically revoked](https://github.com/polarsource/polar/pull/7271) when the subscription is canceled. ## Ability to specify External ID [External ID can now be specified](https://github.com/polarsource/polar/pull/7275) during creation of a customer via the dashboard. ## Ability to set Return URL for Checkouts and Customer Portal A Return URL can now be set while generating a [checkout session](https://polar.sh/docs/api-reference/checkouts/create-session#body-return-url-one-of-0) or a [customer portal session](https://polar.sh/docs/api-reference/customer-portal/sessions/create#body-one-of-0-return-url-one-of-0). This allows you to preserve the context for the end users who visit either if they wish to go back to the application. ## Ability to search Customers via their External ID The [List Customers API](https://polar.sh/docs/api-reference/customers/list) now accepts Customer's External ID [in the **query** parameter](https://polar.sh/docs/api-reference/customers/list#parameter-one-of-0). ## Ability to disable automatic customer emails Via the organization settings, you can now disable the emails we automatically send to customers on certain events. This gives you the ability to own the communications with the customers. ## Launched In-Product Chat Support for Merchants We're excited to introduce a chat widget to the Polar dashboard, making it easier than ever for merchants to get help directly within the product. ## Improved Checkout Flow for Invalid Discount Code Previously, if you entered an invalid discount code during checkout, you couldn't continue even after removing the code. Now, clearing the discount input lets you proceed smoothly with the checkout process. ## Improved Customer Portal Rate Limits We've made several improvements to the Customer Portal to handle authentication rate limits more gracefully: * The portal now clearly shows 401 and 429 errors on the OTP (one-time password) page. * If you hit a 429 (too many requests), you'll be redirected to a clear `/too-many-requests` page. ## Launched Subscription Trials You can now offer [trial periods](/docs/features/trials) for new subscriptions! This highly requested feature allows you to let customers experience your product before their first payment is due. Trials can be configured in both the dashboard and API when creating or updating subscription products. ## Always display taxes line item in the checkout We've improved our checkout experience to always display taxes (vs lazy loading them on country selection), making charges more transparent for your customers regardless of whether taxes apply to their purchase. ## Do not calculate taxes on free or zero-amount orders Orders with a zero amount (such as promotional products) will no longer have taxes calculated, resulting in a clearer and more accurate order summary for your customers. ## Add confirmation modal for deleting discounts When deleting a discount, you'll now see a confirmation modal to help prevent accidental deletions and provide extra clarity on the impact of your actions. ## Fix infinite rendering loop with date picker Resolved a bug where selecting dates in the date picker could cause an infinite rendering loop, improving reliability for date-related forms. ## Require opt-in if you will be charged immediately Users must now explicitly confirm immediate charges or credits when switching subscription intervals, with the UI providing clearer, contextual explanations of invoicing outcomes. ## Check for `expires_at` when activating license keys License key activation now correctly checks the `expires_at` date, ensuring that only valid, non-expired license keys can be activated. ## Fix customer state for trialing subscriptions The [Customer State API](/docs/api-reference/customers/state) now properly handles customers with `trialing` subscriptions, so your integrations and dashboards always show an accurate subscription status. ## Improved preview of next invoice in Customer Portal We've enhanced the Customer Portal to provide a clearer and more accurate preview of your next invoice. The overview now updates automatically after subscription changes, and you can preview upcoming charges with all relevant taxes and discounts included. ## Cancellation metrics We've added detailed cancellation metrics, giving you clearer insights into subscription cancellations and their impact on your business performance. ## Webhooks payload now includes timestamp We've updated our webhooks server implementation to [include a timestamp in each payload](https://github.com/polarsource/polar/pull/6770), in line with the Standard Webhooks specification. This change ensures that every webhook payload contains precise event timing, making it easier to trace and debug webhook deliveries, and to meet integration requirements for external platforms. ## Meter management improvements We've made it easier to manage your meters with new UI functionality for archiving and unarchiving meters directly from the dashboard. You can now archive meters that are no longer needed, which helps keep your meter list organized. Archived meters can be unarchived if you need to use them again. Note that meters cannot be archived if they are still attached to active products or referenced by active benefits. ## Metrics accuracy improvements We've improved the accuracy of our metrics by excluding unpaid orders from all calculations. Previously, orders in pending status were included in metrics, which could lead to inflated numbers. Now, only successfully paid and refunded orders are included in metrics calculations, giving you a more accurate view of your actual business performance. ## Enhanced customer email branding We've improved the branding of emails sent to your customers by using organization-specific 'From' and 'Reply-to' addresses. Customer emails now appear to come from your organization (e.g., "YourOrg (via Polar)") with replies directed to your organization's email address, providing a more professional and branded experience for your customers. ## Update subscription discount We've added the ability to update the discount on a subscription. This allows you to add, remove, or change the discount applied to a subscription at any time. This feature is both available through the [API](/api-reference/subscriptions/update) and the dashboard. ## Payout Reverse Invoices We've added the ability to generate reverse invoices for payouts directly from the Payouts page. This feature allows you to easily create an invoice that details the sales made on your behalf, minus our fees. [Read more](/features/finance/payouts#reverse-invoices) ## Business Purchase Option on Checkout We've added a new "I'm purchasing as a business" checkbox to the Checkout flow. When selected, customers are required to provide their business billing name and complete billing address. ## Enhanced Attribution for Checkout Links We've added support for `reference_id` and UTM parameters (`utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`) as query parameters for Checkout Links. These parameters are automatically stored in the Checkout metadata, allowing you to track the source of your traffic and conversions more effectively. [Read more](/features/checkout/links#store-attribution-and-reference-metadata) ## Checkouts and payments insights We've added a new **Checkouts** tab under the **Sales**, where you can review all the checkout sessions, successful or not. You can filter them by customer email, status, and product. You can also see the payment attempts for each checkout session, including the reason for any failed or declined payments. The payment attempts information is also available on each order. Besides, we've also added new analytics around checkouts: total number of checkouts, successful checkouts, and conversion rate. ## Zapier integration officially launched We're excited to announce the official launch of our [Zapier integration](https://zapier.com/apps/polar/integrations)! Get started now and connect Polar to 2,000+ other web services. We've focused on **triggers** (webhooks) for now, so you can react to events in Polar and trigger actions in other apps. Need to perform actions in Polar? Tell us about your use case [here](https://github.com/orgs/polarsource/discussions/new?category=integrations\&labels=integrations%2Fzapier) and we'll consider adding more actions in the future. ## Customer State Maybe one of our neatest features to date! Customer State is a concept allowing you to query for the current state of a customer, including their **active subscriptions** and **granted [benefits](/features/benefits/introduction)**, in a single [API call](/api-reference/customers/state-external) or single [webhook event](/api-reference/webhooks/customer.state_changed). Combined with the [External ID](/features/customer-management#external-id) feature, you can get up-and-running in minutes. [Read more](/integrate/customer-state) ## Better Auth Plugin Integrating authentication and billing for your users has never been easier. [Better Auth](https://www.better-auth.com/) is an open source authentication framework for TypeScript that is quickly becoming a favorite amongst developers. Today, we're thrilled to have shipped a Polar plugin for Better Auth - in collaboration with them. Checkout our [integration guide](/integrate/sdk/adapters/better-auth). ## Customer External ID We've added support for an `external_id` field on Customers. We believe this will greatly simplify the reconciliation process between your system and Polar. Previously, the recommended way to reconcile with your users was to use `metadata`. However, this was not always the most convenient method, especially if you needed to fetch a Customer from our API. With `external_id`, you can now fetch a Customer directly by their external ID through dedicated `GET`, `PATCH`, and `DELETE` endpoints. You don't even need to store Polar's internal ID in your system anymore! [Read more](/features/customer-management#external-id) Of course, you can also directly preset `external_customer_id` when creating a Checkout Session, and it will automatically be set on the newly created Customer after a successful checkout. [Read more](/features/checkout/session#external-customer-id) ## Polar's take on Product variants We've released big changes to how we handle products and pricing, allowing us to support a unique approach to what the industry typically calls **variants** ๐Ÿ”ฅ We believe having a single product with multiple pricing models and benefits adds unneccessary complexity to the user and to the API. Instead, we chose to treat everything as a product, giving you maximum flexibility about the pricing and benefits you want to offer. Thus, we introduce support for **multiple products** at checkout, allowing customers to switch between them before purchasing. Typically, you can offer a monthly and a yearly product, with specific pricing and benefits for each. This is available right now using the [Checkout Session API](/features/checkout/session) and [Checkout Links](/features/checkout/links). ### Depreciations * Products can no longer have both a monthly and yearly pricing. Existing products still work, but you'll see a warning like this when trying to edit their pricing: ### API changes * The `product_id` and `product_price_id` fields are deprecated in the [Checkout Session API](/api-reference/checkouts/create-session). You should now use the `products` field to specify the products you want to include in the checkout. * The `type` and `recurring_interval` fields on `ProductPrice` are deprecated. `recurring_interval` is now set directly on `Product`. # Analytics Source: https://polar.sh/docs/features/analytics ## Sales Metrics Polar offers a professional metrics dashboard out of the box. So you can stay focused on increasing revenue vs. how to measure it. **Missing any metrics?** [Let us know so we can add it.](https://github.com/orgs/polarsource/discussions/categories/feature-requests) ### Filters You can easily slice and dice metrics with the filters below. ### Period Change the time period in the X-axis to one of: * Yearly * Monthly * Weekly * Daily * Hourly ### Timeframe You can choose a date range to view all metrics for. ### Product By default metrics reflect the total across all products. However, you can specify individual products or subscription tiers to filter metrics by. ## Metrics * **Revenue**: How much revenue you've earned before fees. * **Orders**: How many product sales and subscription payments have been made. * **Average Order Value (AOV)**: The average earning per order, i.e. revenue / orders. * **One-Time Products**: Amount of products sold. * **One-Time Products Revenue**: Amount of revenue earned from products. * **New Subscriptions**: Amount of new subscriptions. * **New Subscription Revenue**: Amount of revenue earned from new subscriptions. * **Renewed Subscriptions**: Amount of renewed subscriptions. * **Renewed Subscription Revenue**: Amount of revenue earned from renewed subscriptions. * **Active Subscriptions**: Amount of active subscriptions (new + renewed). * **Monthly Recurring Revenue (MRR)**: Amount of revenue earned from active subscriptions. * **Checkouts**: Number of created checkouts. * **Succeeded Checkouts**: Number of successful checkouts, i.e. checkouts that lead to a new order or subscription. * **Checkouts Conversion Rate**: The percentage of successful checkouts out of all created checkouts. ## Cost Metrics * **Costs**: How much costs you've incurred. * **Cumulative Costs**: How much costs you've incurred over time. * **Cost Per User**: The average cost per active user. * **Gross Margin**: The gross margin, i.e. revenue - costs. * **Gross Margin Percentage**: The gross margin percentage, i.e. gross margin / revenue. * **Net Cashflow**: The net cashflow, i.e. revenue - costs. * **Monthly Recurring Cost (MRC)**: The monthly recurring cost, i.e. costs / active subscriptions. * **Return on Investment (ROI)**: The return on investment, i.e. (revenue - costs) / costs. # Credits Benefit Source: https://polar.sh/docs/features/benefits/credits Create your own Credits benefit The Credits benefit allows you to credit a customer's Usage Meter balance. ## Crediting Usage Meter Balance The Credits benefit will credit a customer's Usage Meter balance at different points in time depending on the type of product purchased. ### Subscription Products The customer will be credited the amount of units specified in the benefit at the beginning of every subscription cycle period โ€” monthly or yearly. ### One-Time Products The customer will be credited the amount of units specified in the benefit once at the time of purchase. ## Rollover unused credits You can choose to rollover unused credits to the next billing cycle. This means that if a customer doesn't use all of their credits in a given billing cycle, the remaining credits will be added to their balance for the next billing cycle. To enable this feature, check the "Rollover unused credits" checkbox when creating or editing the Credits benefit. If you change the rollover setting for a benefit, it will only apply to new credits issued after the change. Existing credits will not be affected. # Custom Benefit Source: https://polar.sh/docs/features/benefits/custom Create your own Custom benefit You can add a simple, custom benefit, which allows you to attach a note to paying customers. ## **Custom Notes** Secret message only customers can see, e.g [Cal.com](http://Cal.com) link, private email for support etc. For custom integrations you can also distinguish benefits granted to customers to offer even more bespoke user benefits. # Automate Discord Invites & Roles Source: https://polar.sh/docs/features/benefits/discord-access Sell Discord access & roles with ease Automating Discord server invites and roles for customers or subscribers is super easy and powerful with Polar. * Fully automated Discord server invitations * You can even setup multiple Discord servers, or... * Offer different roles for different subscription tiers or products ## Create Discord Benefit Click on `Connect your Discord server`. You'll be redirected to Discord where you can grant the Polar App for your desired server. Next, you'll be prompted to approve the permissions our app requires to function. It needs all of them. ### **Manage Roles** Access to your Discord roles. You'll be able to select which ones to grant to your customers later. ### **Kick Members** Ability to kick members who have this benefit and connected Discord with Polar. ### **Create Invite** Ability to invite members who purchase a product or subscribes to a tier with this benefit. You're now redirected back to Polar and can finish setting up the Discord benefit on our end. ### **Connected Discord server** The Discord server you connected cannot be changed. However, you can create multiple benefits and connect more Discord servers if you want. ### **Granted role** Which Discord role do you want to grant as part of this benefit? ## Adding Benefit to Product Head over to the product you want to associate this new Discord benefit with. You should be able to toggle the benefit in the bottom of the Edit Product form. # Automate Customer File Downloads Source: https://polar.sh/docs/features/benefits/file-downloads Offer digital file downloads with ease ## Sell Digital Products You can easily offer customers and subscribers access to downloadable files with Polar. * Up to 10GB per file * Upload any type of file - from ebooks to full-fledged applications * SHA-256 checksum validation throughout for you and your customers (if desired) * Customers get a signed & personal downloadable URL ## Create Downloadable Benefit 1. Go to `Benefits` in the Dashboard sidebar 2. Click `+ Add Benefit` to create a new benefit 3. Choose `File Downloads` as the `Type` You can now upload the files you want to offer as downloadables for customers. 1. Drag & drop files to the dropzone (`Feed me some bytes`) 2. Or click on that area to open a file browser ### Change filename Click on the filename to change it inline. ### Change order of files You can drag and drop the files in the order you want. ### Review SHA-256 checksum Click on the contextual menu dots and then `Copy SHA-256 Checksum` ### Delete a file Click on the contextual menu dots and then `Delete` in the menu. **Active subscribers & customers will lose access too!** Deleting a file permanently deletes it from Polar and our S3 buckets except for the metadata. Disable the file instead if you don't want it permanently deleted. ### Disable & Enable Files You can disable files at any point to prevent new customers getting access to it. **Existing customers retain their access** Customers who purchased before the file was disabled will still have access to legacy files. Only new customers will be impacted. **Enabling or adding files grants access retroactively** In case you add more files or re-enable existing ones, all current customers and subscribers with the benefit will be granted access. # Automate Private GitHub Repo(s) Access Source: https://polar.sh/docs/features/benefits/github-access Sell premium GitHub repository access with ease ## Sell GitHub Repository Access With Polar you can seamlessly offer your customers and subscribers automated access to private GitHub repositories. * Fully automated collaborator invites * Unlimited repositories (via multiple benefits) from your organization(s) * Users get access upon subscribing & removed on cancellation * Or get lifetime access upon paying a one-time price (product) ### **Use cases** * Sponsorware * Access to private GitHub discussions & issues for sponsors * Early access to new feature development before upstream push * Premium educational materials & code * Self-hosting products * Courses, starter kits, open core software & more... ## Create GitHub Repository Benefit 1. Go to `Benefits` in the sidebar 2. Click `+ New Benefit` to create a new benefit 3. Choose `GitHub Repository Access` as the `Type` You first need to `Connect your GitHub Account` and install a dedicated Polar App for this benefit across the repositories you want to use it with. * Click `Connect your GitHub Account` **Why do I need to connect GitHub again and install a separate app?** This feature requires permission to manage repository collaborators. GitHub Apps does not support progressive permission scope requests. So instead of requesting this sensitive permission from all users (unnecessarily) in our core GitHub Login this feature uses a standalone app instead. Once you've authorized our dedicated GitHub App for this feature you'll be redirected back to Polar and the benefit form - now connected and updated. ### **Repository** Select the desired repository you want to automate collaborator invites for. **Why can I only connect organization repositories vs. personal ones?** GitHub does not support granular permissions for collaborators on personal repositories - granting them all write permissions instead. Since collaborators would then be able to push changes, releases and more, we do not support personal repositories by default.Want this still? Reach out to us and we can enable it. ### **Role** Select the role you want to grant collaborators. * **Read (Default & Highly recommended)** * Triage * Write * Maintain * Admin Read access (read-only) is what 99.9% of cases should use and the others are highly discouraged unless you have special use cases & absolutely know the impact of these permissions. Checkout the [GitHub documentation](https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/repository-roles-for-an-organization#permissions-for-each-role) for reference. Anyone with read access to a repository can create a pull request [(source)](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request). **Additional Costs for Paid GitHub Organizations** GitHub treats collaborators as a seat and they will incurr charges accordingly to your billing unless you're using a free GitHub organization plan. So make sure to confirm you're on a free plan OR charge sufficiently to offset the costs you'll need to pay to GitHub. # Automated Benefits Source: https://polar.sh/docs/features/benefits/introduction Polar offers built-in benefit (entitlements) automation for common upsells within the developer & designer ecosystem with more to come. * [**Credits**](/features/benefits/credits). A simple benefit that allows you to credit a customer's Usage Meter balance. * [**License Keys**](/features/benefits/license-keys). Software license keys that you can customize the branding of. * [**File Downloads**](/features/benefits/file-downloads). Downloadable files of any kind up to 10GB each. * [**GitHub Repository Access**](/features/benefits/github-access). Automatically invite subscribers to private GitHub repo(s). * [**Discord Invite**](/features/benefits/discord-access). Automate invitations and granting of roles to subscribers and customers. ## Product & Subscription Benefits Product and subscription benefits are standalone resources in Polar - connected to one or many products or subscription tiers. This approach is a bit different from other platforms, but offers many advantages: * Easy to enable the same benefit across multiple products & subscriptions * You can change a benefit in one place vs. many * No duplicate data or work (error prone) * More intuitive UI for you and your customers **How customers get access to benefits:** * โœ… Active subscribers of tiers with the benefit enabled * โœ… Customers who bought a product with the benefit (lifetime access) * โŒ Subscribers with an expired subscription (cancelled) * โŒ Users who are not customers ## Creating & Managing Benefits You can manage benefits in two ways: 1. Directly within a product create/edit form 2. Or via `Benefits` in your dashboard # Automate Customer License Key Management Source: https://polar.sh/docs/features/benefits/license-keys Sell license key access to your service, software or APIs with ease You can easily sell software license keys with Polar without having to deal with sales tax or hosting an API to validate them in real-time. License keys with Polar come with a lot of powerful features built-in. * Brandable prefixes, e.g `POLAR_*****` * Automatic expiration after `N` days, months or years * Limited number of user activations, e.g devices * Custom validation conditions * Usage quotas per license key * Automatic revokation upon cancelled subscriptions ## Create License Key Benefit 1. Go to `Benefits` in the sidebar 2. Click `+ New Benefit` to create a new benefit 3. Choose `License Keys` as the `Type` ### Custom Branding Make your license keys standout with brandable prefixes, e.g `MYAPP_` ### Automatic Expiration Want license keys to expire automatically after a certain time period from when the customer bought them? No problem. ### Activation Limits You can require license keys to be activated before future validation. A great feature in case you want to limit license key usage to a certain number of devices, IPs or other conditions. **Enable user to deactivate instances via Polar.** Instead of building your own custom admin for customers to manage their activation instances - leave it to Polar instead. ### Usage Limit Offering OpenAI tokens or anything else with a variable usage cost? You can set a custom usage quota per license key and increment usage upon validation. ## Customer Experience Once customers buy your product or subscribes to your tier, they will automatically receive a unique license key. It's easily accessible to them under their purchases page. Customers can: * View & copy their license key * See expiration date (if applicable) * See usage left (if applicable) * Deactivate activations (if enabled) ## Integrate API It's super easy and straightforward to integrate Polar license keys into your application, library or API. ### Activate License Keys (Optional) In case you've setup license keys to have a maximum amount of activation instances, e.g user devices. You'll then need to create an activation instance prior to validating license keys / activation. **No activation limit?** You can skip this step. ```bash Terminal theme={null} curl -X POST https://api.polar.sh/v1/customer-portal/license-keys/activate -H "Content-Type: application/json" -d '{ "key": "1C285B2D-6CE6-4BC7-B8BE-ADB6A7E304DA", "organization_id": "fda84e25-7b55-4d67-916d-60ead04ff61f", "label": "hello", "conditions": { "major_version": 1 }, "meta": { "ip": "84.19.145.194" } }' ``` Replace with the users license key (from input in your app). Replace with your organization ID here found in your settings. Set a label to associate with this specific activation. JSON object with custom conditions to validate against in the future, e.g IP, mac address, major version etc. JSON object with metadata to store for the users activation. #### **Response (200 OK)** ```json theme={null} { "id": "b6724bc8-7ad9-4ca0-b143-7c896fcbb6fe", "license_key_id": "508176f7-065a-4b5d-b524-4e9c8a11ed63", "label": "hello", "meta": { "ip": "84.19.145.194" }, "created_at": "2024-09-02T13:48:13.251621Z", "modified_at": null, "license_key": { "id": "508176f7-065a-4b5d-b524-4e9c8a11ed63", "organization_id": "fda84e25-7b55-4d67-916d-60ead04ff61f", "user_id": "d910050c-be66-4ca0-b4cc-34fde514f227", "benefit_id": "32a8eda4-56cf-4a94-8228-792d324a519e", "key": "1C285B2D-6CE6-4BC7-B8BE-ADB6A7E304DA", "display_key": "****-E304DA", "status": "granted", "limit_activations": 3, "usage": 0, "limit_usage": 100, "validations": 0, "last_validated_at": null, "expires_at": "2026-08-30T08:40:34.769148Z" } } ``` ### Validate License Keys For each session of your premium app, library or API, we recommend you validate the users license key via the [`/v1/customer-portal/license-keys/validate`](/api-reference/customer-portal/license-keys/validate) endpoint. ```bash Terminal theme={null} curl -X POST https://api.polar.sh/v1/customer-portal/license-keys/validate -H "Content-Type: application/json" -d '{ "key": "1C285B2D-6CE6-4BC7-B8BE-ADB6A7E304DA", "organization_id": "fda84e25-7b55-4d67-916d-60ead04ff61f", "activation_id": "b6724bc8-7ad9-4ca0-b143-7c896fcbb6fe", "conditions": { "major_version": 1 }, "increment_usage": 15 }' ``` Replace with the users license key (from input in your app). Replace with your organization ID here found in your settings. The activation ID to validate - required in case activations limit is enabled and used (above). In case of activation instances. Same exact JSON object as upon registration of the activation. In case you want to increment usage upon validation. #### **Response (200 OK)** ```json theme={null} { "id": "508176f7-065a-4b5d-b524-4e9c8a11ed63", "organization_id": "fda84e25-7b55-4d67-916d-60ead04ff61f", "user_id": "d910050c-be66-4ca0-b4cc-34fde514f227", "benefit_id": "32a8eda4-56cf-4a94-8228-792d324a519e", "key": "1C285B2D-6CE6-4BC7-B8BE-ADB6A7E304DA", "display_key": "****-E304DA", "status": "granted", "limit_activations": 3, "usage": 15, "limit_usage": 100, "validations": 5, "last_validated_at": "2024-09-02T13:57:00.977363Z", "expires_at": "2026-08-30T08:40:34.769148Z", "activation": { "id": "b6724bc8-7ad9-4ca0-b143-7c896fcbb6fe", "license_key_id": "508176f7-065a-4b5d-b524-4e9c8a11ed63", "label": "hello", "meta": { "ip": "84.19.145.194" }, "created_at": "2024-09-02T13:48:13.251621Z", "modified_at": null } } ``` Validate `benefit_id` in case of multiple license keys We require `organization_id` to be provided to avoid cases of Polar license keys being used across Polar organizations erroneously. Otherwise, a valid license key for one organization could be used on another.However, you are required to validate and scope license keys more narrowly within your organization if necessary. Offering more than one type of license key? Be sure to validate their unique benefit\_id in the responses. # Embedded Checkout Source: https://polar.sh/docs/features/checkout/embed Embed our checkout directly on your site You can either copy and paste our code snippet to get up and running in a second or use our JavaScript library for more advanced integrations. Our embedded checkout allows you to provide a seamless purchasing experience without redirecting users away from your site. ## Code Snippet The code snippet can be used on any website or CMS that allows you to insert HTML. First, create a [Checkout Link](/features/checkout/links) as described in the previous section. The code snippet can directly be copied from there by clicking on `Copy Embed Code`. The snippet looks like this: ```typescript theme={null} Purchase ``` This will display a `Purchase` link which will open an inline checkout when clicked. You can style the trigger element any way you want, as long as you keep the `data-polar-checkout` attribute. ## Import Library If you have a more advanced project in JavaScript, like a React app, adding the ` ``` Replace `YOUR_AFFONSO_PROGRAM_ID` with the unique program ID provided by Affonso. This script should be placed on all pages of your website, including: * Your main marketing website * Your application domain * Any subdomains where users might land or make purchases ### 4. Track User Signups (Optional) For better conversion insights, you can track when users sign up through an affiliate link: ```javascript theme={null} // After successful registration window.Affonso.signup(userEmail); ``` ### 5. Pass Referral Data to Polar Checkout To ensure proper commission attribution, pass the referral data when creating checkout sessions: ```javascript theme={null} // Get the referral ID from the Affonso global variable const referralId = window.affonso_referral; // Create checkout session with Polar const checkout = await polar.checkouts.create({ products: ["your_product_id"], success_url: "https://your-site.com/success", metadata: { affonso_referral: referralId, // Include referral ID from Affonso } }); // Redirect to checkout window.location.href = checkout.url; ``` ## How It Works 1. When a user visits your site through an affiliate link, Affonso's script stores a unique identifier in a cookie 2. If you've implemented signup tracking, Affonso records when the user creates an account 3. When the user makes a purchase, the referral ID is passed to Polar as metadata 4. Polar's webhook notifies Affonso about the purchase 5. Affonso attributes the sale to the correct affiliate and calculates the commission ## Benefits of the Integration * **Automated Tracking**: No manual work required to track affiliate-driven sales * **Real-Time Analytics**: Both you and your affiliates get immediate insights into performance * **Seamless User Experience**: The integration works behind the scenes without affecting your checkout flow * **Flexible Commission Structures**: Set up complex commission rules based on product, subscription duration, etc. ## Getting Help More details about the integration: [Polar Affiliate Program](https://affonso.io/polar-affiliate-program) If you need assistance with your Affonso integration, contact Affonso's support team: * Email: [hello@affonso.io](mailto:hello@affonso.io) * Live chat: Available directly in the Affonso dashboard # Polar Integration in Fernand Source: https://polar.sh/docs/features/integrations/fernand Learn how to sync customer and payment data from Polar to Fernand. ## What is Fernand? [Fernand](https://getfernand.com/) is a modern customer support tool designed for SaaS โ€” itโ€™s fast, calm, and built to reduce the anxiety of answering support requests. ## How it works After connecting your [Polar](https://polar.sh/) account to Fernand, youโ€™ll be able to see customer payment information and product access details directly within each customer conversation. This enables you to: * Instantly verify if someone is an active customer * Prioritize conversations from high-tier plans * View product purchases and payment history in context *** ## How to connect Fernand with Polar 1. Open [Integrations](https://app.getfernand.com/settings/organization/integrations) in your Fernand organization settings. 2. Click on **Connect Polar**. 3. You'll be redirected to Polar to authorize the connection. 4. Once approved, Fernand will begin syncing customer data automatically. Thatโ€™s it! Youโ€™ll now see Polar customer info directly in Fernand's conversation list and sidebar. *** ## How to automate your inbox with Polar data Once Polar is connected, you can create automation rules in Fernand based on Polar data. Letโ€™s walk through a basic example: auto-replying to all customers on your `Pro` plan. ### Create a new rule 1. Go to [Rules](https://app.getfernand.com/settings/organization/rules) in Fernand. 2. Click `Add rule` and give it a descriptive name. This ensures the rule runs on each new customer message. Now add a condition based on Polar data. For example: * `Contact is a customer...` * `Contact has paid plan...` You can target specific plans (e.g. `Pro`, `Business`) or specific products to personalize support or automate prioritization. Now define what happens when the rule matches. For example: * Send an auto reply (with variables) * Assign the conversation to a specific agent * Tag the conversation with `priority` or `paid` * Trigger a webhook for external automation ### Disconnecting the integration If you ever want to disconnect Polar from your Fernand workspace: Deleting your organization on Fernand will also remove the Polar integration automatically. # Polar for Framer Source: https://polar.sh/docs/features/integrations/framer The fastest way to sell digital products on your Framer site Introducing the official Polar plugin for Framer. Allowing you to sell products on your site without having to build a custom checkout flow. ![](https://www.framer.com/marketplace/_next/image/?url=https%3A%2F%2Fy4pdgnepgswqffpt.public.blob.vercel-storage.com%2Fplugins%2F174-egCWZYwZbpLc42xnGQIY42F1KqtNDk\&w=1920\&q=100) ## Getting Started [Get your hands on the Polar plugin in the Framer Marketplace](https://www.framer.com/marketplace/plugins/polar/) # Purchase Power Parity with ParityDeals Source: https://polar.sh/docs/features/integrations/paritydeals Offer products with different price across the globe Want to offer different prices in different countries? [ParityDeals](https://www.paritydeals.com/) offers [automatic pricing optimizations depending on customers geolocation](https://www.paritydeals.com/features/purchasing-power-parity-discounts/) and a seamless integration with Polar. ## Simple Integration, Powerful Deals * You can easily and securely (OAuth 2.0) connect Polar to ParityDeals * Select products on Polar to offer deals for * Configure deals by country or holidays * ParityDeals automatically creates and manages discounts on Polar * Showing them to customers based on time and geolocation (unless VPN is detected) * Offering great & local deals internationally with ease ## Setup Guide ### Signup to ParityDeals Go to [app.paritydeals.com](http://app.paritydeals.com) and sign up. ### Connect Polar on ParityDeals In your ParityDeals dashboard, click `Create Deals` > `Create Deals with Polar`. ### Grant ParityDeals Access (OAuth 2.0) No need to create API access keys and share them externally. Just connect securely and grant the necessary permissions using Polar OAuth 2.0. ### Choose Products Now, let's select the Polar products you want to offer deals for. ### Configure Deals Let's configure our deal settings. * Enter your website URL (requires your own site vs. Polar storefront) * Enter a targeted URL path, e.g `/pricing` to only show deals on that page Now we can configure the deals for different countries. ParityDeals offers great defaults, but you can of course change them. ### Configure Banner You can then customize the ParityDeals banner to suit your site and design. ### Embed Banner Finally, we're all setup over at ParityDeals. Just copy the script to their banner and embed it on your site. You're now done ๐Ÿ‘๐Ÿผ ## Questions & Help Checkout the [ParityDeals documentation](https://www.paritydeals.com/docs/) for more guides and information. # Polar for Raycast Source: https://polar.sh/docs/features/integrations/raycast The fastest way to access Polar from your keyboard ## Install Extension [Head over to Polar on the Raycast Store, and install it from there.](https://www.raycast.com/emilwidlund/polar) ### View Orders Easily view orders across organizations. ![](https://files.raycast.com/acvj8yffxqxbnv82lhtsnf7u7x29) ### View Subscriptions View all active subscriptions across your organizations. ![](https://files.raycast.com/y6he77j6ig6hchxbpxdcsd2i1yjf) ### View Customers Keep track of all your customers. # Polar for Zapier Source: https://polar.sh/docs/features/integrations/zapier Connect Polar to hundreds of other apps with Zapier [Zapier](https://zapier.com/apps/polar/integrations) lets you connect Polar to 2,000+ other web services. Automated connections called Zaps, set up in minutes with no coding, can automate your day-to-day tasks and build workflows between apps that otherwise wouldn't be possible. Each Zap has one app as the **Trigger**, where your information comes from and which causes one or more **Actions** in other apps, where your data gets sent automatically. We've focused on **triggers** (webhooks) for now, so you can react to events in Polar and trigger actions in other apps. Need to perform actions in Polar? Tell us about your use case [here](https://github.com/orgs/polarsource/discussions/new?category=integrations\&labels=integrations%2Fzapier) and we'll consider adding more actions in the future. ## Getting Started with Zapier Sign up for a free [Zapier](https://zapier.com/apps/polar/integrations) account, from there you can jump right in. To help you hit the ground running, you'll find popular pre-made Zaps below. ## How do I connect Polar to Zapier? Log in to your [Zapier account](https://zapier.com/sign-up) or create a new account. Navigate to "My Apps" from the top menu bar. Now click on "Connect a new account..." and search for "Polar" Use your credentials to connect your Polar account to Zapier. Once that's done you can start creating an automation! Use a pre-made Zap or create your own with the Zap Editor. Creating a Zap requires no coding knowledge and you'll be walked step-by-step through the setup. Need inspiration? See everything that's possible with [Polar and Zapier](https://zapier.com/apps/Polar/integrations). If you have any additional questions, you can open a ticket with Zapier Support from [https://zapier.com/app/get-help](https://zapier.com/app/get-help) ## Popular use cases # Orders & Subscriptions Source: https://polar.sh/docs/features/orders ## Sales The sales view shows you all sales in a paginated list. ## Order & Subscription Details Each sale has metadata attached to it. Common properties like * Amount * Tax Amount * Invoices * Customer * Basic Customer Details * Past Orders ## Checkouts You can also have an overview of all checkout sessions. You can filter them by customer email, status and product. A checkout can be in the following states: * Open: The checkout session is open and waiting for the customer to complete the payment. * Confirmed: The customer clicked the **Pay** or **Subscribe** button and the payment is being processed. * Succeeded: The payment was successful and the order was created. * Expired: The checkout session expired and the customer can no longer complete it. A new checkout session must be created. If you click on a Checkout, you can have more details on the **payment attempts**, in particular, why a payment has failed or has been declined. # Products Source: https://polar.sh/docs/features/products Create digital products on Polar in minutes **Everything is a product** Subscriptions or pay once products are both considered a product in Polar (API & data model). Just with different pricing & billing logic. So both are shown & managed under Products with the ability to filter based on pricing model. ## Create a product ### Name & Description Starting off with the basic. * **Name** The title of your product. * **Description** Markdown is supported here too. ### Pricing Determine how you want to charge your customers for this product. * **One-time purchase** Customer is charged once and gets access to the product forever. * **Monthly** Customer is charged every month. * **Yearly** Customer is charged every year. * **Fixed price** Set a fixed price for the product. * **Pay what you want** Let customers decide how much they want to pay. * **Free** No charge for the product. For fixed price products, set the amount you want to charge. For pay what you want products, you can set a minimum amount and a default amount that will be preset on checkout. Billing cycle and pricing type cannot be changed after the product is created. **What if I want both a monthly and yearly pricing?** Polar has a unique approach to what the industry typically calls **variants**. Each product has a single pricing model, but you can create multiple products with different pricing models, and showcase them both at checkout. ### Trial Period For recurring products, you can set a trial period during which the customer won't be charged. Toggle **Enable trial period** to enable it. Then, you'll be able to set the duration of the trial period, given a number and a unit (days, weeks, months or years). You can read more about how trials work [here](/features/trials). ### Product Media * You can upload public product images to be displayed on product pages * They can be up to 10MB each * You can remove and re-arrange images ### Checkout Fields You can collect additional information from your customers at checkout. This can be useful for things like phone number, terms of service agreement or specific data you need to collect. Fields are managed from your organization settings, and you can choose which fields to show on a per-product basis, and set if they are required or not. We support the following field types: * Text * Number * Date * Checkbox * Select If you make a checkbox **required**, the customer will need to check it before confirming their purchase. Very handy for legal terms! The data collected will be available in the order and subscription details. ### Automated Entitlements Finally, you can enable or create new entitlements (what we call Benefits) that you tie to the product. Read more in our [product benefits guide](/features/benefits/introduction) on how they work and how to customize the built-in ones we offer: * License Keys * Discord Server Role * GitHub Repository Access * File Downloads * Custom Benefit ## Variants Polar has a unique approach regarding what the industry typically calls **variants**. We believe having a single product with multiple pricing models and benefits adds unnecessary complexity to the user and to the API. Instead, we chose to treat everything as a product, giving you maximum flexibility about the pricing and benefits you want to offer. You can showcase several products at checkout, allowing the customer to switch between them. Typically, you can offer a monthly and a yearly product, with specific pricing and benefits for each. Read more about how to do so using [Checkout Links](/features/checkout/links) or the [Checkout Session API](/features/checkout/session). ## Update a product You can edit any product details, except the **billing cycle** and **pricing type**. For fixed price products, you can change the price. Existing subscribers will remain on their current pricing. If you add benefits, existing subscribers will get them automatically. If you remove benefits, existing subscribers will lose access to them. ## Archive a product Products on Polar can't be deleted, but they can be **archived**. You can do so by clicking the **Archive** button on the bottom right of the product page. Existing customers will keep their access to the product, and subscriptions will continue to renew. However, the product will no longer be available for new purchases. It's possible to unarchive a product using the [Products Update API](/api-reference/products/update#body-is-archived). # Manage Refunds Source: https://polar.sh/docs/features/refunds You can easily refund orders on Polar - both in full or in parts. No matter what refund policy you offer to customers, Polar makes it easy to offer both full and partial refunds to easily deliver the customer experience and refund policy you want. However, even in case you have a โ€œno refundโ€ policy, Polar reserves the right to issue refunds within 60 days of purchase - at our own discretion. We reserve this right in an effort to automatically and proactively reduce costly chargebacks. **Polar can issue refunds on your behalf** Polar reserves the right to issue refunds within 60 days of purchase, at its own discretion, in order to prevent chargebacks. So if you choose to have a โ€œno refundsโ€ policy, be aware that Polar could still issue refunds in an effort to proactively prevent chargebacks. ## Issuing a refund 1. Go to the order details page for the specific order you want to refund 2. Scroll down to the โ€œRefundโ€ section 3. Click โ€œRefundโ€ **Amount** Specify the amount to refund. By default itโ€™s the full order amount, but you can reduce this to issue a partial refund instead. **Payment fees are not refunded** Unfortunately, credit card networks and PSPs charge us for the underlying transactions regardless of whether itโ€™s later refunded (industry standard). Therefore, we cannot offer a refund on our fees since the costs remain constant. Example: An order of $30 costs ~$1.6 in fees to Polar. You can still refund the customer $30, but the ~$1.6 fee remains and is deducted on your balance from other purchases. **Reason** Select the reason for the refund - helpful for future reference. **Revoke Benefits (One-time purchases)** For one-time purchases, you can revoke the customers access to product benefits, e.g file downloads, license keys or Discord/GitHub invites. By default this is selected since we default to a full refund, but can be disabled. **Revoke Benefits (Subscriptions)** You cannot revoke access by refunding an order associated with a subscription. Instead the subscription is required to be canceled and Polar will then automatically revoke access once the subscription itself is revoked. # Seat-Based Pricing Source: https://polar.sh/docs/features/seat-based-pricing Sell team products with assignable seats and tiered pricing Seat-based pricing allows you to sell products where a billing manager purchases a specific number of seats and can assign them to team members. Each seat holder gets their own access to the product benefits, making it perfect for team subscriptions, perpetual licenses, and multi-user products. **Seat-based pricing is ideal for:** * Team subscriptions where one billing manager pays for multiple users * Perpetual team licenses with one-time payment * Organizational licenses with per-seat pricing * Products with volume-based tiering (e.g., \$10/seat for 1-4 seats, \$9/seat for 5+) ## Feature Flag Seat-based pricing is currently in **private beta**. We are enabling this feature gradually. Seat-based pricing is controlled by a feature flag at the organization level. Once enabled by our team, you'll have full access to create and manage seat-based products. ## How it works With seat-based pricing, a billing manager purchases a product (subscription or one-time) with a specific number of seats. They can then: 1. **Assign seats** to team members via email or external customer ID 2. **Manage seats** by resending invitations or revoking access 3. **Scale up** by purchasing additional seats (or a new order for one-time products) 4. **Track usage** by viewing which seats are claimed, pending, or available Team members receive an invitation email with a claim link. Once they claim their seat, benefits are automatically granted. ### Subscriptions vs One-Time Purchases Seat-based pricing works for both recurring subscriptions and one-time purchases: | Feature | Subscriptions | One-Time Purchases | | ----------------- | -------------------------- | ------------------------ | | **Payment** | Recurring (monthly/yearly) | Single payment | | **Seat Duration** | Active while subscribed | Perpetual (never expire) | | **Adding Seats** | Modify subscription | Purchase new order | | **Billing** | Renews automatically | No renewals | | **Benefits** | While subscription active | Forever after claim | Use **subscriptions** for ongoing team access. Use **one-time purchases** for perpetual team licenses. ## Creating a seat-based product From your dashboard, navigate to Products and click **Create Product**. Set your product name, description, and media as usual. Under **Pricing**, select: * **Product type**: Subscription or One-time * **Billing cycle** (subscriptions only): Monthly or Yearly * **Pricing type**: Seat-based Define your pricing tiers based on seat quantity: * **Min seats**: Minimum number of seats required to purchase * **Tiers**: For each tier, set: * **Max seats**: Upper limit for this tier (leave empty for unlimited) * **Price per seat**: Amount charged per seat in this tier (in cents) Example tiered pricing: * 1-4 seats: \$10/seat per month * 5-9 seats: \$9/seat per month * 10+ seats: \$8/seat per month Configure the benefits that seat holders will receive. These are only granted when a seat is claimed, not when purchased. Unlike standard subscriptions, seat-based products **do not grant benefits to the billing manager**. Benefits are only granted to team members who claim their assigned seats. ## Purchasing seats When a customer purchases a seat-based product, they specify how many seats they want during checkout. The total amount is calculated based on your tiered pricing. The checkout experience clearly shows: * Number of seats being purchased * Price per seat based on volume * Total amount * For subscriptions: recurring billing cycle * For one-time: perpetual access indication Once payment is completed, the billing manager can immediately start assigning seats to their team. For **one-time purchases**, each order is independent. If customers want more seats later, they purchase a new order with its own seat pool. All purchased seats remain perpetual. ## Managing seats ### Assigning seats Billing managers can assign seats through: 1. **Customer Portal**: Accessible via the customer portal with billing manager permissions 2. **API**: Programmatically assign seats using the [Customer Seats API](/api-reference/customer-seats/assign) To assign a seat, provide: * **Subscription ID** (for subscriptions) or **Order ID** (for one-time purchases) * **Email address** (creates a new customer if needed) * **External customer ID** (optional, for syncing with your system) * **Customer ID** (if customer already exists in Polar) * **Metadata** (optional, up to 10 keys for custom data like role, department) An invitation email is automatically sent to the recipient with a secure claim link (valid for 24 hours). ### Seat statuses Each seat can have one of three statuses: * **Pending**: Seat assigned, invitation sent, awaiting claim * **Claimed**: Seat claimed by team member, benefits granted * **Revoked**: Seat revoked, benefits removed, can be reassigned ### Resending invitations For pending seats, billing managers can resend the invitation email if it was lost or expired. ### Revoking seats Billing managers can revoke a claimed seat at any time: 1. Benefits are immediately removed from the seat holder 2. The seat becomes available for reassignment 3. The seat holder loses access to all product benefits 4. The revoked seat can be assigned to a different team member Revoking a seat does **not** issue a refund. The billing manager continues to pay for the total number of seats in their subscription. ## Claiming seats When a team member receives a seat invitation: 1. They click the claim link in the email 2. A claim page displays the product details and organization info 3. They click **Claim Seat** to accept 4. Benefits are automatically granted 5. They receive a customer session token for immediate portal access 6. They can access their benefits through the customer portal Claim links are single-use and expire after 24 hours for security. If expired, the billing manager can resend the invitation. ## Scaling seats ### Adding seats **For subscriptions:** Billing managers can upgrade their subscription to add more seats: 1. The new seat count is applied immediately 2. Prorated charges are calculated for the current billing period 3. Future renewals bill at the new seat count 4. New seats can be assigned right away If adding seats moves to a different pricing tier, the new per-seat rate applies to **all seats**, not just the additional ones. **For one-time purchases:** Billing managers purchase a new order with additional seats: 1. Create a new checkout for the same product with desired seat quantity 2. Once paid, a new independent order is created 3. Each order has its own seat pool 4. All seats remain perpetual across all orders ### Reducing seats **For subscriptions:** To reduce seats, the billing manager should: 1. Revoke seats until the desired count is reached 2. Update the subscription to reflect the lower seat count 3. The change takes effect at the next renewal You cannot reduce subscription seats below the number of currently claimed seats. Revoke seats first before reducing the subscription seat count. **For one-time purchases:** Seats cannot be reduced or refunded as they are perpetual. The billing manager can: 1. Revoke unwanted seats to make them unassigned 2. The seats remain available for future assignment 3. No refund is issued for revoked seats ## API Integration Seat-based pricing provides full API support for: * Creating seat-based products with tiered pricing * Checking out with seat quantities * Assigning seats programmatically * Listing seats and their statuses * Revoking seats **SDK Version Requirement** To use seat-based pricing features, ensure you have the latest version of the Polar SDK installed: * TypeScript/JavaScript: `npm install @polar-sh/sdk@latest` * Python: `pip install --upgrade polar-sdk` Older SDK versions may not include seat-based pricing support. See the [Customer Seats API Reference](/api-reference/customer-seats/assign) for complete documentation. ### Example: Assign a seat (subscription) ```typescript theme={null} await polar.customerSeats.assign({ subscription_id: "sub_123", email: "engineer@company.com", metadata: { department: "Engineering", role: "Developer" } }); ``` ### Example: Assign a seat (one-time purchase) ```typescript theme={null} await polar.customerSeats.assign({ order_id: "order_456", email: "engineer@company.com", metadata: { department: "Engineering", role: "Developer" } }); ``` ### Example: List seats for subscription ```typescript theme={null} const seats = await polar.customerSeats.list({ subscription_id: "sub_123" }); console.log(`Available: ${seats.available_seats}/${seats.total_seats}`); ``` ### Example: List seats for order ```typescript theme={null} const seats = await polar.customerSeats.list({ order_id: "order_456" }); console.log(`Available: ${seats.available_seats}/${seats.total_seats}`); ``` ## Webhooks Seat-based pricing triggers webhooks for both subscriptions and orders: **Subscription events:** * `subscription.created` - When seat-based subscription is purchased * `subscription.updated` - When seat count changes * `subscription.canceled` - When subscription is cancelled **Order events:** * `order.created` - When seat-based one-time purchase is completed * `order.updated` - When order is updated **Seat and benefit events (both types):** * `benefit_grant.created` - When a seat is claimed and benefits granted * `benefit_grant.revoked` - When a seat is revoked and benefits removed For both subscriptions and one-time purchases, `benefit_grant.created` events are triggered per seat claim, not at purchase time. ## Best Practices ### Use tiered pricing strategically Structure your tiers to incentivize volume: * Lower per-seat prices as quantity increases * Create tiers at natural team sizes (5, 10, 25, etc.) * Consider flat pricing for very large teams ### Leverage metadata Use seat metadata to store: * Team member roles * Departments or cost centers ### Monitor seat utilization Track how many purchased seats are actually claimed to identify: * Organizations that may need more seats * Unused capacity that could be reduced * Patterns in team adoption ### Communicate clearly Ensure product pages clearly explain: * The billing manager will not get direct access * Seats must be assigned to team members * Pricing structure and volume discounts * Seat assignment and claiming process ## Limitations * Seats must be assigned individually (no bulk import via dashboard, use API instead) * Claim links expire after 24 hours * Billing manager does not receive product benefits * Maximum of 1,000 seats per subscription * Metadata limited to 10 keys and 1KB total size per seat * **Usage-based pricing with shared emails**: If you sell to multiple businesses and different businesses assign seats to the same email address (e.g., "[john@gmail.com](mailto:john@gmail.com)"), and usage-based pricing is enabled, separate meters should be created for each business to properly segregate usage. This is a rare scenario since employees from different businesses typically don't share the same email address. # Trials Source: https://polar.sh/docs/features/trials Offer free trials on your subscriptions Trials are a great way to let potential customers experience your product before committing to a subscription. With Polar, you can easily set up free trials for your subscription products. ## Setting up a trial You can set up a trial period through the following means: * When creating or editing a [product](/features/products). * When creating or editing a [checkout link](/features/checkout/links). * When creating a Checkout Session through the [API](/api-reference/checkouts/create-session). If you set a trial period on the Checkout Link or Checkout Session, it will **override the trial period set on the product**. The trial period consists of two parameters: * **A unit**: day, week, month, or year. * **A duration**: a number representing how many units the trial will last. ## Starting a trial When a customer checks out a subscription product with a trial period, they will not be charged immediately. Instead, they will have access to the product for the duration of the trial period. We'll still collect their payment information at checkout, but they won't be charged until the trial period ends. This means that if they decide to cancel before the trial ends, they won't be charged at all. Once the trial period ends, the customer will be automatically charged for the subscription, and their billing cycle will begin. ## Adding, extending or canceling a trial For existing subscriptions, you can add, extend or cancel a customer's trial period at any time through the dashboard, from the subscription details page. Click on **Update Subscription**, then click on the **Trial** tab. To add or extend a trial, set a new trial end date in the future. If the subscription was active, its status will be changed to **trialing**, and the billing will be postponed until the end of the trial. To cancel a trial, click on the **End trial** button. The subscription will become active immediately, and the customer will be charged immediately for a new billing cycle. ## Preventing trial abuse To protect your business from customers repeatedly signing up for trials, you can enable the **Prevent trial abuse** feature from your organization's subscription settings. ### How it works When this feature is enabled, Polar tracks trial redemptions using: * **Email addresses**: We automatically detect and normalize email aliases (e.g., `user+alias@example.com` is treated as `user@example.com`) to prevent abuse through simple email variations. * **Payment method fingerprints**: We track the unique fingerprint of payment methods (credit cards) used during checkout to identify returning customers even if they use a different email address. A customer will be blocked from starting a new trial if they have previously redeemed a trial for any of your products and match either: * The same unaliased email address, OR * The same payment method fingerprint ### Enabling the feature 1. Go to your organization's **Settings** page 2. Navigate to the **Subscription** section 3. Toggle on **Prevent trial abuse** Once enabled, the feature will apply to all future trial checkout attempts across all your products. ### Customer experience When a customer who has already used a trial attempts to check out again: 1. They will see an error message: "You have already used a trial for this product. Trials can only be used once per customer." 2. The checkout will automatically refresh without the trial period 3. The customer can still complete their purchase and subscribe at the regular price This approach ensures a smooth experience while protecting your business from trial abuse. # Billing Source: https://polar.sh/docs/features/usage-based-billing/billing How billing works with Usage Based ## Metered Pricing Metered Pricing is a pricing model where you charge your customers based on the usage of your application. There are a few different pricing models unique to Usage Based Billing: * Unit Pricing * Volume Pricing *(coming soon)* ### Unit Pricing Unit pricing is a simple pricing model where you charge a fixed amount for each unit of usage. For example: | Product Meter | Price per unit | | ------------------- | -------------- | | `prompt-tokens` | \$0.10 | | `completion-tokens` | \$0.18 | This means that every unit of `prompt-tokens` consumed by a customer will be charged at \$0.10 and every unit of `completion-tokens` will be charged at \$0.18. It's a linear pricing model, where the price per unit is fixed. ### Volume Pricing *(coming soon)* Volume pricing is a pricing model where you charge a fixed amount for a certain volume of usage. Volume pricing is not yet available, but will be coming soon. ## Invoicing Customers for Usage Our Usage Based Billing infrastructure is built to work with Subscription products out of the box. ### Add a metered price to your product To charge your customers for usage, you need to add a metered price to your product. You'll need the select the **Meter** and the **amount per unit**. Optionally, you can set a **cap**. The customer will be charged the cap amount if they exceed it, regardless of the usage. ### Monthly Invoicing If a customer has a subscription with a monthly billing period, usage is aggregated monthly and invoiced at the end of the month with the rest of the subscription. ### Yearly Invoicing If a customer has a subscription with a yearly billing period, usage is aggregated yearly and invoiced at the end of the year with the rest of the subscription. ### Usage Charges and Subscription Cancellation When a subscription is canceled, it generally remains active until the end of the current billing period (known as the grace period). During this grace period, all accumulated usage-based charges continue to be tracked. A final invoice will be issued at the end of that period to cover the consumed usage, even if the subscription will not be renewed. This ensures no pending usage charges are lost. If a [discount](/features/discounts) is applied on the subscription, it'll be applied on the **whole invoice**, including metered usage. ## Customer Portal Customers can view their estimated charges for each meter in the Customer Portal. # Credits Source: https://polar.sh/docs/features/usage-based-billing/credits Crediting customers for Usage Based Billing Credits is the way to pre-pay for usage in Polar. It allows you to give your customers the ability to pre-pay for usage instead of risk getting a hefty bill at the end of the month. ## How Credits Work When you ingest events into a Usage Meter, customers will be charged for the usage based on the product's pricing model. However, sometimes you may want to give your customers the ability to pre-pay for usage instead of risk getting a hefty bill at the end of the month. When you issue Credits to a customer, we first deduct the Credits from their Usage Meter balance. If the Usage Meter balance reaches 0, the customer will be charged for the overage. ### Credits-only spending To avoid any overage charges, don't create any Metered price on your product. This way, billing won't be triggered at all for the meter ## Issuing Credits with the Credits Benefit The Credits benefit will credit a customer's Usage Meter balance at different points in time depending on the type of product the benefit is attached to. ### Subscription Products The customer will be credited the amount of units specified in the benefit at the beginning of every subscription cycle period โ€” monthly or yearly. ### One-Time Products The customer will be credited the amount of units specified in the benefit once at the time of purchase. ## Tracking customer's balance In your application, you'll likely need to track the customer's balance for a given meter. The easiest way to do this is to use the [Customer State](/integrate/customer-state), which will give you the overview of the customer, including the balance for each of their active meters. You can also specifically query the meters balance using the [Customer Meters API](/api-reference/customer-meters/list). Polar doesn't block usage if the customer exceeds their balance. You're responsible for implementing the logic you need to prevent usage if they exceed it. # Event Ingestion Source: https://polar.sh/docs/features/usage-based-billing/event-ingestion Ingest events from your application Events are the core of Usage Based Billing. They represent *some* usage done by a customer in your application. Typical examples of events are: * A customer consumed AI LLM tokens * A customer streamed minutes of video * A customer uploaded a file to your application Events are sent to Polar using the [Events Ingestion API](/api-reference/events/ingest) and are stored in our database. An event consists of the following fields: * A `name`, which is a string that can be used to identify the type of event. For example, `ai_usage`, `video_streamed` or `file_uploaded`. * A `customer_id` or `external_customer_id`, which is Polar's customer ID or your user's ID. This is used to identify the customer that triggered the event. * A `metadata` object, which is a JSON object that can contain any additional information about the event. This is useful for storing information that can be used to filter the events or compute the actual usage. For example, you can store the duration of the video streamed or the size of the file uploaded. Here is an example of an event: ```json theme={null} { "name": "ai_usage", "external_customer_id": "cus_123", "metadata": { "model": "gpt-4.1-nano", "requests": 1, "total_tokens": 77, "request_tokens": 58, "response_tokens": 19 } } ``` ## Ingest events using the Polar SDK To ingest events, you can use the Polar SDKs. ### TypeScript Example ```typescript theme={null} import { Polar } from "@polar-sh/sdk"; const polar = new Polar({ accessToken: process.env["POLAR_ACCESS_TOKEN"] ?? "", }); await polar.events.ingest({ events: [ { name: "", externalCustomerId: "", metadata: { key: "value", }, }, ], }); ``` You are always responsible for checking the balance of your customers' Usage Meter. As events always are ingested, we will never prohibit any customer's action based on their Usage Meter balance. ## Ingestion Strategies To make it easier to ingest events, we have created a set of ingestion strategies for common event sources. Learn more about our [Ingestion Strategies](/features/usage-based-billing/ingestion-strategies). ## Good to know ### Events are immutable Once an event is ingested, it cannot be changed, nor can it be deleted. # Delta Time Strategy Source: https://polar.sh/docs/features/usage-based-billing/ingestion-strategies/delta-time-strategy Ingest delta time of arbitrary execution ## Javascript SDK Ingest delta time of arbitrary execution. Bring your own now-resolver. ``` pnpm add @polar-sh/ingestion ``` ```typescript theme={null} import { Ingestion } from "@polar-sh/ingestion"; import { DeltaTimeStrategy } from "@polar-sh/ingestion/strategies/DeltaTime"; const nowResolver = () => performance.now(); // const nowResolver = () => Number(hrtime.bigint()) // const nowResolver = () => Date.now() // Setup the Delta Time Ingestion Strategy const deltaTimeIngestion = Ingestion({ accessToken: process.env.POLAR_ACCESS_TOKEN, }) .strategy(new DeltaTimeStrategy(nowResolver)) .ingest("execution-time"); export async function GET(request: Request) { try { // Get the wrapped start clock function // Pass Customer Id to properly annotate the ingestion events with a specific customer const start = deltaTimeIngestion.client({ customerId: request.headers.get("X-Polar-Customer-Id") ?? "", }); const stop = start(); await sleep(1000); // { deltaTime: xxx } is automatically ingested to Polar const delta = stop(); return Response.json({ delta }); } catch (error) { return Response.json({ error: error.message }); } } ``` #### Ingestion Payload ```json theme={null} { "customerId": "123", "name": "execution-time", "metadata": { "deltaTime": 1000 } } ``` # Strategy Introduction Source: https://polar.sh/docs/features/usage-based-billing/ingestion-strategies/ingestion-strategy Ingestion strategies for Usage Based Billing Polar offers an ingestion framework to work with Polar's event ingestion API. Want to report events regarding Large Language Model usage, S3 file uploads or something else? Our Ingestion strategies are customized to make it as seamless as possible to fire ingestion events for complex needs. * [LLM Strategy](/features/usage-based-billing/ingestion-strategies/llm-strategy) * [S3 Strategy](/features/usage-based-billing/ingestion-strategies/s3-strategy) * [Stream Strategy](/features/usage-based-billing/ingestion-strategies/stream-strategy) * [Delta Time Strategy](/features/usage-based-billing/ingestion-strategies/delta-time-strategy) ### Help us improve We're always looking for ways to improve our ingestion strategies. Feel free to contribute โ€” [Polar Ingestion SDK](https://github.com/polarsource/polar-ingestion). # LLM Strategy Source: https://polar.sh/docs/features/usage-based-billing/ingestion-strategies/llm-strategy Ingestion strategy for LLM Usage ## Javascript SDK ### LLM Strategy Wrap any LLM model from the `@ai-sdk/*` library, to automatically fire prompt- & completion tokens used by every model call. ``` pnpm add @polar-sh/ingestion ai @ai-sdk/openai ``` ```typescript theme={null} import { Ingestion } from "@polar-sh/ingestion"; import { LLMStrategy } from "@polar-sh/ingestion/strategies/LLM"; import { generateText } from "ai"; import { openai } from "@ai-sdk/openai"; // Setup the LLM Ingestion Strategy const llmIngestion = Ingestion({ accessToken: process.env.POLAR_ACCESS_TOKEN }) .strategy(new LLMStrategy(openai("gpt-4o"))) .cost((ctx) => ({ amount: 123, currency: "usd" })) // Optional: Set the cost of the LLM usage .ingest("openai-usage"); export async function POST(req: Request) { const { prompt }: { prompt: string } = await req.json(); // Get the wrapped LLM model with ingestion capabilities // Pass Customer Id to properly annotate the ingestion events with a specific customer const model = llmIngestion.client({ customerId: "xxx", }); const { text } = await generateText({ model, system: "You are a helpful assistant.", prompt, }); return Response.json({ text }); } ``` #### Ingestion Payload ```json theme={null} { "customerId": "123", "name": "openai-usage", "metadata": { "inputTokens": 100, "outputTokens": 200, "cachedInputTokens": 10, "totalTokens": 300, "model": "gpt-4o", "provider": "openai.responses", "strategy": "LLM", "_cost": { "amount": 123, // Amount is expected to be in cents. $1.23 should be represented as 123 "currency": "usd" }, "_llm": { ... // } } } ``` ## Python SDK Our Python SDK includes an ingestion helper and strategies for common use cases. It's installed as part of the Polar SDK. ```bash pip theme={null} pip install polar-sdk ``` ```bash uv theme={null} uv add polar-sdk ``` ### Ingestion helper The ingestion helper is a simple wrapper around the Polar events ingestion API. It takes care of batching and sending events to Polar in the background, without blocking your main thread. ```python theme={null} import os from polar_sdk.ingestion import Ingestion ingestion = Ingestion(os.getenv("POLAR_ACCESS_TOKEN")) ingestion.ingest({ "name": "my-event", "external_customer_id": "CUSTOMER_ID", "metadata": { "usage": 13.37, } }) ``` ### PydanticAI Strategy [PydanticAI](https://ai.pydantic.dev) is an AI agent framework for Python. A common use-case with AI applications is to track the usage of LLMs, like the number of input and output tokens, and bill the customer accordingly. With our PydanticAI strategy, you can easily track the usage of LLMs and send the data to Polar for billing. ```python theme={null} import os from polar_sdk.ingestion import Ingestion from polar_sdk.ingestion.strategies import PydanticAIStrategy from pydantic import BaseModel from pydantic_ai import Agent ingestion = Ingestion(os.getenv("POLAR_ACCESS_TOKEN")) strategy = ingestion.strategy(PydanticAIStrategy, "ai_usage") class MyModel(BaseModel): city: str country: str agent = Agent("gpt-4.1-nano", output_type=MyModel) if __name__ == '__main__': result = agent.run_sync("The windy city in the US of A.") print(result.output) strategy.ingest("CUSTOMER_ID", result) ``` *This example is inspired from the [Pydantic Model example](https://ai.pydantic.dev/examples/pydantic-model/) of PydanticAI documentation.* #### Ingestion Payload ```json theme={null} { "name": "ai_usage", "external_customer_id": "CUSTOMER_ID", "metadata": { "requests": 1, "total_tokens": 78, "request_tokens": 58, "response_tokens": 20 } } ``` # S3 Strategy Source: https://polar.sh/docs/features/usage-based-billing/ingestion-strategies/s3-strategy Ingestion strategy for S3 Operations ## Javascript SDK Wrap the official AWS S3 Client with our S3 Ingestion Strategy to automatically ingest bytes uploaded. ``` pnpm add @polar-sh/ingestion @aws-sdk/client-s3 ``` ```typescript theme={null} import { Ingestion } from "@polar-sh/ingestion"; import { S3Strategy } from "@polar-sh/ingestion/strategies/S3"; import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"; const s3Client = new S3Client({ region: process.env.AWS_REGION, endpoint: process.env.AWS_ENDPOINT_URL, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID!, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, }, }); // Setup the S3 Ingestion Strategy const s3Ingestion = Ingestion({ accessToken: process.env.POLAR_ACCESS_TOKEN }) .strategy(new S3Strategy(s3Client)) .ingest("s3-uploads"); export async function POST(request: Request) { try { // Get the wrapped S3 Client // Pass Customer Id to properly annotate the ingestion events with a specific customer const s3 = s3Ingestion.client({ customerId: request.headers.get("X-Polar-Customer-Id") ?? "", }); await s3.send( new PutObjectCommand({ Bucket: process.env.AWS_BUCKET_NAME, Key: "a-random-key", Body: JSON.stringify({ name: "John Doe", age: 30, }), ContentType: "application/json", }) ); return Response.json({}); } catch (error) { return Response.json({ error: error.message }); } } ``` #### Ingestion Payload ```json theme={null} { "customerId": "123", "name": "s3-uploads", "metadata": { "bytes": 100, "bucket": "my-bucket", "key": "my-key", "contentType": "application/text" } } ``` # Stream Strategy Source: https://polar.sh/docs/features/usage-based-billing/ingestion-strategies/stream-strategy Ingestion strategy for Readable & Writable Streams ## Javascript SDK Wrap any Readable or Writable stream of choice to automatically ingest the bytes consumed. ``` pnpm add @polar-sh/ingestion ``` ```typescript theme={null} import { Ingestion } from '@polar-sh/ingestion'; import { StreamStrategy } from '@polar-sh/ingestion/strategies/Stream'; const myReadstream = createReadStream(...); // Setup the Stream Ingestion Strategy const streamIngestion = Ingestion({ accessToken: process.env.POLAR_ACCESS_TOKEN }) .strategy(new StreamStrategy(myReadstream)) .ingest("my-stream"); export async function GET(request: Request) { try { // Get the wrapped stream // Pass Customer Id to properly annotate the ingestion events with a specific customer const stream = streamIngestion.client({ customerId: request.headers.get("X-Polar-Customer-Id") ?? "" }); // Consume stream... stream.on('data', () => ...) return Response.json({}); } catch (error) { return Response.json({ error: error.message }); } } ``` #### Ingestion Payload ```json theme={null} { "customerId": "123", "name": "my-stream", "metadata": { "bytes": 100 } } ``` # Introduction Source: https://polar.sh/docs/features/usage-based-billing/introduction Usage based billing using ingested events Usage Based Billing is a new feature. We have a lot in store and welcome feedback! ## Overview Polar has a powerful Usage Based Billing infrastructure that allows you to charge your customers based on the usage of your application. This is done by ingesting events from your application, creating Meters to represent that usage, and then adding metered prices to Products to charge for it. ## Concepts ### Events Events are the core of Usage Based Billing. They represent *some* usage done by a customer in your application. Typical examples of events are: * A customer consumed AI LLM tokens * A customer streamed minutes of video * A customer uploaded a file to your application Events are sent to Polar using the [Events Ingestion API](/api-reference/events/ingest) and are stored in our database. An event consists of the following fields: * A `name`, which is a string that can be used to identify the type of event. For example, `ai_usage`, `video_streamed` or `file_uploaded`. * A `customer_id` or `external_customer_id`, which is Polar's customer ID or your user's ID. This is used to identify the customer that triggered the event. * A `metadata` object, which is a JSON object that can contain any additional information about the event. This is useful for storing information that can be used to filter the events or compute the actual usage. For example, you can store the duration of the video streamed or the size of the file uploaded. Here is an example of an event: ```json theme={null} { "name": "ai_usage", "external_customer_id": "cus_123", "metadata": { "model": "gpt-4.1-nano", "requests": 1, "total_tokens": 77, "request_tokens": 58, "response_tokens": 19 } } ``` ### Meters Meters are there to filter and aggregate the events that are ingested. Said another way, this is how you define what usage you want to charge for, based on the events you send to Polar. For example: * AI usage meter, which filters the events with the name `ai_usage` and sums the `total_tokens` field. * Video streaming meter, which filters the events with the name `video_streamed` and sums the `duration` field. * File upload meter, which filters the events with the name `file_uploaded` and sums the `size` field. You can create and manage your meters from the dashboard. Polar is then able to compute the usage over time, both globally and per customer. ### Metered Price A metered price is a price that is based on the usage of a meter, which is computed by filtering aggregating the events that are ingested. This is how you charge your customers for the usage of your application. ### Meter Credits benefit You can give credits to your customers on a specific meter. This is done by creating a Meter Credits Benefit, which is a special type of benefit that allows you to give credits to your customers on a specific meter. On a recurring product, the customer will be credited the amount of units specified in the benefit at the beginning of every subscription cycle period โ€” monthly or yearly. ## Quickstart Get up and running in 5 minutes Meters consist of filters and an aggregation function. The filter is used to filter the events that should be included in the meter and the aggregation function is used to compute the usage. To enable usage based billing for a Product, you need to add a metered price to the Product. Metered prices are only applicable to Subscription Products. Now you're ready to ingest events from your application. Sending events which match the meter's filter will increment the meter's usage for the customer. Customers can view their estimated charges for each meter in the Customer Portal. # Meters Source: https://polar.sh/docs/features/usage-based-billing/meters Creating and managing meters for Usage Based Billing Meters are there to filter and aggregate the events that are ingested. Said another way, this is how you define what usage you want to charge for, based on the events you send to Polar. For example: * AI usage meter, which filters the events with the name `ai_usage` and sums the `total_tokens` field. * Video streaming meter, which filters the events with the name `video_streamed` and sums the `duration` field. * File upload meter, which filters the events with the name `file_uploaded` and sums the `size` field. You can create and manage your meters from the dashboard. Polar is then able to compute the usage over time, both globally and per customer. ## Creating a Meter To create a meter, navigate to the Meters page in the sidebar and click the "Create Meter" button. ## Filters A filter is a set of clauses that are combined using conjunctions. They're used to filter events that you've ingested into Polar. ### Clauses A clause is a condition that an event must meet to be included in the meter. #### Property Properties are the properties of the event that you want to filter on. If you want to match on a metadata field, you can use the metadata key directly. No need to include a `metadata.` prefix. #### Operator Operators are the operators that you want to use to filter the events. * **Equals** * **Not equals** * **Greater Than** * **Greater Than or Equals** * **Less Than** * **Less Than or Equals** * **Contains** * **Does Not Contain** #### Value Values are automatically parsed in the filter builder. They're parsed in the following order: 1. Number โ€” Tries to parse the value as number 2. Boolean โ€” Checks if value is "true" or "false" 3. String โ€” Treats value as string as fallback ### Conjunctions A conjunction is a logical operator that combines two or more clauses. * **and** โ€” All clauses must be true for the event to be included. * **or** โ€” At least one clause must be true for the event to be included. ## Aggregation The aggregation is the function that is used to aggregate the events that match the filter. For example, if you want to count the number of events that match the filter, you can use the **Count** aggregation. If you want to sum the value of a metadata field, you can use the **Sum** aggregation. * **Count** โ€” Counts the number of events that match the filter. * **Sum** โ€” Sums the value of a property. * **Average** โ€” Computes the average value of a property. * **Minimum** โ€” Computes the minimum value of a property. * **Maximum** โ€” Computes the maximum value of a property. * **Unique** โ€” Counts the number of unique values of a property. Consider the following events: ```json theme={null} [ { "name": "ai_usage", "external_customer_id": "cus_123", "metadata": { "total_tokens": 10 } }, { "name": "ai_usage", "external_customer_id": "cus_123", "metadata": { "total_tokens": 20 } }, { "name": "ai_usage", "external_customer_id": "cus_123", "metadata": { "total_tokens": 30 } }, { "name": "ai_usage", "external_customer_id": "cus_123", "metadata": { "total_tokens": 30 } } ] ``` Here is the result of each aggregation function, over the `total_tokens` metadata property: * **Count**: 4 units * **Sum**: 90 units * **Average**: 22.5 units * **Minimum**: 10 units * **Maximum**: 30 units * **Unique**: 3 units If you want to use a metadata property in the aggregation, you can use the metadata property directly. No need to include a `metadata.` prefix. ## Example The following Meter Filter & Aggregation will match events that have the name `openai-usage` and sum units over metadata property `completionTokens`. You can **Preview** the events matched by the meter while creating it. ## Good to know A few things to keep in mind when creating and managing meters: ### Renaming a Meter Until [https://github.com/polarsource/polar/issues/6490](https://github.com/polarsource/polar/issues/6490) is fixed, please [contact support](/support) to rename the meter for you. ### Updating a Meter You may update a meter's filters or aggregation function as long as the meter doesn't have any processed events or does not have any customer purchase associated with it. # How to allow multiple subscriptions per customer Source: https://polar.sh/docs/guides/allow-multiple-subscriptions-per-customer Learn how to allow multiple subscriptions per customer in Polar. In the Polar dashboard sidebar, click on **Settings**. You can also go directly to:\ `https://polar.sh/dashboard/${org_slug}/settings`\ Scroll down to **Subscriptions** section. **Toggle ON** Allow multiple subscriptions to allow multiple subscriptions per customer. Click **Save** in the **Subscriptions** section to save the changed settings. # How to automatically share links to customers after purchase Source: https://polar.sh/docs/guides/automate-post-purchase-link-sharing Learn how to use the Custom Benefit to automatically share links with customers after purchase. ## Create a Custom Benefit In the Polar dashboard sidebar, click on **Benefits**. You can also go directly to `https://polar.sh/dashboard/${org_slug}/products/benefits`. Click on **Create Benefit**. Youโ€™ll see three configuration fields as shown in the image below: **Description**, **Type**, and **Private note**. The **Type** field is set to Custom by default (which is what we need). The **Private note** field is specific to the Custom **Type**. If you select a different **Type**, youโ€™ll see configuration options specific to that type instead. Fill the **Description** and **Private note** fields where: * **Description** accepts regular text and is the title of the link that you want to share with the customer. * **Private note** uses the [Markdown format](https://www.markdownguide.org/basic-syntax/) so you can format text, add links, or lists. Make sure to add your link inside the **Private note** section so that it's only accessible post a payment. Then, click the **Create** button to save the configuration. ## Create a product using the Custom Benefit In the Polar dashboard sidebar, navigate to **Products** > **Catalogue** for your organization. You can also go directly to `https://polar.sh/dashboard/${org_slug}/products`. Click on **New Product**. Fill out the Product information. On the product creation page, scroll to the bottom to find the **Automated Benefits** section. Click on Custom to see all the custom benefits youโ€™ve created, and then toggle ON the one you want to enable. If you donโ€™t want to use an existing Custom benefit, you can create a new one by clicking **Create new**. Enter the configuration details, following the [Step 3](#configuration-fill) of [Create a Custom Benefit](#create-a-custom-benefit) section. Click on **Create Product** button in the product creation form. Your product is successfully created along with the Custom benefit that allows you to share the links automatically to customers after purchase. ## How the Custom Benefit Appears During Checkout Your Custom Benefit is visible to customers at multiple stages of their purchase journey: ### Checkout Page When a customer opens the checkout session, theyโ€™ll see the **Description of your Custom Benefit** listed under **Included**. ### After Purchase Once the customer completes their purchase, the **Description**, the **Benefit Type** (in this case, **Custom**), and the **rendered Markdown content** from the **Private note** of the **Custom Benefit** are displayed, allowing them to access any links or formatted text youโ€™ve added. ### Purchase Confirmation Email The **Description** and the rendered Markdown content of the **Custom Benefit** also appear in the purchase confirmation email sent to the customer, as shown below. ### Customer Portal When the customer opens the [Customer Portal](/features/customer-portal) through the link in their confirmation email, the **Custom Benefit** is displayed there as well. # How to Change Account Email (as a Merchant) Source: https://polar.sh/docs/guides/change-email-as-merchant Learn how to change your account email on Polar as a Merchant. To access account settings, open the menu in the bottom left corner in the dashboard and click `Account Settings`. Click `Change Email` button in the `Account Connections` section to start changing your email. Enter the new email in the input box that appears. Polar would send an email to the new email that is entered in the previous step. Click the `Update my email` button in the email. Confirm the email change by pressing the `Update the email` button. # How to Create Checkout Session Source: https://polar.sh/docs/guides/create-checkout-session Learn how to create checkout sessions via API. ## Creating a checkout session using the API Create a new organization token by following our [Organization Access Tokens](https://polar.sh/docs/integrate/oat) guide. After creating your access token, you will be able to view it. Please copy and save your access token. In the Polar dashboard sidebar, navigate to **Products** > **Catalogue** for your organization. You can also go directly to:\ `https://polar.sh/dashboard/${org_slug}/products` Retrieve the Product IDs for the items you wish to include in checkout by clicking on the **โ‹ฎ (More options) menu** next to chosen products and selecting **Copy Product ID**.\ These IDs will be required in the next step to create a checkout session. Open your terminal and paste the following curl command to make an API call for creating a checkout session. Be sure to replace: * \ with your actual access token * \, \, etc., with the product IDs you want to include in the session ```bash Terminal theme={null} curl --request POST \ --url https://api.polar.sh/v1/checkouts/ \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "products": [ "", "", "" ] }' ``` Find the description of all the parameters in the [Create Checkout Session API](https://polar.sh/docs/api-reference/checkouts/create-session). The curl command returns a JSON. Access the checkout URL from the "url" key of the JSON. ```json theme={null} { "...": "...", "url": "https://buy.polar.sh/polar_c_...", // [!code ++] "...": "..." } ``` # How to Create Multiple Organizations Source: https://polar.sh/docs/guides/create-multiple-organizations Learn how to create multiple organizations. To create your first organization, sign up on [Polar](https://polar.sh). To create an organization in addition to your first organization, open the menu in the bottom left corner in the dashboard and click `New Organization`. Now that you have multiple organizations, to switch between then, just open the menu in the bottom left corner in the dashboard, and then select the desired organization you want to switch to. # How to create product variants Source: https://polar.sh/docs/guides/create-variants Learn how create product variants in Polar and how customers can easily switch between them in the customer portal. ## Creating product variants (3 subscription products) In the Polar dashboard sidebar, navigate to **Products** > **Catalogue** for your organization. You can also go directly to:\ `https://polar.sh/dashboard/${org_slug}/products` Click on **New Product** and fill the product information. Here, we are creating a monthly subscription product **Basic version** with cost \$20. Your product catalogue should now show this product as follows: Click on **New Product** and create two more subscription products one by one. * **Mid version**: Fill the product information. We create a monthly subscription product named **Mid version** with cost \$30. * **Advanced version**: We create a monthly subscription product named **Advanced version** with cost \$40. * **Product Catalogue**: You should be able to see all your products on Product Catalogue. ## Creating checkout links with variants In the Polar dashboard sidebar, navigate to **Products** > **Checkout Links**. You can also go directly to:\ `https://polar.sh/dashboard/${org_slug}/products/checkout-links` Click on **New Link** and select all your products which you want to offer as variants. At this step, you may add a label, success URL and metadata. You may also configure whether discount codes are allowed and whether billing address is required from customers. Click on **Create Link** after adding your configurations. You should be able to see your label name in **Checkout Links**. In our case, **3 products** is the default label name assigned by the system. Click on your label to see the checkout link. You can copy this link and share to your customers for them to purchase a variant. ## Purchasing from variants On opening the checkout link, the customer will need to select the variant they want to purchase and fill their email address and card details. Once the customer has purchased the subscription, they will receive an email containing the link to access their purchase. ## Downgrading to another product variant On opening the link from the email received, the customer needs to click on **Change Plan**. Then, they can select the plan they want to downgrade to and click on **Change Plan**. Now, the product is changed to **Basic version** instead of **Mid version** on the portal. ## Upgrading to another product variant On opening the link from the email received, the customer needs to click on **Change Plan**. Then, they need to select the variant they want to upgrade to, **Advanced version** and click on **Change Plan**. Now, the product is changed to **Advanced version** on the portal. # How to Customize Benefits Order in Checkouts Source: https://polar.sh/docs/guides/customize-benefits-order-in-checkouts Learn how to customize the order in which Benefits appear in checkouts. ## Create a Product with Benefits * [Create a Custom Benefit](https://polar.sh/docs/guides/automate-post-purchase-link-sharing#create-a-custom-benefit) * [Create a File Downloads Benefit](https://polar.sh/docs/features/benefits/file-downloads#create-downloadable-benefit) [Create a Product](https://polar.sh/docs/features/products#create-a-product) that includes the two **Benefits** created above. Instead of creating the Benefits beforehand, you can also create them while creating the product by clicking the `Create New` button under the desired Benefit Type in the Automated Benefits section of the product configuration. [Create a Checkout Link](https://polar.sh/docs/features/checkout/links#create-a-checkout-link) for the product. The checkout session looks like below. The Custom Benefit named **Product Link** appears first, followed by the File Downloads Benefit named **Product File**. ## Reorder Benefits In the Polar dashboard sidebar, navigate to **Products** > **Catalogue** for your organization. You can also go directly to `https://polar.sh/dashboard/${org_slug}/products`. Choose the product whose Benefits you want to reorder, click on it and scroll down to the **Automated Benefits** section. Click the `Reorder` button to change the order of benefits. Drag the Benefits up or down to change their order as desired. Open the product checkout page. The benefits have been reordered successfully. You can also Reorder the Benefits while creating the Product itself by clicking on `Reorder` button in Automated Benefits section. # How to Customize Products Order in Checkouts Source: https://polar.sh/docs/guides/customize-products-order-in-checkouts Learn how to customize the order in which products appear in checkouts. Currently, customizing the products order in checkout is only supported via the APIs. ## Create Organization Access Token and Product IDs Create a new organization token by following our [Organization Access Tokens](https://polar.sh/docs/integrate/oat) guide. After creating your access token, you will be able to view it. Please copy and save your access token. In the Polar dashboard sidebar, navigate to **Products** > **Catalogue** for your organization. You can also go directly to `https://polar.sh/dashboard/${org_slug}/products`. Retrieve the Product IDs for the items you wish to include in checkout by clicking on the **โ‹ฎ (More options) menu** next to chosen products and selecting **Copy Product ID**.\ These IDs will be required in the next step to create a checkout. ## Reordering Products In Checkout Links API Open your terminal and paste the following curl command to make an API call for creating a checkout link. Be sure to replace: * \ with your actual access token. Make sure your token has the **`checkout_links:write`** scope enabled to use [Create Checkout Link API](/api-reference/checkout-links/create). * \, \, etc., with the product IDs in the order you want them to appear in the checkout. ```bash Terminal theme={null} curl --request POST \ --url https://api.polar.sh/v1/checkout-links/ \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "products": [ "", "" ] }' ``` Find all the available parameters along with the description in the [Create Checkout Link API](https://polar.sh/docs/api-reference/checkout-links/create). The curl command returns a JSON. Access the checkout link URL from the "url" key of the JSON and open it. ```json theme={null} { "...": "...", "...": "...", "url": "https://buy.polar.sh/polar_cl_..." // [!code ++] } ``` It looks like below: If you want to change the order and make Product 2 appear before Product 1, place Product 2's ID first, followed by Product 1's ID in the API call. ```bash Terminal theme={null} curl --request POST \ --url https://api.polar.sh/v1/checkouts/ \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "products": [ "", "" ] }' ``` The checkout looks like below: ## Reordering Products In Checkout Session API Open your terminal and paste the following curl command to make an API call for creating a checkout session. Be sure to replace: * \ with your actual access token. Make sure your token has the **`checkouts:write`** scope enabled to use [Create Checkout Session API](/api-reference/checkouts/create-session). * \, \, etc., with the product IDs in the order you want them to appear in the checkout. ```bash Terminal theme={null} curl --request POST \ --url https://api.polar.sh/v1/checkouts/ \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "products": [ "", "" ] }' ``` Find all the available parameters along with the description in the [Create Checkout Session API](https://polar.sh/docs/api-reference/checkouts/create-session). The curl command returns a JSON. Access the checkout session URL from the "url" key of the JSON and open it. ```json theme={null} { "...": "...", "url": "https://buy.polar.sh/polar_c_...", // [!code ++] "...": "..." } ``` It looks like below: If you want to change the order and make Product 2 appear before Product 1, place Product 2's ID first, followed by Product 1's ID in the API call. ```bash Terminal theme={null} curl --request POST \ --url https://api.polar.sh/v1/checkouts/ \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "products": [ "", "" ] }' ``` The checkout looks like below: # How to disable email editing in checkout Source: https://polar.sh/docs/guides/disable-email-editing-in-checkout Learn how to prevent customers from editing their email address during checkout by linking to existing customers. When you want to prevent customers from changing their email address during the checkout process, you can link the checkout session to an existing customer. This is useful when customers are already authenticated in your application and you want to ensure the purchase is associated with their verified account. ## Overview By passing either `customer_id` or `external_customer_id` when creating a checkout session, Polar will: * Pre-fill the customer's information in the checkout form * **Disable the email field** so it cannot be edited * Link the resulting order to the specified customer ## Using Customer ID If you've already created a customer in Polar and have their Polar customer ID, you can use it directly. Retrieve the customer ID from your Polar dashboard or through the [Customers API](/api-reference/customers/list). The customer ID is a UUID format like: `992fae2a-2a17-4b7a-8d9e-e287cf90131b` Pass the `customer_id` parameter when creating the checkout session. ```ts TypeScript theme={null} import { Polar } from "@polar-sh/sdk"; const polar = new Polar({ accessToken: process.env["POLAR_ACCESS_TOKEN"] }); const checkout = await polar.checkouts.create({ products: [""], customerId: "992fae2a-2a17-4b7a-8d9e-e287cf90131b", // [!code ++] }); console.log(checkout.url); ``` ```py Python theme={null} from polar_sdk import Polar with Polar( access_token="", ) as polar: checkout = polar.checkouts.create(request={ "products": [""], "customer_id": "992fae2a-2a17-4b7a-8d9e-e287cf90131b", # [!code ++] }) print(checkout.url) ``` ```bash cURL theme={null} curl --request POST \ --url https://api.polar.sh/v1/checkouts/ \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "products": [""], "customer_id": "992fae2a-2a17-4b7a-8d9e-e287cf90131b" }' ``` Redirect your customer to the checkout URL returned in the response. The email field will be pre-filled and disabled for editing. ## Using External Customer ID If you have your own user management system, you can use your internal customer ID. This is the recommended approach as it makes reconciliation between your system and Polar easier. When creating a checkout session, pass your application's user ID as the `external_customer_id`. Polar will: * Look for an existing customer with this external ID * If found, link to that customer and pre-fill their data ```ts TypeScript theme={null} import { Polar } from "@polar-sh/sdk"; const polar = new Polar({ accessToken: process.env["POLAR_ACCESS_TOKEN"] }); const checkout = await polar.checkouts.create({ products: [""], externalCustomerId: "user_12345", // Your application's user ID // [!code ++] }); console.log(checkout.url); ``` ```py Python theme={null} from polar_sdk import Polar with Polar( access_token="", ) as polar: checkout = polar.checkouts.create(request={ "products": [""], "external_customer_id": "user_12345", # Your application's user ID # [!code ++] }) print(checkout.url) ``` ```bash cURL theme={null} curl --request POST \ --url https://api.polar.sh/v1/checkouts/ \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "products": [""], "external_customer_id": "user_12345" }' ``` Redirect your customer to the checkout URL. The email field will be disabled from editing. # How to disable subscription upgrades/downgrades in customer portal Source: https://polar.sh/docs/guides/disable-subscription-changes-in-customer-portal Learn how to disable the option for customers to upgrade or downgrade subscription plans from the customer portal. In the Polar dashboard sidebar, click on **Settings**. You can also go directly to:\ `https://polar.sh/dashboard/${org_slug}/settings`\ Scroll down to **Subscriptions** section. **Toggle OFF** Allow price changes to prevent customers from upgrading or downgrading their subscriptions from the customer portal. Click **Save** in the **Subscriptions** section to save the changed settings. # How to Grant Meter Credits After Purchase Source: https://polar.sh/docs/guides/grant-meter-credits-after-purchase Learn how to automatically grant meter credits to users after purchase using the Meter Credits benefits or Webhooks. ## Overview When building usage-based billing systems, you may want to give new customers an initial credit balance (e.g., 10 free units) when they purchase a product. This is useful for: * Offering free trials with credits * Onboarding bonuses * Promotional credits for new signups or purchases There are two ways to grant initial meter credits in Polar: 1. **Meter Credits Benefit (Recommended)** - Automatically grant credits when a customer purchases a product 2. **Webhook + Events API** - Programmatically grant credits for advanced use cases ### Which Method Should I Use? | Feature | Meter Credits Benefit | Webhook + Events API | | ---------------------- | ----------------------------- | ----------------------------------------- | | **Setup Complexity** | โœ… Simple - No code required | โš™๏ธ Advanced - Requires coding | | **Automatic Credits** | โœ… Yes | โŒ No - Manual implementation | | **Custom Logic** | โŒ No | โœ… Yes - Full control | | **One-time Products** | โœ… Credits granted at purchase | โœ… Credits granted at purchase | | **Recurring Products** | โŒ No | โœ… Can be credited every cycle (with code) | | **Best For** | Most use cases | Complex crediting rules | **Start with Method 1** (Meter Credits Benefit) unless you need custom logic or complex crediting rules. It's simpler and requires no code. *** ## Method 1: Using Meter Credits Benefit (Recommended) The simplest way to grant initial credits is to use the built-in **Meter Credits** benefit. This automatically grants credits (once) to customers when they purchase a product. ### Step 1: Create a Meter with Sum Aggregation First, [create a meter](/features/usage-based-billing/meters) that will track your customers usage. ### Step 2: Create a Meter Credits Benefit Now [create a Meter Credits benefit](/features/benefits/credits) that will grant the initial credits. ### Step 3: Create a Product with the Benefit Create a product and attach the Meter Credits benefit to it. Navigate to **Products** > **Catalogue** and click **New Product**. * Set a name and description * Choose your product type (Recurring) * Set the price (can be \$0 for free signup, or any amount) * Click "Add Additional Price" * Attach your meter to the product and set the per unit cost Scroll to the **Automated Benefits** section and toggle ON the Meter Credits benefit you created. Click **Create Product** to save. That's it! Now when a customer purchases this product, they will automatically receive the specified amount of meter credits. *** ## Method 2: Using Webhooks + Events API (Advanced) For more complex scenarios where you need custom logic or want to grant credits outside the purchase flow, you can use webhooks and the Events API. ### When to Use This Method * You need custom logic to determine credit amounts * You want to grant credits based on external events * You need to grant credits to existing customers programmatically * You want to implement complex crediting rules ### How It Works This approach involves: 1. Creating a product with a meter attached (using sum aggregation) 2. Setting up webhooks to listen for purchases 3. When a purchase is made, ingesting a negative event value to grant credits **Why negative values?** When you ingest an event with a negative value (e.g., `-10`) to a meter using **Sum** aggregation, it effectively grants the customer 10 units of credit, reducing their usage meter balance. ### Prerequisites * A Polar account with an organization * A meter created with **Sum** aggregation * A product with the meter attached * Webhooks enabled * A Polar access token for API calls ### Step 1: Create a Meter First, create a meter that will track your customers' usage. In the Polar dashboard sidebar, click on **Products** > **Meters**. Click **Create Meter** and configure: * **Name**: Give your meter a descriptive name (e.g., "API Calls" or "Storage Usage") * **Filter**: Add filters to match your usage events (e.g., name equals "api\_usage") * **Aggregation**: Select **Sum** and enter the property to sum (e.g., `units`) The meter **must use Sum aggregation** for this approach to work. Save your meter and note down the meter name - you'll need this when ingesting events. Learn more about [creating meters](/features/usage-based-billing/meters). ### Step 2. Create a Product Follow the same steps as Method 1 to create your product (you don't need to create or attach the Meter Credits benefit for this approach). ### Step 3: Set Up Webhooks Configure webhooks to receive notifications when users make purchases. Follow our [Setup Webhooks](/integrate/webhooks/endpoints) guide to create a new webhook endpoint. When configuring your webhook, make sure to subscribe to the `order.paid` event. This event is triggered when a customer successfully completes a purchase. Store your webhook secret securely in your environment variables. ```bash Terminal theme={null} POLAR_ACCESS_TOKEN="polar_pat_..." POLAR_WEBHOOK_SECRET="whsec_..." PRODUCT_ID="prod_..." # The product ID to grant credits for ``` ### Step 4: Implement the Webhook Handler Create a webhook handler that listens for `order.paid` events and grants initial credits by ingesting negative event values. #### Next.js Example ```typescript icon="square-js" title="app/api/webhook/polar/route.ts" theme={null} import { Polar } from "@polar-sh/sdk"; import { Webhooks } from "@polar-sh/nextjs"; const polar = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN }); export const POST = Webhooks({ webhookSecret: process.env.POLAR_WEBHOOK_SECRET, onOrderPaid: async (order) => { // Check if this is the product we want to grant credits for const targetProductId = process.env.PRODUCT_ID; if (order.data.product_id === targetProductId) { // Grant 10 credits by ingesting a negative event await polar.events.ingest({ events: [ { name: "meter-name", customerId: order.data.customer_id, metadata: { units: -10, // Negative value grants credits reason: "initial_signup_bonus", }, }, ], }); console.log(`Granted 10 credits to customer ${order.data.customer_id}`); } }, }); ``` ### Step 5: Test Your Integration Test that credits are properly granted when a customer makes a purchase. Test your integration in Polar's [sandbox environment](/integrate/sandbox) to avoid affecting production data. Create a checkout session and complete a test purchase. Check your server logs to confirm the `order.paid` webhook was received. Use the [Customer Meters API](/api-reference/customer-meters/list) to verify the credits were applied: ```typescript theme={null} const meters = await polar.customerMeters.list({ customerId: "cus_...", }); console.log(meters.items[0].balance); // Should show -10 (or your credit amount) ``` ### Important Considerations for Webhook Method #### Negative Balance vs. Positive Usage When using negative events to grant credits: * A **negative balance** (e.g., `-10`) means the customer has 10 credits available * As the customer uses your service, positive events reduce this negative balance * When the balance reaches `0`, the customer has used all their credits * Positive balances indicate usage beyond the granted credits #### Example Flow ```typescript theme={null} // Initial state: Customer has 0 balance // You grant 10 credits: balance = -10 // Customer uses 3 units: balance = -7 (7 credits remaining) // Customer uses 5 more units: balance = -2 (2 credits remaining) // Customer uses 3 more units: balance = 1 (1 unit of overage, if metered pricing is enabled) ``` #### Preventing Double Credits To avoid granting credits multiple times, consider: 1. **Check order status** - Only grant credits for new orders 2. **Use idempotency** - Track which orders you've already processed 3. **Database records** - Store a record of credit grants ```typescript theme={null} // Example with idempotency check const hasGrantedCredits = await checkIfAlreadyGranted(order.id); if (!hasGrantedCredits) { await polar.events.ingest({ events: [{ name: "meter-name", customerId: order.data.customer_id, metadata: { units: -10 }, }], }); await recordCreditGrant(order.id); } ``` # How to Grant Meter Credits Before Purchase Source: https://polar.sh/docs/guides/grant-meter-credits-before-purchase Learn how to grant meter credits to customers before they make any purchase using the Polar API. ## Overview You may want to grant usage credits to customers even before they make a purchase. This is useful for: * **Free trial credits** - Give new signups free credits to try your service * **Promotional campaigns** - Grant credits as part of marketing initiatives * **Testing and demos** - Provide credits for product demonstrations This guide shows you how to grant credits to customers who don't have an active subscription or purchase yet. ## How It Works To grant credits before a purchase, you need to: 1. Create a customer in Polar (if they don't exist) 2. Create a meter with **Sum** aggregation to track usage 3. Ingest an event with a **negative value** to grant credits **Why negative values?** When you ingest an event with a negative value (e.g., `-10`) to a meter using **Sum** aggregation, it grants the customer credits. A negative balance means available credits, which get reduced as they use your service. ## Step 1: Create a Meter First, create a meter that will track your customers' usage. In the Polar dashboard sidebar, click on **Products** > **Meters**. Click **Create Meter** and configure: * **Name**: Give your meter a descriptive name (e.g., "API Calls" or "Storage Usage") * **Filter**: Add filters to match your usage events (e.g., name equals "api\_usage") * **Aggregation**: Select **Sum** and enter the property to sum (e.g., `units`) The meter **must use Sum aggregation** for this approach to work. Save your meter and note down the meter name - you'll need this when ingesting events. Learn more about [creating meters](/features/usage-based-billing/meters). ## Step 2: Create a Customer If your customer doesn't already exist in Polar, you need to create them first. ### Option A: Create via Dashboard In the Polar dashboard sidebar, click on **Customers**. Click **Add Customer** and fill in: * **Email**: Customer's email address (required) * **Name**: Customer's full name (optional) * **External ID**: Your internal user ID for easy reference (optional but recommended) Click **Save** and note down the Customer ID. ### Option B: Create via API Use the Polar SDK or API to create a customer programmatically: ```typescript icon="square-js" title="create-customer.ts" theme={null} import { Polar } from "@polar-sh/sdk"; const polar = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN, }); // Create a new customer await polar.customers.create({ email: "user@example.com", name: "John Doe", externalId: "user_123", // Your internal user ID (optional) }); ``` Use the `externalId` field to link Polar customers with your internal user system. This allows you to reference customers without storing Polar's internal ID. ### Using External ID If you set an `externalId` when creating the customer, you can use it in event ingestion instead of the Polar customer ID: ```typescript theme={null} // Instead of using customerId, use externalCustomerId await polar.events.ingest({ events: [{ name: "api_usage", externalCustomerId: "user_123", // Your internal ID metadata: { units: -10 } }] }); ``` Learn more about [customer management](/features/customer-management). ## Step 3: Grant Credits by Ingesting a Negative Event Now that you have a customer and a meter, grant credits by ingesting an event with a negative value. ### Using the Polar SDK ```typescript icon="square-js" title="grant-credits.ts" theme={null} import { Polar } from "@polar-sh/sdk"; const polar = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN, }); async function grantCredits(customerId: string, credits: number) { await polar.events.ingest({ events: [ { customerId, name: "api_usage", // Must match your meter's filter name metadata: { units: -credits, // Negative value grants credits }, }, ], }); console.log(`Granted ${credits} credits to customer ${customerId}`); } // Grant 10 credits to a customer await grantCredits("cus_abc123", 10); ``` ### Using External Customer ID If you're using `externalId` for customer management: ```typescript icon="square-js" title="grant-credits-external.ts" theme={null} import { Polar } from "@polar-sh/sdk"; const polar = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN, }); async function grantCreditsToExternalUser(externalUserId: string, credits: number) { await polar.events.ingest({ events: [ { name: "api_usage", // Must match your meter's filter name externalCustomerId: externalUserId, // Use your internal ID metadata: { units: -credits, // Negative value grants credits }, }, ], }); console.log(`Granted ${credits} credits to user ${externalUserId}`); } // Grant 10 credits using your internal user ID await grantCreditsToExternalUser("user_123", 10); ``` ## Step 4: Verify Credits Were Granted Check that the credits were successfully granted to the customer. ### Using Customer Meters API ```typescript theme={null} import { Polar } from "@polar-sh/sdk"; const polar = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN, }); // Check customer's meter balance const meters = await polar.customerMeters.list({ customerId: "cus_abc123", }); meters.items.filter((balance) => balance > 0).forEach((meter) => { console.log(`${Math.abs(meter.balance)} credits available for ${meter.meter_id}.`); }); ``` ### Example Usage Flow ```typescript theme={null} // Initial state: Customer created, 0 balance // Grant 10 credits: balance = 10 // Customer uses 3 units await polar.events.ingest({ events: [{ name: "api_usage", customerId: "cus_abc123", metadata: { units: 3 } // Positive value for usage }] }); // Balance is now 7 (7 credits remaining) // Customer uses 8 more units await polar.events.ingest({ events: [{ name: "api_usage", customerId: "cus_abc123", metadata: { units: 8 } }] }); // Balance is now -1 (used 1 unit beyond credits) ``` # Introduction Source: https://polar.sh/docs/guides/introduction A collection of how-tos with Polar in form of step-by-step guides and tutorials. This section is your starting point for learning how to use Polar with various stacks, environments, etc. Here you will find step-by-step guides and tutorials to help you integrate Polar into your projects, set up your environment, and make the most of our platform. ## Get Started Browse the guides in the sidebar to find the right one based on your needs. If you want to request a new guide, please [create an issue on polarsource/polar](https://github.com/polarsource/polar/issues/new/choose). # Integrate Polar with Laravel Source: https://polar.sh/docs/guides/laravel In this guide, we'll show you how to integrate Polar with Laravel. Consider following this guide while using the Polar Sandbox Environment. This will allow you to test your integration without affecting your production data. ## Polar Laravel Example App We've created a simple example Laravel application that you can use as a reference. [View Code on GitHub](https://github.com/polarsource/polar-laravel) ## Setting up environment variables ### Polar API Key To authenticate with Polar, you need to create an access token, and supply it to Laravel using a `POLAR_API_KEY` environment variable. You can create an organization access token from your organization settings. ## Fetching Polar Products for display ### Creating the Products Controller Go ahead and add the following entry in your `routes/web.php` file: ```php theme={null} // routes/web.php Route::get('/products', [ProductsController::class, 'handle']); ``` Next up, create the `ProductsController` class in the `app/Http/Controllers` directory: ```php theme={null} // app/Http/Controllers/ProductsController.php api.polar.sh when ready to go live // And don't forget to update the .env file with the correct POLAR_ORGANIZATION_ID and POLAR_WEBHOOK_SECRET $data = Http::get('https://sandbox-api.polar.sh/v1/products', [ 'is_archived' => false, ]); $products = $data->json(); return view('products', ['products' => $products['items']]); } } ``` ## Displaying Products Finally, create the `products` view in the `resources/views` directory: ```php theme={null} // resources/views/products.blade.php @foreach ($products as $product)

{{ $product['name'] }}

Buy
@endforeach ``` Notice that we create a link to `/checkout` with a query parameter `priceId`. This is the ID of the price that the user will be charged for when they click the "Buy" button. We will configure this route in the next section. That's it for the products page. You can now display the products to your users, and they will be able to buy them. Let's now create the checkout endpoint. ## Generating Polar Checkout Sessions This endpoint will be responsible for creating a new checkout session, redirecting the user to the Polar Checkout page & redirect back to a configured confirmation page. Go ahead and create a new entry in your `routes/web.php` file: ```php theme={null} // routes/web.php Route::get('/checkout', [CheckoutController::class, 'handle']); ``` Next, create the `CheckoutController` class in the `app/Http/Controllers` directory: ```php theme={null} // app/Http/Controllers/CheckoutController.php query('priceId', ''); // Polar will replace {CHECKOUT_ID} with the actual checkout ID upon a confirmed checkout $confirmationUrl = $request->getSchemeAndHttpHost() . '/confirmation?checkout_id={CHECKOUT_ID}'; // Change from sandbox-api.polar.sh -> api.polar.sh when ready to go live // And don't forget to update the .env file with the correct POLAR_ORGANIZATION_ID and POLAR_WEBHOOK_SECRET $result = Http::withHeaders([ 'Authorization' => 'Bearer ' . env('POLAR_API_KEY'), 'Content-Type' => 'application/json', ])->post('https://sandbox-api.polar.sh/v1/checkouts/custom/', [ 'product_price_id' => $productPriceId, 'success_url' => $confirmationUrl, 'payment_processor' => 'stripe', ]); $data = $result->json(); $checkoutUrl = $data['url']; return redirect($checkoutUrl); } } ``` We can now easily create a checkout session & redirect there by creating a link to `/checkout?priceId={priceId}`. Just like we did when displaying the products above. Upon Checkout success, the user will be redirected to the confirmation page. ## Creating the Confirmation Page Create a new entry in your `routes/web.php` file: ```php theme={null} // routes/web.php Route::get('/confirmation', [ConfirmationController::class, 'handle']); ``` Next, create the `ConfirmationController` class in the `app/Http/Controllers` directory: ```php theme={null} // app/Http/Controllers/ConfirmationController.php api.polar.sh when ready to go live // And don't forget to update the .env file with the correct POLAR_ORGANIZATION_ID and POLAR_WEBHOOK_SECRET $data = Http::withHeaders([ 'Authorization' => 'Bearer ' . env('POLAR_API_KEY'), 'Content-Type' => 'application/json', ])->get('https://sandbox-api.polar.sh/v1/checkouts/custom/' . $request->query('checkout_id')); $checkout = $data->json(); Log::info(json_encode($checkout, JSON_PRETTY_PRINT)); return view('confirmation', ['checkout' => $checkout]); } } ``` The checkout is not considered "successful" yet however. It's initially marked as `confirmed` until you've received a webhook event `checkout.updated` with a status set to `succeeded`. We'll cover this in the next section. ## Handling Polar Webhooks Polar can send you events about various things happening in your organization. This is very useful for keeping your database in sync with Polar checkouts, orders, subscriptions, etc. Configuring a webhook is simple. Head over to your organization's settings page and click on the "Add Endpoint" button to create a new webhook. ### Tunneling webhook events to your local development environment If you're developing locally, you can use a tool like [ngrok](https://ngrok.com/) to tunnel webhook events to your local development environment. This will allow you to test your webhook handlers without deploying them to a live server. Run the following command to start an ngrok tunnel: ```bash Terminal theme={null} ngrok http 3000 ``` ### Add Webhook Endpoint 1. Point the Webhook to `your-app.com/api/webhook/polar`. This must be an absolute URL which Polar can reach. If you use ngrok, the URL will look something like this: `https://.ngrok-free.app/api/webhook/polar`. 2. Select which events you want to be notified about. You can read more about the available events in the [Events section](/api-reference#webhooks). 3. Generate a secret key to sign the requests. This will allow you to verify that the requests are truly coming from Polar. 4. Add the secret key to your environment variables. ```bash Terminal theme={null} # .env POLAR_API_KEY="polar_oat..." POLAR_WEBHOOK_SECRET="..." ``` ### Setting up the Webhook handler First, we need to install the standard-webhooks package to properly decode the incoming webhook payloads. ```bash Terminal theme={null} composer require standard-webhooks/standard-webhooks:dev-main ``` Go and add a `routes/api.php` file and add the following entry: ```php theme={null} // routes/api.php Route::webhooks('/webhook/polar'); ``` Make sure that it is included in the Bootstrap file. ```php theme={null} // bootstrap/app.php withRouting( web: __DIR__.'/../routes/web.php', api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware) { // }) ->withExceptions(function (Exceptions $exceptions) { // })->create(); ``` We will use Spatie's Webhook Client to handle the webhook events. It will automatically verify the signature of the requests, and dispatch the payload to a job queue for processing. ```bash Terminal theme={null} composer require spatie/laravel-webhook-client ``` Let's publish the config: ```bash Terminal theme={null} php artisan vendor:publish --provider="Spatie\WebhookClient\WebhookClientServiceProvider" --tag="webhook-client-config" ``` This will create a new file called webhook-client.php in the config folder. We need to adjust it to properly verify the signature of the requests. ```php theme={null} // config/webhook-client.php [ [ /* * This package supports multiple webhook receiving endpoints. If you only have * one endpoint receiving webhooks, you can use 'default'. */ 'name' => 'default', /* * We expect that every webhook call will be signed using a secret. This secret * is used to verify that the payload has not been tampered with. */ 'signing_secret' => env('POLAR_WEBHOOK_SECRET'), /* * The name of the header containing the signature. */ 'signature_header_name' => 'webhook-signature', /* * This class will verify that the content of the signature header is valid. * * It should implement \Spatie\WebhookClient\SignatureValidator\SignatureValidator */ // 'signature_validator' => \Spatie\WebhookClient\SignatureValidator\DefaultSignatureValidator::class, 'signature_validator' => App\Handler\PolarSignature::class, /* * This class determines if the webhook call should be stored and processed. */ 'webhook_profile' => \Spatie\WebhookClient\WebhookProfile\ProcessEverythingWebhookProfile::class, /* * This class determines the response on a valid webhook call. */ 'webhook_response' => \Spatie\WebhookClient\WebhookResponse\DefaultRespondsTo::class, /* * The classname of the model to be used to store webhook calls. The class should * be equal or extend Spatie\WebhookClient\Models\WebhookCall. */ 'webhook_model' => \Spatie\WebhookClient\Models\WebhookCall::class, /* * In this array, you can pass the headers that should be stored on * the webhook call model when a webhook comes in. * * To store all headers, set this value to `*`. */ 'store_headers' => [], /* * The class name of the job that will process the webhook request. * * This should be set to a class that extends \Spatie\WebhookClient\Jobs\ProcessWebhookJob. */ 'process_webhook_job' => App\Handler\ProcessWebhook::class, ], ], /* * The integer amount of days after which models should be deleted. * * 7 deletes all records after 1 week. Set to null if no models should be deleted. */ 'delete_after_days' => 30, ]; ``` ### Preparing the database By default, all webhook calls get saved into the database. So, we need to publish the migration that will hold the records. So run: ```bash Terminal theme={null} php artisan vendor:publish --provider="Spatie\WebhookClient\WebhookClientServiceProvider" --tag="webhook-client-migrations" ``` This will create a new migration file in the โ€œdatabase/migrationโ€ folder. Then run `php artisan migrate` to run the migration. ### Setting up the queue system Before we set up our job handler โ€” letโ€™s set up our queue system Go to your โ€œ.envโ€ file and set the QUEUE\_CONNECTION=database โ€” you can decide to use other connections like redis. Letโ€™s create our jobs table by running php artisan queue:table and then run the migration using php artisan migrate. ### Create the Handlers The next thing we do is to create a folder named Handler inside the app folder. Then inside this app/Handler, create two files which are * PolarSignature.php * ProcessWebhook.php Inside app/Handler/PolarSignature.php, what we want to do is to validate that the request came from Polar. Add the code to that file. ```php theme={null} // app/Handler/PolarSignature.php signingSecret); $wh = new \StandardWebhooks\Webhook($signingSecret); return boolval( $wh->verify($request->getContent(), array( "webhook-id" => $request->header("webhook-id"), "webhook-signature" => $request->header("webhook-signature"), "webhook-timestamp" => $request->header("webhook-timestamp"), ))); } } ``` Great. So the other file app/Handler/ProcessWebhook.php extends the ProcessWebhookJob class which holds the WebhookCall variables containing each jobโ€™s detail. ```php theme={null} // app/Handler/ProcessWebhook.php webhookCall, true); $data = $decoded['payload']; switch ($data['type']) { case "checkout.created": // Handle the checkout created event break; case "checkout.updated": // Handle the checkout updated event break; case "subscription.created": // Handle the subscription created event break; case "subscription.updated": // Handle the subscription updated event break; case "subscription.active": // Handle the subscription active event break; case "subscription.revoked": // Handle the subscription revoked event break; case "subscription.canceled": // Handle the subscription canceled event break; default: // Handle unknown event Log::info($data['type']); break; } //Acknowledge you received the response http_response_code(200); } } ``` Our application is ready to receive webhook requests. Donโ€™t forget to run `php artisan queue:listen` to process the jobs. ### Tips If you're keeping track of active and inactive subscriptions in your database, make sure to handle the `subscription.active` and `subscription.revoked` events accordingly. The cancellation of a subscription is handled by the `subscription.canceled` event. The user has probably canceled their subscription before the end of the billing period. Do not revoke any kind of access immediately, but rather wait until the end of the billing period or when you receive the `subscription.revoked` event. ## Notifying the client about the event If you're building a real-time application, you might want to notify the client about the event. On the confirmation-page, you can listen for the `checkout.updated` event and update the UI accordingly when it reaches the succeeded status. ## Polar Laravel Example App We've created a simple example Laravel application that you can use as a reference [View Code on GitHub](https://github.com/polarsource/polar-laravel) If you have issues or need support, feel free to join [our Discord](https://discord.gg/Pnhfz3UThd). # Integrate Polar with Next.js Source: https://polar.sh/docs/guides/nextjs In this guide, we'll show you how to integrate Polar with Next.js. Feel free to use our quick-start script to get started inside a new Next.js project: ```bash Terminal theme={null} # Inside a new Next.js project npx polar-init ``` Consider following this guide while using the Polar Sandbox Environment. This will allow you to test your integration without affecting your production data. [A complete code-example of this guide can be found on GitHub](https://github.com/polarsource/polar-next). ## Install the Polar JavaScript SDK To get started, you need to install the Polar JavaScript SDK and the Polar Nextjs helper package. You can do this by running the following command: ```bash Terminal theme={null} pnpm install @polar-sh/sdk @polar-sh/nextjs ``` ## Setting up environment variables ### Polar Access Token To authenticate with Polar, you need to create an access token, and supply it to Next.js using a `POLAR_ACCESS_TOKEN` environment variable. You can create an organization access token from your organization settings. ## Configuring a Polar API Client To interact with the Polar API, you need to create a new instance of the `Polar` class. This class uses the provided access token to authenticate with the Polar API. ```typescript theme={null} // src/polar.ts import { Polar } from "@polar-sh/sdk"; export const api = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN!, server: "sandbox", // Use this option if you're using the sandbox environment - else use 'production' or omit the parameter }); ``` Remember to replace `sandbox` with `production` when you're ready to switch to the production environment. ## Generating Polar Checkout Sessions Next up, we need to create a checkout endpoint to handle the creation of checkout sessions. Go ahead and create a new GET route in Next.js. ```typescript theme={null} // src/app/checkout/route.ts import { Checkout } from "@polar-sh/nextjs"; export const GET = Checkout({ accessToken: process.env.POLAR_ACCESS_TOKEN!, successUrl: "/confirmation?checkout_id={CHECKOUT_ID}", server: "sandbox", // Use this option if you're using the sandbox environment - else use 'production' or omit the parameter }); ``` ## Handling Polar Webhooks Polar can send you events about various things happening in your organization. This is very useful for keeping your database in sync with Polar checkouts, orders, subscriptions, etc. Configuring a webhook is simple. Head over to your organization's settings page and click on the "Add Endpoint" button to create a new webhook. ### Tunneling webhook events to your local development environment If you're developing locally, you can use a tool like [ngrok](https://ngrok.com/) to tunnel webhook events to your local development environment. This will allow you to test your webhook handlers without deploying them to a live server. Run the following command to start an ngrok tunnel: ```bash Terminal theme={null} ngrok http 3000 ``` ### Add Webhook Endpoint 1. Point the Webhook to `your-app.com/api/webhook/polar`. This must be an absolute URL which Polar can reach. If you use ngrok, the URL will look something like this: `https://.ngrok-free.app/api/webhook/polar`. 2. Select which events you want to be notified about. You can read more about the available events in the [Events section](/api-reference#webhooks). 3. Generate a secret key to sign the requests. This will allow you to verify that the requests are truly coming from Polar. 4. Add the secret key to your environment variables. ```bash Terminal theme={null} # .env POLAR_ACCESS_TOKEN="polar_pat..." POLAR_WEBHOOK_SECRET="..." ``` ### Setting up the Webhook handler ```typescript theme={null} // src/app/api/webhook/polar/route.ts import { Webhooks } from "@polar-sh/nextjs"; export const POST = Webhooks({ webhookSecret: process.env.POLAR_WEBHOOK_SECRET, onPayload: async (payload) => // Handle payload... }); ``` The webhook event is now verified and you can proceed to handle the payload data. ### Handling Webhook Events Depending on which events you've subscribed to, you'll receive different payloads. This is where you can update your database, send notifications, etc. ```typescript theme={null} // src/app/api/webhook/polar/route.ts import { Webhooks } from "@polar-sh/nextjs"; export const POST = Webhooks({ webhookSecret: process.env.POLAR_WEBHOOK_SECRET, onPayload: async (payload) => ..., onOrderCreated: async (order) => ..., onCustomerStateChanged: async (customerState) => ..., ... }); ``` ### Notifying the client about the event If you're building a real-time application, you might want to notify the client about the event. On the confirmation-page, you can listen for the `checkout.updated` event and update the UI accordingly when it reaches the succeeded status. ## Conclusion If you have issues or need support, feel free to join [our Discord](https://discord.gg/Pnhfz3UThd). # Proration for Subscription Upgrades and Downgrades Source: https://polar.sh/docs/guides/proration-for-subscription-changes Learn how proration works in Polar, including available proration types, billing behavior during upgrades and downgrades, and how to configure proration using APIs and dashboard settings. ## What is Proration? Proration is the adjustment of charges based on the unused portion of the current billing period when a subscription is upgraded or downgraded. ## Types of Proration For both `prorate` and `invoice` proration types, the **subscription plan always changes immediately**, whether the change is an upgrade or a downgrade. The only difference between the two proration types is **when the price difference is charged or credited**. ### Invoice Immediately (`invoice`) The prorated price difference is charged or credited immediately when the subscription is upgraded or downgraded. ### Next Invoice (`prorate`) The prorated price difference is charged or credited on the next invoice on updgrading or downgrading a subscription. ## How to change the default proration setting using dashboard In the Polar dashboard sidebar, click on **Settings**. You can also go directly to:\ `https://polar.sh/dashboard/${org_slug}/settings`\ Scroll down to **Subscriptions** section. Choose the default proration behavior: **Invoice Immediately** or **Next invoice**. The default proration setting is changed successfully. ## How to change the default proration setting using the OAT API You can change the default proration behavior using the [Update Organization API](https://polar.sh/docs/api-reference/organizations/update). ## How to prorate when updating subscription via API Create a new organization token by following our [Organization Access Tokens](https://polar.sh/docs/integrate/oat) guide. After creating your access token, you will be able to view it. Please copy and save your access token. * In the Polar dashboard sidebar, navigate to **Sales** > **Subscriptions** for your organization. You can also go directly to:\ `https://polar.sh/dashboard/${org_slug}/sales/subscriptions` * Click on the Subscription you want to upgrade/downgrade and copy its **Subscription ID**. * In the Polar dashboard sidebar, navigate to **Products** > **Catalogue** for your organization. You can also go directly to:\ `https://polar.sh/dashboard/${org_slug}/products` * Retrieve the Product ID for the item you wish to upgrade the subscription to by clicking on the **โ‹ฎ (More options) menu** next to chosen products and selecting **Copy Product ID**. Call the **Update Subscription API** as follows. Make sure to replace: * **subscription\_id** with the ID of the subscription you want to update * **product\_id** with the new product ID * **type\_of\_proration** with the desired proration type: * `invoice`: to charge/credit the prorated price difference immediately * `prorate`: to charge/credit the prorated price difference on next invoice ```bash cURL theme={null} curl --request PATCH \ --url https://api.polar.sh/v1/subscriptions/{} \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "product_id": "", "proration_behavior": "type_of_proration" }' ``` ```py Python theme={null} from polar_sdk import Polar with Polar( access_token="", ) as polar: res = polar.subscriptions.update(id="", subscription_update={ "product_id": "", "proration_behavior": "" }) # Handle response print(res) ``` ## Some examples for proration amount calculation You can create a subscription with two plan options, one for \$5 and other for \$20 by following [Create Product Variants](https://polar.sh/docs/guides/create-variants). ### Case 1: Upgrading the subscription You can upgrade a subscription by following the steps outlined in [Subscription Upgrades](https://polar.sh/docs/guides/subscription-upgrades), which explains the process from both the merchantโ€™s and the customerโ€™s perspectives. **Example 1: Proration type = `invoice`** Assuming that the month when the subscription was purchased has 30 days. In case of 31 days, instead of 30th + 1 day, it would be 31st + 1 day = 32nd day. **Example 2: Proration type = `prorate`** Assuming that the month when the subscription was purchased has 30 days. In case of 31 days, instead of 30th + 1 day, it would be 31st + 1 day = 32nd day. If the billing interval changes, i.e. daily to monthly or monthly to yearly or vice versa, the proration is always applied as invoice (even if set to prorate). ### Case 2: Downgrading the subscription You can downgrade a subscription by following the steps outlined in [Subscription Downgrades](https://polar.sh/docs/guides/subscription-downgrades), which explains the process from both the merchantโ€™s and the customerโ€™s perspectives. **Example 1: Proration type = `invoice`** Assuming that the month when the subscription was purchased has 30 days. In case of 31 days, instead of 30th + 1 day, it would be 31st + 1 day = 32nd day. **Example 2: Proration type = `prorate`** Assuming that the month when the subscription was purchased has 30 days. In case of 31 days, instead of 30th + 1 day, it would be 31st + 1 day = 32nd day. If the billing interval changes, i.e. daily to monthly or monthly to yearly or vice versa, the proration is always applied as invoice (even if set to prorate). # How to schedule subscription downgrades with Upstash QStash Source: https://polar.sh/docs/guides/schedule-subscription-downgrades-qstash Learn how to automatically schedule subscription downgrades at the end of the current billing period using the Polar API and Upstash QStash. ## Overview When managing subscriptions, you may want to schedule downgrades to take effect at the end of the current billing period rather than immediately. This ensures customers receive the full value of their current subscription while automatically transitioning to another tier when their billing period ends. ## Prerequisites * A [Polar API access token](/integrate/authentication) * An [Upstash QStash](https://upstash.com/qstash) account * A server endpoint that can receive HTTP requests * [Node.js](https://nodejs.org/en/blog/announcements/v20-release-announce) or your preferred backend language installed ## Step 1: Set Up Environment Variables Store your API credentials securely in environment variables. ```bash Terminal theme={null} # Polar API credentials POLAR_MODE="sandbox" # can be "production" POLAR_ACCESS_TOKEN="polar_pat_..." # Upstash QStash credentials QSTASH_URL="https://qstash.upstash.io" QSTASH_TOKEN="ey...=" QSTASH_CURRENT_SIGNING_KEY="sig_..." QSTASH_NEXT_SIGNING_KEY="sig_..." # Your application URL APP_URL="https://localhost:3000" ``` You can find your QStash token in the [Upstash Console](https://console.upstash.com/qstash) under the **QStash** section. ## Step 2: Schedule the Downgrade with QStash Use Upstash QStash to schedule an HTTP request to your downgrade endpoint at the end of the billing period. Here's a complete Next.js API route that schedules a downgrade: ```tsx title="app/api/schedule-downgrade/route.ts" theme={null} import { Polar } from "@polar-sh/sdk"; import { Client } from "@upstash/qstash"; import { NextRequest, NextResponse } from "next/server"; const polar = new Polar({ server: process.env.POLAR_MODE, accessToken: process.env.POLAR_ACCESS_TOKEN, }); const qstash = new Client({ token: process.env.QSTASH_TOKEN, }); export async function POST(req: NextRequest) { try { const { subscriptionId, newProductId } = await req.json(); // Step 1: Fetch current subscription details const subscription = await polar.subscriptions.get({ id: subscriptionId, }); if (!subscription.currentPeriodEnd) { return NextResponse.json( { error: "Subscription has no current period end date" }, { status: 400 } ); } // Step 2: Calculate when to execute the downgrade const executeAt = new Date(subscription.currentPeriodEnd); // Step 3: Schedule the downgrade with QStash const result = await qstash.publishJSON({ retries: 1, body: { subscriptionId, newProductId, customerId: subscription.customerId, }, url: `${process.env.APP_URL}/api/execute-downgrade`, delay: Math.floor((executeAt.getTime() - (new Date()).getTime()) / 1000), }); // Step 4: Optionally store the scheduled task // You might want to store this in your database console.log(`Scheduled downgrade for subscription ${subscriptionId}`); console.log(`Will execute at: ${executeAt.toISOString()}`); console.log(`QStash message ID: ${result.messageId}`); return NextResponse.json({ success: true, messageId: result.messageId, scheduledFor: executeAt.toISOString(), }); } catch (error) { console.error("Failed to schedule downgrade:", error); return NextResponse.json( { error: "Failed to schedule downgrade" }, { status: 500 } ); } } ``` Store the QStash `messageId` in your database along with the subscription ID. This allows you to track or cancel scheduled downgrades if needed. ## Step 3: Create the Downgrade Execution Endpoint Create an endpoint that QStash will call at the scheduled time to execute the downgrade. Here's a completed Next.js API route for downgrade: ```tsx title="app/api/execute-downgrade/route.ts" theme={null} import { Polar } from "@polar-sh/sdk"; import { NextRequest, NextResponse } from "next/server"; import { verifySignatureAppRouter } from "@upstash/qstash/nextjs"; import { SubscriptionProrationBehavior } from "@polar-sh/sdk/models/components/subscriptionprorationbehavior.js"; const polar = new Polar({ server: process.env.POLAR_MODE, accessToken: process.env.POLAR_ACCESS_TOKEN, }); async function handler(req: NextRequest) { try { const { subscriptionId, newProductId, customerId } = await req.json(); console.log(`Executing downgrade for subscription ${subscriptionId}`); // Fetch the subscription to verify it's still active const subscription = await polar.subscriptions.get({ id: subscriptionId, }); // Verify subscription is still active if (subscription.status !== "active" && subscription.status !== "trialing") { console.log(`Subscription ${subscriptionId} is not active, skipping downgrade`); return NextResponse.json({ success: false, reason: "Subscription is not active", }); } // Check if already on the target product if (subscription.productId === newProductId) { console.log(`Subscription already on product ${newProductId}`); return NextResponse.json({ success: true, reason: "Already on target product", }); } // Execute the downgrade const updatedSubscription = await polar.subscriptions.update({ id: subscriptionId, subscriptionUpdate: { productId: newProductId, prorationBehavior: SubscriptionProrationBehavior.Invoice, }, }); console.log(`Successfully downgraded subscription ${subscriptionId}`); console.log(`New product: ${updatedSubscription.product.name}`); return NextResponse.json({ success: true, subscription: updatedSubscription, }); } catch (error) { console.error("Failed to execute downgrade:", error); // Return 200 to prevent QStash retries for certain errors if (error instanceof Error && error.message.includes("not found")) { return NextResponse.json( { success: false, error: "Subscription not found" }, { status: 200 } ); } // Let QStash retry for other errors return NextResponse.json( { error: "Failed to execute downgrade" }, { status: 500 } ); } } // Verify QStash signature to ensure requests come from QStash export const POST = verifySignatureAppRouter(handler); ``` **Security**: Always verify QStash signatures to ensure requests are coming from QStash and not malicious actors. The `verifySignatureAppRouter` function handles this automatically. ## Step 4: Test Your Integration Test the complete flow to ensure downgrades are scheduled and executed correctly. Use Polar's [sandbox environment](/integrate/sandbox) to test without affecting production data. ```tsx theme={null} const polar = new Polar({ // Use sandbox for testing server: process.env.POLAR_MODE, accessToken: process.env.POLAR_ACCESS_TOKEN, }); ``` Create a test subscription and schedule a downgrade for a few minutes in the future. ```tsx theme={null} // Schedule downgrade 5 minutes from now for testing const executeAt = new Date(Date.now() + 5 * 60 * 1000); const result = await fetch("/api/schedule-downgrade", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ subscriptionId: "sub_test_xxxxx", newProductId: "prod_basic_xxxxx", }), }); ``` Check the [Upstash Console](https://console.upstash.com/qstash) to see scheduled messages and their status. After the scheduled time, verify the subscription was downgraded: ```tsx theme={null} const subscription = await polar.subscriptions.get({ id: "sub_test_xxxxx", }); console.log("Current product:", subscription.product.name); ``` # Implementing Seat-Based Pricing Source: https://polar.sh/docs/guides/seat-based-pricing Complete guide to implementing team products with seat-based pricing This guide walks you through implementing seat-based pricing for team products, from creating the product to handling seat assignments and claims. ## What you'll build By the end of this guide, you'll have: * A seat-based product with tiered pricing (subscription or one-time) * Checkout flow for purchasing seats * Seat assignment and management interface * Claim flow for team members This guide covers both **subscription-based** and **one-time purchase** seat-based products. The implementation is similar for both, with key differences in scaling and billing. ## Prerequisites * Polar organization with `seat_based_pricing_enabled` feature flag * Polar SDK installed (`npm install @polar-sh/sdk` or `pip install polar-sdk`) * **Important:** Seat-based pricing requires the latest version of the SDK. Make sure to update to the latest version to access all seat-based pricing features. * Basic understanding of Polar products and subscriptions Seat-based pricing is currently in **private beta**. To enable the `seat_based_pricing_enabled` feature flag for your organization, please contact our support team. ## Step 1: Create a seat-based product In the Polar dashboard, go to **Products** and click **Create Product**. Set your product name, description, and media. For example: * **Subscription**: Team Pro Plan - Professional features for your entire team * **One-time**: Enterprise License Pack - Perpetual team licenses Under **Pricing**: * **Product type**: Choose **Subscription** for recurring billing or **One-time** for perpetual licenses * **Billing cycle** (subscriptions only): Monthly or Yearly * **Pricing type**: Seat-based * **Min seats**: 1 (or your minimum team size) Define your volume-based pricing: | Tier | Max Seats | Price per Seat | | ---- | --------- | -------------- | | 1 | 4 | \$10/month | | 2 | 9 | \$9/month | | 3 | Unlimited | \$8/month | Example: A team purchasing 6 seats pays 6 ร— $9 = $54/month. Configure benefits that seat holders will receive: * License Keys * File Downloads * Discord roles * Custom benefits Benefits are granted when seats are claimed, not at purchase time. You can also create seat-based products via API: **Subscription example:** ```typescript theme={null} const subscriptionProduct = await polar.products.create({ name: "Team Pro Plan", organization_id: "org_123", is_recurring: true, prices: [{ type: "recurring", recurring_interval: "month", amount_type: "seat_based", price_currency: "usd", seat_tiers: [ { min_seats: 1, max_seats: 4, price_per_seat: 1000 }, // $10/month { min_seats: 5, max_seats: 9, price_per_seat: 900 }, // $9/month { min_seats: 10, max_seats: null, price_per_seat: 800 } // $8/month ] }] }); ``` **One-time purchase example:** ```typescript theme={null} const oneTimeProduct = await polar.products.create({ name: "Enterprise License Pack", organization_id: "org_123", is_recurring: false, prices: [{ type: "one_time", amount_type: "seat_based", price_currency: "usd", seat_tiers: [ { min_seats: 1, max_seats: 10, price_per_seat: 5000 }, // $50 per seat { min_seats: 11, max_seats: 50, price_per_seat: 4500 }, // $45 per seat { min_seats: 51, max_seats: null, price_per_seat: 4000 } // $40 per seat ] }] }); ``` ## Step 2: Implement checkout flow Create a checkout session that allows customers to select seat quantity: ```typescript theme={null} const checkout = await polar.checkouts.create({ product_price_id: "price_123", seats: 5, // Customer selects quantity success_url: "https://yourapp.com/success", customer_email: "billing@company.com" }); // Redirect to checkout.url ``` The checkout displays: * Price per seat based on quantity * Total amount * Clear indication this is for team access The checkout automatically calculates pricing based on your tiers. A customer selecting 5 seats will see the $9/seat price, totaling $45. ## Step 3: Handle post-purchase webhook Listen for purchase webhooks to know when a customer buys seats: ```typescript theme={null} // Webhook handler app.post('/webhooks/polar', async (req, res) => { const event = req.body; // For subscriptions if (event.type === 'subscription.created') { const subscription = event.data; if (subscription.product.has_seat_based_price) { await notifyBillingManager(subscription.customer_id, { message: `Your ${subscription.seats}-seat subscription is active!`, manage_seats_url: `https://yourapp.com/seats/subscription/${subscription.id}` }); } } // For one-time purchases if (event.type === 'order.created') { const order = event.data; if (order.seats) { await notifyBillingManager(order.customer_id, { message: `Your ${order.seats} perpetual seat licenses have been purchased!`, manage_seats_url: `https://yourapp.com/seats/order/${order.id}` }); } } res.sendStatus(200); }); ``` ## Step 4: Build seat management interface Create an interface for billing managers to assign seats: ```typescript theme={null} // List available seats (works for both subscriptions and orders) async function getSeatInfo(params: { subscription_id?: string; order_id?: string }) { const { seats, available_seats, total_seats } = await polar.customerSeats.list(params); return { seats, available: available_seats, total: total_seats, canAssign: available_seats > 0 }; } // Assign a seat (works for both subscriptions and orders) async function assignSeat( params: { subscription_id?: string; order_id?: string }, email: string, metadata?: Record ) { try { const seat = await polar.customerSeats.assign({ ...params, email: email, metadata: metadata // e.g., { department: "Engineering" } }); return { success: true, seat: seat, message: `Invitation sent to ${email}` }; } catch (error) { if (error.status === 400) { return { success: false, error: "No seats available or customer already has a seat" }; } throw error; } } // Example usage: // For subscriptions: getSeatInfo({ subscription_id: "sub_123" }) // For orders: getSeatInfo({ order_id: "order_456" }) ``` Example UI component (React): ```tsx theme={null} function SeatManagement({ subscriptionId }: { subscriptionId: string }) { const [seatInfo, setSeatInfo] = useState(null); const [email, setEmail] = useState(""); useEffect(() => { loadSeats(); }, [subscriptionId]); async function loadSeats() { const info = await getSeatInfo(subscriptionId); setSeatInfo(info); } async function handleAssign() { const result = await assignSeat(subscriptionId, email, { role: "Developer" }); if (result.success) { setEmail(""); loadSeats(); // Refresh list toast.success("Invitation sent!"); } } return (

Seat Management

{seatInfo?.available} of {seatInfo?.total} seats available

{/* Assign new seat */}
setEmail(e.target.value)} placeholder="team-member@company.com" />
{/* List existing seats */} {seatInfo?.seats.map(seat => ( ))}
Email Status Role Actions
{seat.customer_email} {seat.seat_metadata?.role} {seat.status === 'pending' && ( )} {seat.status === 'claimed' && ( )}
); } ``` ## Step 5: Implement seat claim flow When a team member receives an invitation email, they'll click a link with the invitation token. Build a claim page: ```typescript theme={null} // Claim page route: /claim?token=abc123... async function handleClaimPage(token: string) { // Get claim information (no auth required) const claimInfo = await polar.customerSeats.getClaimInfo({ invitation_token: token }); if (!claimInfo.can_claim) { return { error: "This invitation has expired or already been claimed" }; } return { product: claimInfo.product_name, organization: claimInfo.organization_name, email: claimInfo.customer_email }; } async function claimSeat(token: string) { const { seat, customer_session_token } = await polar.customerSeats.claim({ invitation_token: token }); // Store the customer session token // This allows immediate portal access localStorage.setItem('polar_session', customer_session_token); return { success: true, seat: seat, sessionToken: customer_session_token }; } ``` Example claim page (React): ```tsx theme={null} function ClaimPage() { const [token] = useSearchParams(); const [claimInfo, setClaimInfo] = useState(null); const [claiming, setClaiming] = useState(false); useEffect(() => { loadClaimInfo(); }, [token]); async function loadClaimInfo() { const info = await handleClaimPage(token.get('token')); setClaimInfo(info); } async function handleClaim() { setClaiming(true); try { const result = await claimSeat(token.get('token')); // Redirect to customer portal window.location.href = `/portal?session=${result.sessionToken}`; } catch (error) { toast.error("Failed to claim seat"); setClaiming(false); } } if (claimInfo?.error) { return
Error: {claimInfo.error}
; } return (

You've been invited!

Join {claimInfo?.organization}'s {claimInfo?.product} plan

Email: {claimInfo?.email}

); } ``` ## Step 6: Handle benefit granting After a seat is claimed, benefits are granted automatically via background jobs. Listen for webhooks to track this: ```typescript theme={null} app.post('/webhooks/polar', async (req, res) => { const event = req.body; if (event.type === 'benefit_grant.created') { const grant = event.data; // A team member received their benefits console.log(`Benefit ${grant.benefit_id} granted to ${grant.customer_id}`); // Update your app (e.g., create license, grant access) await grantAccess(grant.customer_id, grant.benefit); } if (event.type === 'benefit_grant.revoked') { const grant = event.data; // A seat was revoked await revokeAccess(grant.customer_id, grant.benefit); } res.sendStatus(200); }); ``` ## Step 7: Implement seat revocation Allow billing managers to revoke seats: ```typescript theme={null} async function revokeSeat(seatId: string) { const revokedSeat = await polar.customerSeats.revoke({ seat_id: seatId }); // Benefits are automatically revoked via webhook return { success: true, seat: revokedSeat, message: "Seat revoked successfully" }; } ``` Revoking a seat immediately removes access but does not issue a refund. The billing manager continues to pay for all purchased seats. ## Step 8: Handle scaling **For subscriptions**, allow billing managers to add or reduce seats: ```typescript theme={null} async function addSeats(subscriptionId: string, newTotal: number) { // Update subscription seat count const subscription = await polar.subscriptions.update({ id: subscriptionId, seats: newTotal }); // New seats are immediately available for assignment return subscription; } async function reduceSeats(subscriptionId: string, newTotal: number) { const { seats } = await polar.customerSeats.list({ subscription_id: subscriptionId }); const claimedCount = seats.filter(s => s.status === 'claimed').length; if (newTotal < claimedCount) { throw new Error( `Cannot reduce to ${newTotal} seats. ${claimedCount} seats are currently claimed. Revoke seats first.` ); } // Update will take effect at next renewal const subscription = await polar.subscriptions.update({ id: subscriptionId, seats: newTotal }); return subscription; } ``` **For one-time purchases**, customers buy additional seats via new orders: ```typescript theme={null} async function purchaseMoreSeats(productId: string, additionalSeats: number) { // Create a new checkout for additional seats const checkout = await polar.checkouts.create({ product_id: productId, seats: additionalSeats, success_url: "https://yourapp.com/success" }); // Each order is independent with its own seat pool return checkout; } ``` For one-time purchases, each order has its own independent seat pool. Customers can purchase additional seats anytime by creating a new order. All seats remain perpetual. ## Best Practices ### 1. Validate seat availability Always check available seats before showing the assignment form: ```typescript theme={null} if (available_seats === 0) { return (
All seats are assigned.
); } ``` ### 2. Use metadata effectively Store useful context in seat metadata: ```typescript theme={null} await polar.customerSeats.assign({ subscription_id: subId, email: "dev@company.com", metadata: { department: "Engineering", role: "Senior Developer", cost_center: "R&D", manager: "jane@company.com" } }); ``` ### 3. Handle expired tokens gracefully ```typescript theme={null} try { await claimSeat(token); } catch (error) { if (error.status === 400) { // Show resend option return "This invitation has expired. Contact your admin to resend."; } } ``` ### 4. Track utilization Monitor seat usage to identify upsell opportunities: ```typescript theme={null} const { seats, available_seats, total_seats } = await getSeatInfo(subId); const utilization = ((total_seats - available_seats) / total_seats) * 100; if (utilization > 80) { // Suggest adding more seats showUpgradePrompt(); } ``` ### 5. Clear communication Make it clear to billing managers that: * They won't receive direct access to benefits * Seats must be assigned to team members * Revocation doesn't refund costs * **For subscriptions**: Reducing seats requires revoking claims first * **For one-time purchases**: Seats are perpetual and non-refundable ## Common Patterns ### Bulk seat assignment ```typescript theme={null} async function assignMultipleSeats( subscriptionId: string, emails: string[] ) { const results = await Promise.allSettled( emails.map(email => polar.customerSeats.assign({ subscription_id: subscriptionId, email: email }) ) ); const succeeded = results.filter(r => r.status === 'fulfilled'); const failed = results.filter(r => r.status === 'rejected'); return { succeeded: succeeded.length, failed: failed.length, errors: failed.map(f => f.reason) }; } ``` ### Syncing with your user system ```typescript theme={null} // When a user joins your system async function onUserSignup(email: string, organizationId: string) { // Check if they have a pending seat const subscriptions = await getOrganizationSubscriptions(organizationId); for (const sub of subscriptions) { const { seats } = await polar.customerSeats.list({ subscription_id: sub.id }); const pendingSeat = seats.find(s => s.status === 'pending' && s.customer_email === email ); if (pendingSeat) { // Auto-claim on signup const claimLink = `/claim?token=${pendingSeat.invitation_token}`; return { shouldClaim: true, claimLink }; } } } ``` ### Custom portal integration ```typescript theme={null} // Display team subscriptions in your app async function getTeamSubscriptions(customerId: string) { const subs = await polar.customerPortal.seats.listSubscriptions({ customer_id: customerId }); return subs.map(sub => ({ product: sub.product.name, status: sub.status, role: sub.seat_metadata?.role, expires: sub.current_period_end })); } ``` ## Troubleshooting ### Seats not appearing Ensure the feature flag is enabled: ``` seat_based_pricing_enabled: true ``` ### Benefits not granted after claim Check webhook logs for `benefit_grant.created` events. Benefits are granted asynchronously via background jobs. ### Cannot reduce seats Make sure to revoke seats before reducing the subscription seat count. You cannot reduce below the currently claimed count. ### Claim link expired Invitation tokens expire after 24 hours. Have the billing manager resend the invitation: ```typescript theme={null} await polar.customerSeats.resend({ seat_id: seatId }); ``` ## Next Steps * Review the [Seat-Based Pricing Feature Documentation](/features/seat-based-pricing) * Explore the [Customer Seats API Reference](/api-reference/customer-seats/assign) * Set up [webhook handlers](/integrate/webhooks/endpoints) for real-time updates * Learn about [Customer Portal customization](/features/customer-portal) ## Need Help? Join our [Discord community](https://polar.sh/discord) or contact support for assistance with seat-based pricing implementation. # How to perform subscription downgrades Source: https://polar.sh/docs/guides/subscription-downgrades Learn how to downgrade a subscription as a merchant or a customer. ## Downgrading a subscription as a merchant In the Polar dashboard sidebar, navigate to **Sales** > **Subscriptions** for your organization. You can also go directly to:\ `https://polar.sh/dashboard/${org_slug}/sales/subscriptions` Click on the subscription you want to downgrade. The subscription details page opens up as shown below. Click on **Update Subscription**. Select the **New product** (plan to downgrade to) and the **Proration behavior** from the dropdown menu. Regardless of the option, the subscription is downgraded immediately, only the invoicing happens according to the selected **Proration behaior**. There are two types of proration: * **Next invoice**: The customer is charged (or credited) in the upcoming invoice for the difference. * **Invoice immediately**: The customer is charged (or credited) right away for the difference. Then, click on **Update Subscription**. The subscription is successfully downgraded to **Basic version**. Note that merchants can disable customer-level upgrades or downgrades in the portal [by toggling OFF **Allow price change** setting](/guides/disable-subscription-changes-in-customer-portal). ## Downgrading a subscription as a customer Open the email you received after purchasing the subscription. Click the **Access my Purchase** link to go to the Customer Portal, where you can downgrade your subscription. In the Overview tab of Customer Portal, scroll to **Subscriptions** and click on **Change Plan**. Select the plan you want to downgrade to and click on **Change Plan**. The subscription is successfully downgraded to **Basic version**. # How to perform subscription upgrades Source: https://polar.sh/docs/guides/subscription-upgrades Learn how to upgrade a subscription as a merchant or a customer. ## Upgrading a subscription as a merchant In the Polar dashboard sidebar, navigate to **Sales** > **Subscriptions** for your organization. You can also go directly to:\ `https://polar.sh/dashboard/${org_slug}/sales/subscriptions` Click on the subscription you want to upgrade. The subscription details page opens up as shown below. Click on **Update Subscription**. Select the **New product** (plan to upgrade to) and the **Proration behavior** from the dropdown menu. Regardless of the option, the subscription is upgraded immediately, only the invoicing happens according to the selected **Proration behaior**. There are two types of proration: * **Next invoice**: The customer is charged (or credited) in the upcoming invoice for the difference. * **Invoice immediately**: The customer is charged (or credited) right away for the difference. Then, click on **Update Subscription**. The subscription is successfully upgraded to **Advanced version**. Note that merchants can disable customer-level upgrades or downgrades in the portal [by toggling OFF **Allow price change** setting](/guides/disable-subscription-changes-in-customer-portal). ## Upgrading subscription as a customer Open the email you received after purchasing the subscription. Click the **Access my Purchase** link to go to the Customer Portal, where you can upgrade your subscription. In the Overview tab of Customer Portal, scroll to **Subscriptions** and click on **Change Plan**. Select the plan you want to upgrade to and click on **Change Plan**. The subscription is successfully upgraded to **Advanced version**. # How to Switch Checkout Theme Source: https://polar.sh/docs/guides/theme-switch-in-checkout Learn how to switch the theme in a checkout session and a checkout link. ## Changing the Theme for a Checkout Session Created via the API Create a Checkout Session by following our [How to Create Checkout Session](/guides/create-checkout-session) guide and obtain the `url`. To switch to the dark theme, append the query parameter theme=dark to your checkout session URL as obtained earlier. For example, if your checkout session URL is:\ [https://polar.sh/checkout/polar\_c\_ES7DwhlyvlYTPNaLiccKqiwJPda43FIzer](https://polar.sh/checkout/polar_c_ES7DwhlyvlYTPNaLiccKqiwJPda43FIzer) You should use:\ [https://polar.sh/checkout/polar\_c\_ES7DwhlyvlYTPNaLiccKqiwJPda43FIzer?theme=dark](https://polar.sh/checkout/polar_c_ES7DwhlyvlYTPNaLiccKqiwJPda43FIzer?theme=dark) The checkout session then looks like below: Similarly, to switch to the light theme, append the query parameter theme=light to your checkout session URL. For example:\ [https://polar.sh/checkout/polar\_c\_ES7DwhlyvlYTPNaLiccKqiwJPda43FIzer?theme=light](https://polar.sh/checkout/polar_c_ES7DwhlyvlYTPNaLiccKqiwJPda43FIzer?theme=light) The checkout session then looks like below: ## Switching Theme for a Checkout Link Create a checkout link by referring to our [Create a checkout link](https://polar.sh/docs/features/checkout/links#create-a-checkout-link) guide. To switch to the dark theme, append the query parameter theme=dark to your checkout link. For example, if your checkout link is:\ [https://sandbox-api.polar.sh/v1/checkout-links/polar\_cl\_QHMjrDLsORxLIRfyQlfyqF1TWUR6Cy4afVcd/redirect](https://sandbox-api.polar.sh/v1/checkout-links/polar_cl_QHMjrDLsORxLIRfyQlfyqF1TWUR6Cy4afVcd/redirect) You should use:\ [https://sandbox-api.polar.sh/v1/checkout-links/polar\_cl\_QHMjrDLsORxLIRfyQlfyqF1TWUR6Cy4afVcd/redirect?theme=dark](https://sandbox-api.polar.sh/v1/checkout-links/polar_cl_QHMjrDLsORxLIRfyQlfyqF1TWUR6Cy4afVcd/redirect?theme=dark) The checkout session then looks like below: Similarly, to switch to the light theme, append the query parameter theme=light to your checkout link. For example:\ [https://sandbox-api.polar.sh/v1/checkout-links/polar\_cl\_QHMjrDLsORxLIRfyQlfyqF1TWUR6Cy4afVcd/redirect?theme=light](https://sandbox-api.polar.sh/v1/checkout-links/polar_cl_QHMjrDLsORxLIRfyQlfyqF1TWUR6Cy4afVcd/redirect?theme=light) The checkout session then looks like below: # Authentication Source: https://polar.sh/docs/integrate/authentication All bearer tokens should be kept private and never shared or exposed in client-side code. To authenticate requests, Polar API has two mechanisms. 1. [Organization Access Tokens (OAT)](/integrate/oat) - Recommended 2. [OAuth 2.0 Provider](/integrate/oauth2/introduction) (Partner Integrations) ## Organization Access Tokens (OAT) They are tied to **one** of your organization. You can create them from your organization settings. ## Security To protect your data and ensure the security of Polar, we've several mechanisms in place to automatically revoke tokens that may have been leaked publicly on the web. In particular, we're part of the [GitHub Secret Scanning Program](https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning). If GitHub systems detect a Polar token in a code repository or public discussion, our systems are notified and the tokens are immediately revoked. If you received an email about one of your token being leaked, it means that we were notified of such situation. The email contains the details about the nature of the token and the source of the leak. In the future, it's crucial that you remain extra cautious about not leaking your tokens publicly online. You can read more about the good practices to manage secrets in the [OWASP Secrets Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html). # Customer State Source: https://polar.sh/docs/integrate/customer-state The quickest way to integrate billing in your application Customer State is a concept allowing you to query for the current state of a customer, including their active subscriptions and granted [benefits](/features/benefits/introduction), in a single [API call](/api-reference/customers/state-external) or single [webhook event](/api-reference/webhooks/customer.state_changed). Combined with the [External ID](/features/customer-management#external-id) feature, you can get up-and-running in minutes. ## The customer state object The customer state object contains: * All the data about the customer. * The list of their **active** subscriptions. * The list of their **granted** benefits. * The list of their **active** meters, with their current balance. Thus, with that single object, you have all the required information to check if you should provision access to your service or not. One endpoint to rule them all, using your own customer ID. The same one, but with internal Polar customer ID. ## The `customer.state_changed` webhook To be notified of the customer state changes, you can listen to the `customer.state_changed` webhook event. It's triggered when: * Customer is created, updated or deleted. * A subscription is created or updated. * A benefit is granted or revoked. By subscribing to this webhook event, you keep your system up-to-date and update your customer's access accordingly. One webhook to rule them all. # Polar over Model Context Protocol (MCP) Source: https://polar.sh/docs/integrate/mcp Extend the capabilities of your AI agents with Polar's MCP Server Supercharge your AI agents with Polar as a Model Context Protocol (MCP) server. ## What is MCP? MCP is a protocol for integrating tools with AI agents. It can greatly enhance the capabilities of your AI agents by providing them with real-time data and context. Polar offers a remote MCP server that you can connect to from most AI clients. ## How does it work? You need a MCP-capable agent environment to use Polar over MCP. A few of them are Claude Desktop and Cursor. ## Connecting to Polar MCP Polar provides two MCP servers: * **Production**: `https://mcp.polar.sh/mcp/polar-mcp` - Connect to your live Polar organization * **Sandbox**: `https://mcp.polar.sh/mcp/polar-sandbox` - Connect to the Polar sandbox environment for testing When you can specify a MCP URL, use one of the URLs above depending on your environment. If you have to specify a command, use: ```json theme={null} { "mcpServers": { "Polar": { "command": "npx", "args": ["mcp-remote", "https://mcp.polar.sh/mcp/polar-mcp"] } } } ``` For sandbox: ```json theme={null} { "mcpServers": { "Polar Sandbox": { "command": "npx", "args": ["mcp-remote", "https://mcp.polar.sh/mcp/polar-sandbox"] } } } ``` ### Cursor In `.cursor/mcp.json`, add: ```json theme={null} { "mcpServers": { "Polar": { "url": "https://mcp.polar.sh/mcp/polar-mcp" } } } ``` For sandbox: ```json theme={null} { "mcpServers": { "Polar Sandbox": { "url": "https://mcp.polar.sh/mcp/polar-sandbox" } } } ``` ### Windsurf In `mcp_config.json`, add: ```json theme={null} { "mcpServers": { "Polar": { "command": "npx", "args": ["mcp-remote", "https://mcp.polar.sh/mcp/polar-mcp"] } } } ``` For sandbox: ```json theme={null} { "mcpServers": { "Polar Sandbox": { "command": "npx", "args": ["mcp-remote", "https://mcp.polar.sh/mcp/polar-sandbox"] } } } ``` ### Codex Add the following to your `~/.codex/config.toml`: ```toml theme={null} [features] rmcp_client = true [mcp_servers.polar] type = "http" url = "https://mcp.polar.sh/mcp/polar-mcp" ``` Then run: ```sh theme={null} codex mcp login polar ``` For sandbox: ```toml theme={null} [features] rmcp_client = true [mcp_servers.polar_sandbox] type = "http" url = "https://mcp.polar.sh/mcp/polar-sandbox" ``` Then run: ```sh theme={null} codex mcp login polar_sandbox ``` ### Claude Code Run the following command: ``` claude mcp add --transport http "Polar" "https://mcp.polar.sh/mcp/polar-mcp" ``` For sandbox: ``` claude mcp add --transport http "Polar Sandbox" "https://mcp.polar.sh/mcp/polar-sandbox" ``` ### ChatGPT MCP is only available for paid users in beta on ChatGPT web, by enabling Developer Mode. Once Developer Mode is enabled, go to *Settings* โ†’ *Connectors* and add the MCP server using `https://mcp.polar.sh/mcp/polar-mcp`. For sandbox, use `https://mcp.polar.sh/mcp/polar-sandbox` instead. ### Claude Desktop Go to *Settings* โ†’ *Connectors* and click *Add custom connector*. Name it "Polar" and add `https://mcp.polar.sh/mcp/polar-mcp` as the server URL. For sandbox, use `https://mcp.polar.sh/mcp/polar-sandbox` as the server URL instead. Save, and click *Connect* to connect to your Polar organization. # OAuth 2.0 Connect Source: https://polar.sh/docs/integrate/oauth2/connect ## Authorize To start the authorization flow you need to redirect the user to the authorization URL. It looks like this: ``` https://polar.sh/oauth2/authorize? response_type=code &client_id=CLIENT_ID &redirect_uri=https%3A%2F%2Fexample.com%2Fcallback &scope=openid%20email ``` The parameters are the one described in the [OpenID Connect specification](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). The most important ones are: Indicates that you want to use the authorization code flow. Most common and the only one supported by Polar. The Client ID you got when creating the OAuth 2.0 client. The URL where the user will be redirected after granting access to their data. Make sure you declared it when creating the OAuth2 client. A space-separated list of scopes you want to ask for. Make sure they are part of the scopes you declared when creating the OAuth2 client. If you redirect the user to this URL, they'll see a page asking them to grant access to their data, corresponding to the scopes you asked for. By default, the user will also be prompted to select one of their organizations, as Polar generates organization-level access tokens by default. If they allow it and select an organization, they'll be redirected to your `redirect_uri` with a `code` parameter in the query string. This code is a one-time code that you can exchange for an access token. To skip the organization selection and get a user-scoped token instead, you can add `sub_type=user` to the authorization URL: ``` https://polar.sh/oauth2/authorize? response_type=code &client_id=CLIENT_ID &redirect_uri=https%3A%2F%2Fexample.com%2Fcallback &scope=openid%20email &sub_type=user ``` #### Exchange code token Once you have the authorization code, you can exchange it for an access token. To do so, you'll need to make a `POST` request to the token endpoint. This call needs to be authenticated with the Client ID and Client Secret you got when creating the OAuth2 client. Here is an example with cURL: ```bash Terminal theme={null} curl -X POST https://api.polar.sh/v1/oauth2/token \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'grant_type=authorization_code&code=AUTHORIZATION_CODE&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&redirect_uri=https://example.com/callback' ``` You should get the following response: ```json theme={null} { "token_type": "Bearer", "access_token": "polar_at_XXX", "expires_in": 864000, "refresh_token": "polar_rt_XXX", "scope": "openid email", "id_token": "ID_TOKEN" } ``` The `access_token` will allow you to make authenticated API requests on behalf of the user. The `refresh_token` is a long-lived token that you can use to get new access tokens when the current one expires. The `id_token` is a signed JWT token containing information about the user, as per the [OpenID Connect specification](https://openid.net/specs/openid-connect-core-1_0.html#IDToken). #### Organization vs User Access Tokens By default, Polar OAuth2 flow generates **organization-level** access tokens. These tokens are tied to a specific organization rather than a user, allowing requests to operate only on that organization's data, which improves privacy and security. When using the default flow, the user will be prompted to select one of their organizations before allowing access to their data: ``` https://polar.sh/oauth2/authorize?response_type=code&client_id=polar_ci_j3X95_MgfdSCeCd2qkFnUw&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback&scope=openid%20email ``` To request a **user-scoped** access token instead, you need to explicitly add the parameter `sub_type=user` to the authorization URL: ``` https://polar.sh/oauth2/authorize?response_type=code&client_id=polar_ci_j3X95_MgfdSCeCd2qkFnUw&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback&scope=openid%20email&sub_type=user ``` The rest of the flow remains unchanged. The access token you'll get will be tied to either the selected organization (default) or the user (when explicitly requested). #### Public Clients Public clients are clients where the Client Secret can't be kept safe, as it would be accessible by the final user. This is the case for SPA, mobile applications, or any client running on the user's device. In this case, **and only if the client is configured as a Public Client**, the request to the token endpoint won't require the `client_secret` parameter. However, the [PKCE](https://oauth.net/2/pkce/) method will be required to maximize security. ### Make authenticated requests Once you have an access token, either from a Personal Access Token or from the OpenID Connect flow, you can make authenticated requests to the API. Here is a simple example with cURL: ```bash Terminal theme={null} curl -X GET https://api.polar.sh/v1/oauth2/userinfo \ -H 'Authorization: Bearer polar_at_XXX' ``` # Introduction Source: https://polar.sh/docs/integrate/oauth2/introduction For partners building services and extensions for Polar customers ### OpenID Connect (OAuth2) Only use our **OpenID Connect** in case you want to act on the behalf of other users via our API, e.g building an app/service for Polar customers. Otherwise, always use an **Organization Access Token (OAT)** to integrate Polar for your own service. Polar implements the [OpenID Connect specification](https://openid.net/developers/how-connect-works/) to enable third-party authentication. It's a layer on top of the OAuth2 framework aiming at making integration more standard and predictable. In particular, it comes with a **discovery endpoint** allowing compatible clients to automatically work with the OpenID Connect server. Here is Polar's one: [OpenID Configuration](https://api.polar.sh/.well-known/openid-configuration) # Create an OAuth 2.0 Client Source: https://polar.sh/docs/integrate/oauth2/setup Before being able to make authentication requests, you'll need an **OAuth2 Client**. It's the entity that'll identify you, as a third-party developer, between Polar and the final user. You can manage them from your [User Settings](https://polar.sh/settings#oauth) Here are the required fields: * *Application Name*: the name of the application that'll be shown to the final users. * *Client Type*: the type of client you are creating. [Read more](#public-clients) * *Redirect URIs*: for security reasons, you need to declare your application URL where the users will be redirected after granting access to their data. When configuring your OAuth client, you must use an `https://` URL for security reasons. We block `http://` URLs, except when the hostname is `localhost`. This exception allows you to use `http://localhost` for convenient testing in development mode. * *Scopes*: the list of scopes your app will be able to ask for. To improve privacy and security, select only the scopes you really need for your application. * *Homepage URL*: the URL of your application. It'll be shown to the final users on the authorization page. Optionally, you can also add a **logo**, **terms of service** and **privacy policy** URL. They'll all be shown to the final users on the authorization page. Once your client is created, you'll get a **Client ID** and a **Client Secret**. You'll need those values to make authentication requests. Those values are super sensitive and should be kept secret. They allow making authentication requests on Polar! # Sandbox Environment Source: https://polar.sh/docs/integrate/sandbox A separate environment, isolated from your production data To test Polar or work on your integration without worrying about actual money processing or breaking your live organization, you can use our [sandbox environment](https://sandbox.polar.sh/start). It's a dedicated server, completely isolated from the production instance where you can do all the experiments you want. **Why a dedicated environment instead of a test mode?** Since we're dealing with money and need to keep track of all movements to assure our Merchant of Record service, we found it safer to isolate live data from test data so it never interferes. Besides, it allows you to create an unlimited number of account and organization to test lot of different scenarios. Consider it as your own development server! ## Get started You can access the sandbox environment directly on [sandbox.polar.sh](https://sandbox.polar.sh/start) or by clicking on `Go to sandbox` from the organization switcher. You'll then need to create a dedicated user account and organization, the same way described in our [Quick Start guide](/introduction). ### Testing payments The sandbox environment allows you to experience the complete customer funnel, including checkout. You can perform test payments using Stripe's [test card numbers](https://docs.stripe.com/testing#cards). The easiest one to test a successful payment is to use the following card number with a future expiration date and random CVC: ``` 4242 4242 4242 4242 ``` ## API and SDK To make requests to our [API](/api-reference), all you need to do is to switch the base URL from `https://api.polar.sh` to `https://sandbox-api.polar.sh`. You'll also need to create an access token in the **sandbox environment**, the access token created in the production environment can't be used in the sandbox. Our official SDK supports the sandbox environment through a dedicated parameter. ```ts TypeScript theme={null} const polar = new Polar({ server: 'sandbox', accessToken: process.env['POLAR_ACCESS_TOKEN'] ?? '', }) ``` ```py Python theme={null} s = Polar( server="sandbox", access_token="", ) ``` ```go Go theme={null} s := polargo.New( polargo.WithServer("sandbox"), polargo.WithSecurity(os.Getenv("POLAR_ACCESS_TOKEN")), ) ``` ```php PHP theme={null} $sdk = Polar\Polar::builder() ->setServer('sandbox') ->setSecurity( '' ) ->build(); ``` ## Limitations The limitations listed below only apply to sandbox and doesn't reflect the behavior in production. * Subscriptions created in sandbox are automatically canceled **90 days after their creation**. # Astro Source: https://polar.sh/docs/integrate/sdk/adapters/astro Payments and Checkouts made dead simple with Astro ## Examples * [With Astro](https://github.com/polarsource/examples/tree/main/with-astro) * [With Astro and Cloudflare Workers](https://github.com/polarsource/examples/tree/main/with-astro-cloudflare-workers) ## Installation Install the required Polar packages using the following command: ```bash Terminal theme={null} npm install zod @polar-sh/astro ``` ```bash Terminal theme={null} yarn add zod @polar-sh/astro ``` ```bash Terminal theme={null} pnpm add zod @polar-sh/astro ``` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript icon="square-js" theme={null} import { Checkout } from "@polar-sh/astro"; import { POLAR_ACCESS_TOKEN, POLAR_SUCCESS_URL } from "astro:env/server"; export const GET = Checkout({ accessToken: POLAR_ACCESS_TOKEN, successUrl: POLAR_SUCCESS_URL, returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Checkout server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise theme: "dark", // Enforces the theme - System-preferred theme will be set if left omitted }); ``` ### Query Params Pass query params to this route. * products `?products=123` * customerId (optional) `?products=123&customerId=xxx` * customerExternalId (optional) `?products=123&customerExternalId=xxx` * customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com` * customerName (optional) `?products=123&customerName=Jane` * metadata (optional) `URL-Encoded JSON string` ## Customer Portal Create a customer portal where your customer can view orders and subscriptions. ```typescript icon="square-js" theme={null} import { CustomerPortal } from "@polar-sh/astro"; import { POLAR_ACCESS_TOKEN } from "astro:env/server"; export const GET = CustomerPortal({ accessToken: POLAR_ACCESS_TOKEN, getCustomerId: (event) => "", // Function to resolve a Polar Customer ID returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Customer Portal server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }); ``` ## Webhooks A simple utility which resolves incoming webhook payloads by signing the webhook secret properly. ```typescript icon="square-js" theme={null} import { Webhooks } from '@polar-sh/astro'; import { POLAR_WEBHOOK_SECRET } from "astro:env/server" export const POST = Webhooks({ webhookSecret: POLAR_WEBHOOK_SECRET, onPayload: async (payload) => /** Handle payload */, }) ``` ### Payload Handlers The Webhook handler also supports granular handlers for easy integration. * `onPayload` - Catch-all handler for any incoming Webhook event * `onCheckoutCreated` - Triggered when a checkout is created * `onCheckoutUpdated` - Triggered when a checkout is updated * `onOrderCreated` - Triggered when an order is created * `onOrderPaid` - Triggered when an order is paid * `onOrderRefunded` - Triggered when an order is refunded * `onRefundCreated` - Triggered when a refund is created * `onRefundUpdated` - Triggered when a refund is updated * `onSubscriptionCreated` - Triggered when a subscription is created * `onSubscriptionUpdated` - Triggered when a subscription is updated * `onSubscriptionActive` - Triggered when a subscription becomes active * `onSubscriptionCanceled` - Triggered when a subscription is canceled * `onSubscriptionRevoked` - Triggered when a subscription is revoked * `onSubscriptionUncanceled` - Triggered when a subscription cancellation is reversed * `onProductCreated` - Triggered when a product is created * `onProductUpdated` - Triggered when a product is updated * `onOrganizationUpdated` - Triggered when an organization is updated * `onBenefitCreated` - Triggered when a benefit is created * `onBenefitUpdated` - Triggered when a benefit is updated * `onBenefitGrantCreated` - Triggered when a benefit grant is created * `onBenefitGrantUpdated` - Triggered when a benefit grant is updated * `onBenefitGrantRevoked` - Triggered when a benefit grant is revoked * `onCustomerCreated` - Triggered when a customer is created * `onCustomerUpdated` - Triggered when a customer is updated * `onCustomerDeleted` - Triggered when a customer is deleted * `onCustomerStateChanged` - Triggered when a customer state changes # BetterAuth Source: https://polar.sh/docs/integrate/sdk/adapters/better-auth Payments and Checkouts made dead simple with BetterAuth ## @polar-sh/better-auth A [Better Auth](https://github.com/better-auth/better-auth) plugin for integrating [Polar](https://polar.sh) payments and subscriptions into your authentication flow. ### Features * [Automatic Customer creation on signup](#automatic-customer-creation-on-signup) * [Sync customer deletion](#sync-customer-deletion) * [Reference System to associate purchases with organizations](#3-2-orders) * [Checkout Integration](#checkout-plugin) * [Event Ingestion & Customer Meters for flexible Usage Based Billing](#usage-plugin) * [Handle Polar Webhooks securely with signature verification](#webhooks-plugin) * [Customer Portal](#portal-plugin) ## Examples * [With Next.js, Better Auth and Cloudflare Workers](https://github.com/polarsource/examples/tree/main/with-nextjs-better-auth-cloudflare-workers) ## Installation Install the required Better Auth and Polar packages using the following command: ```bash Terminal theme={null} npm install better-auth @polar-sh/better-auth @polar-sh/sdk ``` ```bash Terminal theme={null} yarn add better-auth @polar-sh/better-auth @polar-sh/sdk ``` ```bash Terminal theme={null} pnpm add better-auth @polar-sh/better-auth @polar-sh/sdk ``` ## Integrate Polar with BetterAuth Go to your Polar Organization Settings, create an Organization Access Token, and add it to the environment variables of your application. ```bash .env theme={null} POLAR_ACCESS_TOKEN=... ``` The Polar plugin comes with it's own set of plugins to add functionality to your stack: * **checkout** - Enable seamless checkout integration * **portal** - Make it possible for your customers to manage their orders, subscriptions & benefits * **usage** - List customer meters & ingest events for Usage Based Billing * **webhooks** - Listen for relevant Polar webhooks ```typescript icon="square-js" BetterAuth Server with Polar Example theme={null} import { betterAuth } from "better-auth"; import { polar, checkout, portal, usage, webhooks } from "@polar-sh/better-auth"; // [!code ++] import { Polar } from "@polar-sh/sdk"; // [!code ++] const polarClient = new Polar({ // [!code ++] accessToken: process.env.POLAR_ACCESS_TOKEN, // [!code ++] // Use 'sandbox' if you're using the Polar Sandbox environment // Remember that access tokens, products, etc. are completely separated between environments. // Access tokens obtained in Production are for instance not usable in the Sandbox environment. server: 'sandbox' // [!code ++] }); // [!code ++] const auth = betterAuth({ // ... Better Auth config plugins: [ polar({ // [!code ++] client: polarClient, // [!code ++] createCustomerOnSignUp: true, // [!code ++] use: [ // [!code ++] checkout({ // [!code ++] products: [ // [!code ++] { // [!code ++] productId: "123-456-789", // ID of Product from Polar Dashboard // [!code ++] slug: "pro" // Custom slug for easy reference in Checkout URL, e.g. /checkout/pro // [!code ++] } // [!code ++] ], // [!code ++] successUrl: "/success?checkout_id={CHECKOUT_ID}", // [!code ++] authenticatedUsersOnly: true // [!code ++] }), // [!code ++] portal(), // [!code ++] usage(), // [!code ++] webhooks({ // [!code ++] secret: process.env.POLAR_WEBHOOK_SECRET, // [!code ++] onCustomerStateChanged: (payload) => // Triggered when anything regarding a customer changes // [!code ++] onOrderPaid: (payload) => // Triggered when an order was paid (purchase, subscription renewal, etc.) // [!code ++] ... // Over 25 granular webhook handlers // [!code ++] onPayload: (payload) => // Catch-all for all events // [!code ++] }) // [!code ++] ], // [!code ++] }) // [!code ++] ] }); ``` #### Polar Plugin Configuration Options ```typescript theme={null} // ... const auth = betterAuth({ // ... Better Auth config plugins: [ polar({ client: polarClient, // [!code ++] createCustomerOnSignUp: true, // [!code ++] getCustomerCreateParams: ({ user }, request) => ({ // [!code ++] metadata: { // [!code ++] myCustomProperty: 123, // [!code ++] }, // [!code ++] }), // [!code ++] use: [ // [!code ++] // This is where you add Polar plugins // [!code ++] ], // [!code ++] }), ], }); ``` * `client` (required): Polar SDK client instance * `createCustomerOnSignUp` (optional): Automatically create a Polar customer when a user signs up * `getCustomerCreateParams` (optional): Custom function to provide additional customer creation metadata * `use` (optional): Array of Polar plugins to enable specific functionality (checkout, portal, usage, and webhooks) You will be using the BetterAuth Client to interact with the Polar functionalities. ```typescript icon="square-js" BetterAuth Client with Polar Example theme={null} import { createAuthClient } from "better-auth/react"; import { polarClient } from "@polar-sh/better-auth"; // [!code ++] import { organizationClient } from "better-auth/client/plugins"; // [!code ++] // All Polar plugins, etc. should be attached to BetterAuth server export const authClient = createAuthClient({ // [!code ++] plugins: [polarClient()], // [!code ++] }); // [!code ++] ``` ## Automatic Customer creation on signup Enable the `createCustomerOnSignUp` [Polar plugin configuration option](#polar-plugin-configuration-options) to automatically create a new Polar Customer when a new User is added in the BetterAuth database. All new customers are created with an associated `externalId`, i.e. the ID of your User in the Database. This skips any Polar to User mapping in your database. ## Sync Customer deletion To add user deletion logic with external Polar customer deletion in BetterAuth, extend the user config of your betterAuth setup with the deleteUser option and an afterDelete hook. Hereโ€™s how to integrate this alongside your Polar plugin and automatic customer creation: ```typescript icon="square-js" Customer Deletion Example theme={null} const auth = betterAuth({ user: { // [!code ++] deleteUser: { // [!code ++] enabled: true, // [!code ++] afterDelete: async (user, request) => { // [!code ++] await polar.customers.deleteExternal({ // [!code ++] externalId: user.id, // [!code ++] }); // [!code ++] }, // [!code ++] }, // [!code ++] }, // [!code ++] }); ``` ## Checkout Plugin [Source code](https://github.com/polarsource/polar-adapters/blob/main/packages/polar-betterauth/src/plugins/checkout.ts) To support [checkouts](/features/checkout/links) in your app, you would pass the `checkout` plugin in the `use` property. The checkout plugin accepts the following configuration options: * **`products`** (optional): An array of product mappings or a function that returns them asynchronously. Each mapping contains a `productId` and a `slug` that allows you to reference products by a friendly slug instead of their full ID. * **`successUrl`** (optional): The relative URL where customers will be redirected after a successful checkout completion. You can use the `{CHECKOUT_ID}` placeholder in the URL to include the checkout session ID in the redirect. * **`returnUrl`** (optional): An optional URL which renders a back-button in the Checkout. * **`authenticatedUsersOnly`** (optional): A boolean flag that controls whether checkout sessions require user authentication. When set to `true`, only authenticated users can initiate checkouts and the customer information will be automatically associated with the authenticated user. When `false`, anonymous checkouts are allowed. * **`theme`** (optional): A string that can be used to enforce the theme of the checkout page. Can be either `light` or `dark`. Update the `use` property of the Polar plugin for BetterAuth client to have the `checkout` plugin. ```typescript icon="square-js" Checkout Plugin Example theme={null} import { polar, checkout // [!code ++] } from "@polar-sh/better-auth"; const auth = betterAuth({ // ... Better Auth config plugins: [ polar({ ... use: [ checkout({ // [!code ++] // Optional field - will make it possible to pass a slug to checkout instead of Product ID products: [ { productId: "123-456-789", slug: "pro" } ], // [!code ++] // Relative URL to return to when checkout is successfully completed successUrl: "/success?checkout_id={CHECKOUT_ID}", // [!code ++] // Whether you want to allow unauthenticated checkout sessions or not authenticatedUsersOnly: true, // [!code ++] // An optional URL which renders a back-button in the Checkout returnUrl: "https://myapp.com" // [!code ++] }) // [!code ++] ], }) ] }); ``` When the `checkout` plugin is passed, you are then able to initialize Checkout Sessions using the `checkout` method on the BetterAuth client. This will redirect the user to the product's checkout link. The `checkout` method accepts the following properties: * **`products`** (optional): An array of Polar Product IDs. * **`slug`** (optional): A string that can be used as a reference to the `products` defined in the Checkout config * **`referenceId`** (optional): An identifier that will be saved in the metadata of the checkout, order & subscription object ```typescript icon="square-js" BetterAuth Checkout with Polar Example theme={null} await authClient.checkout({ // Polar Product IDs products: ["e651f46d-ac20-4f26-b769-ad088b123df2"], // [!code ++] // OR // if "products" in passed in the checkout plugin's config, you may pass the slug // slug: "pro", // [!code ++] }); ``` This plugin supports the Organization plugin. If you pass the organization ID to the Checkout referenceId, you will be able to keep track of purchases made from organization members. ```typescript icon="square-js" BetterAuth Checkout with Polar Organization Example theme={null} const organizationId = (await authClient.organization.list())?.data?.[0]?.id, await authClient.checkout({ // Any Polar Product ID can be passed here products: ["e651f46d-ac20-4f26-b769-ad088b123df2"], // Or, if you setup "products" in the Checkout Config, you can pass the slug slug: 'pro', // Reference ID will be saved as `referenceId` in the metadata of the checkout, order & subscription object referenceId: organizationId }); ``` ## Usage Plugin [Source code](https://github.com/polarsource/polar-adapters/blob/main/packages/polar-betterauth/src/plugins/usage.ts) A plugin for Usage Based Billing that allows you to [ingest events](#event-ingestion) from your application and list the [authenticated user's Usage Meter](#customer-meters). To enable [usage based billing](/integrate/sdk/adapters/better-auth) in your app, you would pass the `usage` plugin in the `use` property. ```typescript icon="square-js" Usage Plugin Example theme={null} import { polar, checkout, portal, usage // [!code ++] } from "@polar-sh/better-auth"; const auth = betterAuth({ // ... Better Auth config plugins: [ polar({ ... use: [ checkout(...), portal(), usage() // [!code ++] ], }) ] }); ``` ### 1. Event Ingestion Polar's Usage Based Billing builds entirely on event ingestion. Ingest events from your application, create Meters to represent that usage, and add metered prices to Products to charge for it. The `ingestion` method of the `usage` plugin accepts the following parameters: * `event` (string): The name of the event to ingest. For example, `ai_usage`, `video_streamed` or `file_uploaded`. * `metadata` (object): A record containing key-value pairs that provide additional context about the event. Values can be strings, numbers, or booleans. This is useful for storing information that can be used to filter the events or compute the actual usage. For example, you can store the duration of the video streamed or the size of the file uploaded. The authenticated user is automatically associated with the ingested event. ```typescript icon="square-js" Event Ingestion with Usage Plugin Example theme={null} const { data: ingested } = await authClient.usage.ingestion({ event: "file-uploads", metadata: { uploadedFiles: 12, }, }); ``` ### 2. Customer Meters A method to list the authenticated user's Usage Meters (aka Customer Meters). A Customer Meter contains all the information about their consumption on your defined meters. The `meters` method of the `usage` plugin accepts the following parameters: * `page` (number): The page number for pagination (starts from 1). * `limit` (number): The maximum number of meters to return per page. ```typescript icon="square-js" Customer Meters with Usage Plugin Example theme={null} const { data: customerMeters } = await authClient.usage.meters.list({ query: { page: 1, limit: 10, }, }); ``` The `meters` method returns the following fields in the response object: * **Customer Information**: Details about the authenticated customer * **Meter Information**: Configuration and settings of the usage meter * **Customer Meter Information**: * **Consumed Units**: Total units consumed by the customer * **Credited Units**: Total units credited to the customer * **Balance**: The balance of the meter, i.e. the difference between credited and consumed units. ## Webhooks Plugin [Source code](https://github.com/polarsource/polar-adapters/blob/main/packages/polar-betterauth/src/plugins/webhooks.ts) The `webhooks` plugin can be used to capture incoming events from your Polar organization. To set up the Polar `webhooks` plugin with the BetterAuth client, follow the steps below: Configure a Webhook endpoint in your Polar Organization Settings page by following [this guide](/integrate/webhooks/endpoints). Webhook endpoint is configured at `/api/auth/polar/webhooks`. Add the obtained webhook secret to your application environment as an environment variable (to be used as `process.env.POLAR_WEBHOOK_SECRET`): ```bash .env theme={null} POLAR_WEBHOOK_SECRET="..." ``` Pass the `webhooks` plugin in the `use` property. ```typescript icon="square-js" Webhooks Plugin Example theme={null} import { polar, webhooks // [!code ++] } from "@polar-sh/better-auth"; const auth = betterAuth({ // ... Better Auth config plugins: [ polar({ ... use: [ webhooks({ // [!code ++] secret: process.env.POLAR_WEBHOOK_SECRET, // [!code ++] onCustomerStateChanged: (payload) => // Triggered when anything regarding a customer changes // [!code ++] onOrderPaid: (payload) => // Triggered when an order was paid (purchase, subscription renewal, etc.) // [!code ++] ... // Over 25 granular webhook handlers // [!code ++] onPayload: (payload) => // Catch-all for all events // [!code ++] }) // [!code ++] ], }) ] }); ``` The `webhooks` plugin allows you to invoke handlers for all Polar webhook events: * `onPayload` - Catch-all handler for any incoming Webhook event * `onCheckoutCreated` - Triggered when a checkout is created * `onCheckoutUpdated` - Triggered when a checkout is updated * `onOrderCreated` - Triggered when an order is created * `onOrderPaid` - Triggered when an order is paid * `onOrderRefunded` - Triggered when an order is refunded * `onRefundCreated` - Triggered when a refund is created * `onRefundUpdated` - Triggered when a refund is updated * `onSubscriptionCreated` - Triggered when a subscription is created * `onSubscriptionUpdated` - Triggered when a subscription is updated * `onSubscriptionActive` - Triggered when a subscription becomes active * `onSubscriptionCanceled` - Triggered when a subscription is canceled * `onSubscriptionRevoked` - Triggered when a subscription is revoked * `onSubscriptionUncanceled` - Triggered when a subscription cancellation is reversed * `onProductCreated` - Triggered when a product is created * `onProductUpdated` - Triggered when a product is updated * `onOrganizationUpdated` - Triggered when an organization is updated * `onBenefitCreated` - Triggered when a benefit is created * `onBenefitUpdated` - Triggered when a benefit is updated * `onBenefitGrantCreated` - Triggered when a benefit grant is created * `onBenefitGrantUpdated` - Triggered when a benefit grant is updated * `onBenefitGrantRevoked` - Triggered when a benefit grant is revoked * `onCustomerCreated` - Triggered when a customer is created * `onCustomerUpdated` - Triggered when a customer is updated * `onCustomerDeleted` - Triggered when a customer is deleted * `onCustomerStateChanged` - Triggered when a customer state changes ## Portal Plugin [Source code](https://github.com/polarsource/polar-adapters/blob/main/packages/polar-betterauth/src/plugins/portal.ts) A plugin which enables customer management of their purchases, orders and subscriptions. ```typescript icon="square-js" Portal Plugin Example theme={null} import { polar, checkout, portal // [!code ++] } from "@polar-sh/better-auth"; const auth = betterAuth({ // ... Better Auth config plugins: [ polar({ ... use: [ checkout(...), portal({ returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Customer Portal }) // [!code ++] ], }) ] }); ``` The `portal` plugin gives the BetterAuth Client a set of customer management methods, scoped under `authClient.customer` object. ### 1. Customer Portal Management The following method will redirect the user to the Polar Customer Portal, where they can see their orders, purchases, subscriptions, benefits, etc. ```typescript icon="square-js" Open Customer Portal Example theme={null} await authClient.customer.portal(); ``` ### 2. Customer State The portal plugin also adds a convenient method to retrieve the Customer State. ```typescript icon="square-js" Retrieve Customer State Example theme={null} const { data: customerState } = await authClient.customer.state(); ``` The customer state object contains: * All the data about the customer. * The list of their active subscriptions. This does not include subscriptions done by a parent organization. See the subscription list-method below for more information. * The list of their granted benefits. * The list of their active meters, with their current balance. Using the customer state object, you can determine whether to provision access for the user to your service. Learn more about the Polar Customer State [in the Polar Docs](https://polar.sh/docs/integrate/customer-state). ### 3. Benefits, Orders & Subscriptions The portal plugin adds the following 3 convenient methods for listing benefits, orders & subscriptions relevant to the authenticated user/customer. #### 3.1 Benefits This method only lists granted benefits for the authenticated user/customer. ```typescript icon="square-js" List User Benefits Example theme={null} const { data: benefits } = await authClient.customer.benefits.list({ query: { page: 1, limit: 10, }, }); ``` #### 3.2 Orders This method lists orders like purchases and subscription renewals for the authenticated user/customer. ```typescript icon="square-js" List User Orders Example theme={null} const { data: orders } = await authClient.customer.orders.list({ query: { page: 1, limit: 10, productBillingType: "one_time", // or 'recurring' }, }); ``` Using the Organization ID as the `referenceId` you can retrieve all the subscriptions associated with that organization (instead of the user). To figure out if a user should have access, pass the user's organization ID to see if there is an active subscription for that organization. ```typescript icon="square-js" List Organization Subscriptions Example theme={null} const organizationId = (await authClient.organization.list())?.data?.[0]?.id, const { data: subscriptions } = await authClient.customer.orders.list({ query: { page: 1, limit: 10, active: true, referenceId: organizationId }, }); const userShouldHaveAccess = subscriptions.some( sub => // Your logic to check subscription product or whatever. ) ``` #### 3.3 Subscriptions This method lists the subscriptions associated with authenticated user/customer. ```typescript icon="square-js" List User Subscriptions Example theme={null} const { data: subscriptions } = await authClient.customer.subscriptions.list({ query: { page: 1, limit: 10, active: true, }, }); ``` This will not return subscriptions made by a parent organization to the authenticated user. # Deno Source: https://polar.sh/docs/integrate/sdk/adapters/deno Payments and Checkouts made dead simple with Deno ## Examples * [With Deno](https://github.com/polarsource/examples/tree/main/with-deno) ## Checkout Create a Checkout handler which takes care of redirections. ```typescript icon="square-js" theme={null} import { Checkout } from "jsr:@polar-sh/deno"; Deno.serve( Checkout({ accessToken: "xxx", returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Checkout theme: "dark", // Enforces the theme - System-preferred theme will be set if left omitted }) ); ``` ### Query Params Pass query params to this route. * products `?products=123` * customerId (optional) `?products=123&customerId=xxx` * customerExternalId (optional) `?products=123&customerExternalId=xxx` * customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com` * customerName (optional) `?products=123&customerName=Jane` * metadata (optional) `URL-Encoded JSON string` ## Customer Portal Create a customer portal where your customer can view orders and subscriptions. ```typescript icon="square-js" theme={null} import { CustomerPortal } from "jsr:@polar-sh/deno"; Deno.serve( CustomerPortal({ accessToken: "xxx", getCustomerId: (req) => "", returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Customer Portal server: "sandbox", }) ); ``` ## Webhooks A simple utility which resolves incoming webhook payloads by signing the webhook secret properly. ```typescript icon="square-js" theme={null} import { Webhooks } from "jsr:@polar-sh/deno"; Deno.serve( Webhooks({ webhookSecret: Deno.env.get('POLAR_WEBHOOK_SECRET'), onPayload: async (payload) => /** Handle payload */, }) ); ``` ### Payload Handlers The Webhook handler also supports granular handlers for easy integration. * `onPayload` - Catch-all handler for any incoming Webhook event * `onCheckoutCreated` - Triggered when a checkout is created * `onCheckoutUpdated` - Triggered when a checkout is updated * `onOrderCreated` - Triggered when an order is created * `onOrderPaid` - Triggered when an order is paid * `onOrderRefunded` - Triggered when an order is refunded * `onRefundCreated` - Triggered when a refund is created * `onRefundUpdated` - Triggered when a refund is updated * `onSubscriptionCreated` - Triggered when a subscription is created * `onSubscriptionUpdated` - Triggered when a subscription is updated * `onSubscriptionActive` - Triggered when a subscription becomes active * `onSubscriptionCanceled` - Triggered when a subscription is canceled * `onSubscriptionRevoked` - Triggered when a subscription is revoked * `onSubscriptionUncanceled` - Triggered when a subscription cancellation is reversed * `onProductCreated` - Triggered when a product is created * `onProductUpdated` - Triggered when a product is updated * `onOrganizationUpdated` - Triggered when an organization is updated * `onBenefitCreated` - Triggered when a benefit is created * `onBenefitUpdated` - Triggered when a benefit is updated * `onBenefitGrantCreated` - Triggered when a benefit grant is created * `onBenefitGrantUpdated` - Triggered when a benefit grant is updated * `onBenefitGrantRevoked` - Triggered when a benefit grant is revoked * `onCustomerCreated` - Triggered when a customer is created * `onCustomerUpdated` - Triggered when a customer is updated * `onCustomerDeleted` - Triggered when a customer is deleted * `onCustomerStateChanged` - Triggered when a customer state changes # Elysia Source: https://polar.sh/docs/integrate/sdk/adapters/elysia Payments and Checkouts made dead simple with Elysia ## Examples * [With Elysia](https://github.com/polarsource/examples/tree/main/with-elysia) ## Installation Install the required Polar packages using the following command: ```bash Terminal theme={null} npm install zod @polar-sh/elysia ``` ```bash Terminal theme={null} yarn add zod @polar-sh/elysia ``` ```bash Terminal theme={null} pnpm add zod @polar-sh/elysia ``` ### Checkout Create a Checkout handler which takes care of redirections. ```typescript icon="square-js" theme={null} import { Elysia } from "elysia"; import { Checkout } from "@polar-sh/elysia"; const app = new Elysia(); app.get( "/checkout", Checkout({ accessToken: "xxx", // Or set an environment variable to POLAR_ACCESS_TOKEN successUrl: process.env.SUCCESS_URL, returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Checkout server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise theme: "dark", // Enforces the theme - System-preferred theme will be set if left omitted }) ); ``` #### Query Params Pass query params to this route. * products `?products=123` * customerId (optional) `?products=123&customerId=xxx` * customerExternalId (optional) `?products=123&customerExternalId=xxx` * customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com` * customerName (optional) `?products=123&customerName=Jane` * metadata (optional) `URL-Encoded JSON string` ### Customer Portal Create a customer portal where your customer can view orders and subscriptions. ```typescript icon="square-js" theme={null} import { Elysia } from "elysia"; import { CustomerPortal } from "@polar-sh/elysia"; const app = new Elysia(); app.get( "/portal", CustomerPortal({ accessToken: "xxx", // Or set an environment variable to POLAR_ACCESS_TOKEN getCustomerId: (event) => "", // Function to resolve a Polar Customer ID returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Customer Portal server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }) ); ``` ### Webhooks A simple utility which resolves incoming webhook payloads by signing the webhook secret properly. ```typescript icon="square-js" theme={null} import { Elysia } from 'elysia' import { Webhooks } from "@polar-sh/elysia"; const app = new Elysia() app.post('/polar/webhooks', Webhooks({ webhookSecret: process.env.POLAR_WEBHOOK_SECRET!, onPayload: async (payload) => /** Handle payload */, })) ``` ### Payload Handlers The Webhook handler also supports granular handlers for easy integration. * `onPayload` - Catch-all handler for any incoming Webhook event * `onCheckoutCreated` - Triggered when a checkout is created * `onCheckoutUpdated` - Triggered when a checkout is updated * `onOrderCreated` - Triggered when an order is created * `onOrderPaid` - Triggered when an order is paid * `onOrderRefunded` - Triggered when an order is refunded * `onRefundCreated` - Triggered when a refund is created * `onRefundUpdated` - Triggered when a refund is updated * `onSubscriptionCreated` - Triggered when a subscription is created * `onSubscriptionUpdated` - Triggered when a subscription is updated * `onSubscriptionActive` - Triggered when a subscription becomes active * `onSubscriptionCanceled` - Triggered when a subscription is canceled * `onSubscriptionRevoked` - Triggered when a subscription is revoked * `onSubscriptionUncanceled` - Triggered when a subscription cancellation is reversed * `onProductCreated` - Triggered when a product is created * `onProductUpdated` - Triggered when a product is updated * `onOrganizationUpdated` - Triggered when an organization is updated * `onBenefitCreated` - Triggered when a benefit is created * `onBenefitUpdated` - Triggered when a benefit is updated * `onBenefitGrantCreated` - Triggered when a benefit grant is created * `onBenefitGrantUpdated` - Triggered when a benefit grant is updated * `onBenefitGrantRevoked` - Triggered when a benefit grant is revoked * `onCustomerCreated` - Triggered when a customer is created * `onCustomerUpdated` - Triggered when a customer is updated * `onCustomerDeleted` - Triggered when a customer is deleted * `onCustomerStateChanged` - Triggered when a customer state changes # Express Source: https://polar.sh/docs/integrate/sdk/adapters/express Payments and Checkouts made dead simple with Express ## Examples * [With Express v4](https://github.com/polarsource/examples/tree/main/with-express-v4) ## Installation Install the required Polar packages using the following command: ```bash Terminal theme={null} npm install zod @polar-sh/express ``` ```bash Terminal theme={null} yarn add zod @polar-sh/express ``` ```bash Terminal theme={null} pnpm add zod @polar-sh/express ``` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript theme={null} import express from "express"; import { Checkout } from "@polar-sh/express"; const app = express(); app.get( "/checkout", Checkout({ accessToken: "xxx", // Or set an environment variable to POLAR_ACCESS_TOKEN successUrl: process.env.SUCCESS_URL, returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Checkout server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise theme: "dark", // Enforces the theme - System-preferred theme will be set if left omitted }) ); ``` ### Query Params Pass query params to this route. * products `?products=123` * customerId (optional) `?products=123&customerId=xxx` * customerExternalId (optional) `?products=123&customerExternalId=xxx` * customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com` * customerName (optional) `?products=123&customerName=Jane` * metadata (optional) `URL-Encoded JSON string` ## Customer Portal Create a customer portal where your customer can view orders and subscriptions. ```typescript theme={null} import express from "express"; import { CustomerPortal } from "@polar-sh/express"; const app = express(); app.get( "/portal", CustomerPortal({ accessToken: "xxx", // Or set an environment variable to POLAR_ACCESS_TOKEN getCustomerId: (event) => "", // Function to resolve a Polar Customer ID returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Customer Portal server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }) ); ``` ## Webhooks A simple utility which resolves incoming webhook payloads by signing the webhook secret properly. ```typescript theme={null} import express from 'express' import { Webhooks } from "@polar-sh/express"; const app = express() app .use(express.json()) .post('/polar/webhooks', Webhooks({ webhookSecret: process.env.POLAR_WEBHOOK_SECRET!, onPayload: async (payload) => /** Handle payload */, })) ``` ### Payload Handlers The Webhook handler also supports granular handlers for easy integration. * `onPayload` - Catch-all handler for any incoming Webhook event * `onCheckoutCreated` - Triggered when a checkout is created * `onCheckoutUpdated` - Triggered when a checkout is updated * `onOrderCreated` - Triggered when an order is created * `onOrderPaid` - Triggered when an order is paid * `onOrderRefunded` - Triggered when an order is refunded * `onRefundCreated` - Triggered when a refund is created * `onRefundUpdated` - Triggered when a refund is updated * `onSubscriptionCreated` - Triggered when a subscription is created * `onSubscriptionUpdated` - Triggered when a subscription is updated * `onSubscriptionActive` - Triggered when a subscription becomes active * `onSubscriptionCanceled` - Triggered when a subscription is canceled * `onSubscriptionRevoked` - Triggered when a subscription is revoked * `onSubscriptionUncanceled` - Triggered when a subscription cancellation is reversed * `onProductCreated` - Triggered when a product is created * `onProductUpdated` - Triggered when a product is updated * `onOrganizationUpdated` - Triggered when an organization is updated * `onBenefitCreated` - Triggered when a benefit is created * `onBenefitUpdated` - Triggered when a benefit is updated * `onBenefitGrantCreated` - Triggered when a benefit grant is created * `onBenefitGrantUpdated` - Triggered when a benefit grant is updated * `onBenefitGrantRevoked` - Triggered when a benefit grant is revoked * `onCustomerCreated` - Triggered when a customer is created * `onCustomerUpdated` - Triggered when a customer is updated * `onCustomerDeleted` - Triggered when a customer is deleted * `onCustomerStateChanged` - Triggered when a customer state changes # Fastify Source: https://polar.sh/docs/integrate/sdk/adapters/fastify Payments and Checkouts made dead simple with Fastify ## Examples * [With Fastify](https://github.com/polarsource/examples/tree/main/with-fastify) ## Installation Install the required Polar packages using the following command: ```bash Terminal theme={null} npm install zod @polar-sh/fastify ``` ```bash Terminal theme={null} yarn add zod @polar-sh/fastify ``` ```bash Terminal theme={null} pnpm add zod @polar-sh/fastify ``` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript theme={null} import fastify from "fastify"; import { Checkout } from "@polar-sh/fastify"; fastify().get( "/checkout", Checkout({ accessToken: "xxx", // Or set an environment variable to POLAR_ACCESS_TOKEN successUrl: process.env.SUCCESS_URL, returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Checkout server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise theme: "dark", // Enforces the theme - System-preferred theme will be set if left omitted }) ); ``` ### Query Params Pass query params to this route. * products `?products=123` * customerId (optional) `?products=123&customerId=xxx` * customerExternalId (optional) `?products=123&customerExternalId=xxx` * customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com` * customerName (optional) `?products=123&customerName=Jane` * metadata (optional) `URL-Encoded JSON string` ## Customer Portal Create a customer portal where your customer can view orders and subscriptions. ```typescript theme={null} import fastify from "fastify"; import { CustomerPortal } from "@polar-sh/fastify"; fastify().get( "/portal", CustomerPortal({ accessToken: "xxx", // Or set an environment variable to POLAR_ACCESS_TOKEN getCustomerId: (event) => "", // Function to resolve a Polar Customer ID returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Customer Portal server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }) ); ``` ## Webhooks A simple utility which resolves incoming webhook payloads by signing the webhook secret properly. ```typescript theme={null} import fastify from 'fastify' import { Webhooks } from "@polar-sh/fastify"; fastify.post('/polar/webhooks', Webhooks({ webhookSecret: process.env.POLAR_WEBHOOK_SECRET!, onPayload: async (payload) => /** Handle payload */, })) ``` ### Payload Handlers The Webhook handler also supports granular handlers for easy integration. * `onPayload` - Catch-all handler for any incoming Webhook event * `onCheckoutCreated` - Triggered when a checkout is created * `onCheckoutUpdated` - Triggered when a checkout is updated * `onOrderCreated` - Triggered when an order is created * `onOrderPaid` - Triggered when an order is paid * `onOrderRefunded` - Triggered when an order is refunded * `onRefundCreated` - Triggered when a refund is created * `onRefundUpdated` - Triggered when a refund is updated * `onSubscriptionCreated` - Triggered when a subscription is created * `onSubscriptionUpdated` - Triggered when a subscription is updated * `onSubscriptionActive` - Triggered when a subscription becomes active * `onSubscriptionCanceled` - Triggered when a subscription is canceled * `onSubscriptionRevoked` - Triggered when a subscription is revoked * `onSubscriptionUncanceled` - Triggered when a subscription cancellation is reversed * `onProductCreated` - Triggered when a product is created * `onProductUpdated` - Triggered when a product is updated * `onOrganizationUpdated` - Triggered when an organization is updated * `onBenefitCreated` - Triggered when a benefit is created * `onBenefitUpdated` - Triggered when a benefit is updated * `onBenefitGrantCreated` - Triggered when a benefit grant is created * `onBenefitGrantUpdated` - Triggered when a benefit grant is updated * `onBenefitGrantRevoked` - Triggered when a benefit grant is revoked * `onCustomerCreated` - Triggered when a customer is created * `onCustomerUpdated` - Triggered when a customer is updated * `onCustomerDeleted` - Triggered when a customer is deleted * `onCustomerStateChanged` - Triggered when a customer state changes # Hono Source: https://polar.sh/docs/integrate/sdk/adapters/hono Payments and Checkouts made dead simple with Hono ## Examples * [With Hono and Cloudflare Workers](https://github.com/polarsource/examples/tree/main/with-hono-cloudflare-workers) ## Installation Install the required Polar packages using the following command: ```bash Terminal theme={null} npm install zod @polar-sh/hono ``` ```bash Terminal theme={null} yarn add zod @polar-sh/hono ``` ```bash Terminal theme={null} pnpm add zod @polar-sh/hono ``` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript theme={null} import { Hono } from "hono"; import { Checkout } from "@polar-sh/hono"; const app = new Hono(); app.get( "/checkout", Checkout({ accessToken: "xxx", // Or set an environment variable to POLAR_ACCESS_TOKEN successUrl: process.env.SUCCESS_URL, returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Checkout server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise theme: "dark", // Enforces the theme - System-preferred theme will be set if left omitted }) ); ``` ### Query Params Pass query params to this route. * products `?products=123` * customerId (optional) `?products=123&customerId=xxx` * customerExternalId (optional) `?products=123&customerExternalId=xxx` * customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com` * customerName (optional) `?products=123&customerName=Jane` * metadata (optional) `URL-Encoded JSON string` ## Customer Portal Create a customer portal where your customer can view orders and subscriptions. ```typescript theme={null} import { Hono } from "hono"; import { CustomerPortal } from "@polar-sh/hono"; const app = new Hono(); app.get( "/portal", CustomerPortal({ accessToken: "xxx", // Or set an environment variable to POLAR_ACCESS_TOKEN getCustomerId: (event) => "", // Function to resolve a Polar Customer ID returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Customer Portal server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }) ); ``` ## Webhooks A simple utility which resolves incoming webhook payloads by signing the webhook secret properly. ```typescript theme={null} import { Hono } from 'hono' import { Webhooks } from "@polar-sh/hono"; const app = new Hono() app.post('/polar/webhooks', Webhooks({ webhookSecret: process.env.POLAR_WEBHOOK_SECRET!, onPayload: async (payload) => /** Handle payload */, })) ``` ### Payload Handlers The Webhook handler also supports granular handlers for easy integration. * `onPayload` - Catch-all handler for any incoming Webhook event * `onCheckoutCreated` - Triggered when a checkout is created * `onCheckoutUpdated` - Triggered when a checkout is updated * `onOrderCreated` - Triggered when an order is created * `onOrderPaid` - Triggered when an order is paid * `onOrderRefunded` - Triggered when an order is refunded * `onRefundCreated` - Triggered when a refund is created * `onRefundUpdated` - Triggered when a refund is updated * `onSubscriptionCreated` - Triggered when a subscription is created * `onSubscriptionUpdated` - Triggered when a subscription is updated * `onSubscriptionActive` - Triggered when a subscription becomes active * `onSubscriptionCanceled` - Triggered when a subscription is canceled * `onSubscriptionRevoked` - Triggered when a subscription is revoked * `onSubscriptionUncanceled` - Triggered when a subscription cancellation is reversed * `onProductCreated` - Triggered when a product is created * `onProductUpdated` - Triggered when a product is updated * `onOrganizationUpdated` - Triggered when an organization is updated * `onBenefitCreated` - Triggered when a benefit is created * `onBenefitUpdated` - Triggered when a benefit is updated * `onBenefitGrantCreated` - Triggered when a benefit grant is created * `onBenefitGrantUpdated` - Triggered when a benefit grant is updated * `onBenefitGrantRevoked` - Triggered when a benefit grant is revoked * `onCustomerCreated` - Triggered when a customer is created * `onCustomerUpdated` - Triggered when a customer is updated * `onCustomerDeleted` - Triggered when a customer is deleted * `onCustomerStateChanged` - Triggered when a customer state changes # Laravel Source: https://polar.sh/docs/integrate/sdk/adapters/laravel Payments and Checkouts made dead simple with Laravel Seamlessly integrate Polar.sh subscriptions and payments into your Laravel application. This package provides an elegant way to handle subscriptions, manage recurring payments, and interact with Polar's API. With built-in support for webhooks, subscription management, and a fluent API, you can focus on building your application while we handle the complexities of subscription billing. This provider is not maintained or officially supported by Polar. Use at your own discretion. If you have questions about the provider, please contact the project maintainer. ## Installation **Step 1:** You can install the package via composer: ```bash Terminal theme={null} composer require danestves/laravel-polar ``` **Step 2:** Run `:install`: ```bash Terminal theme={null} php artisan polar:install ``` This will publish the config, migrations and views, and ask to run the migrations. Or publish and run the migrations individually: ```bash Terminal theme={null} php artisan vendor:publish --tag="polar-migrations" ``` ```bash Terminal theme={null} php artisan vendor:publish --tag="polar-config" ``` ```bash Terminal theme={null} php artisan vendor:publish --tag="polar-views" ``` ```bash Terminal theme={null} php artisan migrate ``` This is the contents of the published config file: ```php theme={null} Settings | under the "Developers" section. | */ 'access_token' => env('POLAR_ACCESS_TOKEN'), /* |-------------------------------------------------------------------------- | Polar Webhook Secret |-------------------------------------------------------------------------- | | The Polar webhook secret is used to verify that the webhook requests | are coming from Polar. You can find your webhook secret in the Polar | dashboard > Settings > Webhooks on each registered webhook. | | We (the developers) recommend using a single webhook for all your | integrations. This way you can use the same secret for all your | integrations and you don't have to manage multiple webhooks. | */ 'webhook_secret' => env('POLAR_WEBHOOK_SECRET'), /* |-------------------------------------------------------------------------- | Polar Url Path |-------------------------------------------------------------------------- | | This is the base URI where routes from Polar will be served | from. The URL built into Polar is used by default; however, | you can modify this path as you see fit for your application. | */ 'path' => env('POLAR_PATH', 'polar'), /* |-------------------------------------------------------------------------- | Default Redirect URL |-------------------------------------------------------------------------- | | This is the default redirect URL that will be used when a customer | is redirected back to your application after completing a purchase | from a checkout session in your Polar account. | */ 'redirect_url' => null, /* |-------------------------------------------------------------------------- | Currency Locale |-------------------------------------------------------------------------- | | This is the default locale in which your money values are formatted in | for display. To utilize other locales besides the default "en" locale | verify you have to have the "intl" PHP extension installed on the system. | */ 'currency_locale' => env('POLAR_CURRENCY_LOCALE', 'en'), ]; ``` ## Usage ### Access Token Configure your access token. Create a new token in the Polar Dashboard > Settings > Developers and paste it in the `.env` file. * [https://sandbox.polar.sh/dashboard/ORG\_SLUG/settings](https://sandbox.polar.sh/dashboard/ORG_SLUG/settings) (Sandbox) * [https://polar.sh/dashboard/ORG\_SLUG/settings](https://polar.sh/dashboard/ORG_SLUG/settings) (Production) ```bash Terminal theme={null} POLAR_ACCESS_TOKEN="" ``` ### Webhook Secret Configure your webhook secret. Create a new webhook in the Polar Dashboard > Settings > Webhooks. * [https://sandbox.polar.sh/dashboard/ORG\_SLUG/settings/webhooks](https://sandbox.polar.sh/dashboard/ORG_SLUG/settings/webhooks) (Sandbox) * [https://polar.sh/dashboard/ORG\_SLUG/settings/webhooks](https://polar.sh/dashboard/ORG_SLUG/settings/webhooks) (Production) Configure the webhook for the following events that this pacckage supports: * `order.created` * `order.updated` * `subscription.created` * `subscription.updated` * `subscription.active` * `subscription.canceled` * `subscription.revoked` * `benefit_grant.created` * `benefit_grant.updated` * `benefit_grant.revoked` ```bash Terminal theme={null} POLAR_WEBHOOK_SECRET="" ``` ### Billable Trait Letโ€™s make sure everythingโ€™s ready for your customers to checkout smoothly. ๐Ÿ›’ First, weโ€™ll need to set up a model to handle billingโ€”donโ€™t worry, itโ€™s super simple! In most cases, this will be your appโ€™s User model. Just add the Billable trait to your model like this (youโ€™ll import it from the package first, of course): ```php theme={null} use Danestves\LaravelPolar\Billable; class User extends Authenticatable { use Billable; } ``` Now the user model will have access to the methods provided by the package. You can make any model billable by adding the trait to it, not just the User model. ### Polar Script Polar includes a JavaScript script that you can use to initialize the [Polar Embedded Checkout](https://polar.sh/docs/features/checkout/embed). If you going to use this functionality, you can use the `@polarEmbedScript` directive to include the script in your views inside the `` tag. ```blade theme={null} ... @polarEmbedScript ``` ### Webhooks This package includes a webhook handler that will handle the webhooks from Polar. #### Webhooks & CSRF Protection Incoming webhooks should not be affected by [CSRF protection](https://laravel.com/docs/csrf). To prevent this, add your webhook path to the except list of your `App\Http\Middleware\VerifyCsrfToken` middleware: ```php theme={null} protected $except = [ 'polar/*', ]; ``` Or if you're using Laravel v11 and up, you should exclude `polar/*` in your application's `bootstrap/app.php` file: ```php theme={null} ->withMiddleware(function (Middleware $middleware) { $middleware->validateCsrfTokens(except: [ 'polar/*', ]); }) ``` ### Commands This package includes a list of commands that you can use to retrieve information about your Polar account. | Command | Description | | ---------------------------- | ------------------------------------------ | | `php artisan polar:products` | List all available products with their ids | ### Checkouts #### Single Payments To create a checkout to show only a single payment, pass a single items to the array of products when creating the checkout. ```php theme={null} use Illuminate\Http\Request; Route::get('/subscribe', function (Request $request) { return $request->user()->checkout(['product_id_123']); }); ``` If you want to show multiple products that the user can choose from, you can pass an array of product ids to the `checkout` method. ```php theme={null} use Illuminate\Http\Request; Route::get('/subscribe', function (Request $request) { return $request->user()->checkout(['product_id_123', 'product_id_456']); }); ``` This could be useful if you want to offer monthly, yearly, and lifetime plans for example. > \[!NOTE] > If you are requesting the checkout a lot of times we recommend you to cache the URL returned by the `checkout` method. #### Custom Price You can override the price of a product using the `charge` method. ```php theme={null} use Illuminate\Http\Request; Route::get('/subscribe', function (Request $request) { return $request->user()->charge(1000, ['product_id_123']); }); ``` #### Embedded Checkout Instead of redirecting the user you can create the checkout link, pass it to the page and use our blade component: ```php theme={null} use Illuminate\Http\Request; Route::get('/billing', function (Request $request) { $checkout = $request->user()->checkout(['product_id_123']); return view('billing', ['checkout' => $checkout]); }); ``` Now we can use the button like this: ```blade theme={null} ``` The component accepts the normal props that a link element accepts. You can change the theme of the embedded checkout by using the following prop: ```blade theme={null} ``` It defaults to light theme, so you only need to pass the prop if you want to change it. ### Prefill Customer Information You can override the user data using the following methods in your models provided by the `Billable` trait. ```php theme={null} public function polarName(): ?string; // default: $model->name public function polarEmail(): ?string; // default: $model->email ``` ### Redirects After Purchase You can redirect the user to a custom page after the purchase using the `withSuccessUrl` method: ```php theme={null} $request->user()->checkout('variant-id') ->withSuccessUrl(url('/success')); ``` You can also add the `checkout_id={CHECKOUT_ID}` query parameter to the URL to retrieve the checkout session id: ```php theme={null} $request->user()->checkout('variant-id') ->withSuccessUrl(url('/success?checkout_id={CHECKOUT_ID}')); ``` ### Custom metadata and customer metadata You can add custom metadata to the checkout session using the `withMetadata` method: ```php theme={null} $request->user()->checkout('variant-id') ->withMetadata(['key' => 'value']); ``` You can also add customer metadata to the checkout session using the `withCustomerMetadata` method: ```php theme={null} $request->user()->checkout('variant-id') ->withCustomerMetadata(['key' => 'value']); ``` These will then be available in the relevant webhooks for you. #### Reserved Keywords When working with custom data, this library has a few reserved terms. * `billable_id` * `billable_type` * `subscription_type` Using any of these will result in an exception being thrown. ### Customers #### Customer Portal Customers can update their personal information (e.g., name, email address) by accessing their [self-service customer portal](https://polar.sh/docs/features/customer-portal). To redirect customers to this portal, call the `redirectToCustomerPortal()` method on your billable model (e.g., the User model). ```php theme={null} use Illuminate\Http\Request; Route::get('/customer-portal', function (Request $request) { return $request->user()->redirectToCustomerPortal(); }); ``` Optionally, you can obtain the signed customer portal URL directly: ```php theme={null} $url = $user->customerPortalUrl(); ``` ### Orders #### Retrieving Orders You can retrieve orders by using the `orders` relationship on the billable model: ```blade theme={null} @foreach ($user->orders as $order) @endforeach
{{ $order->ordered_at->toFormattedDateString() }} {{ $order->polar_id }} {{ $order->amount }} {{ $order->tax_amount }} {{ $order->refunded_amount }} {{ $order->refunded_tax_amount }} {{ $order->currency }}
``` #### Check order status You can check the status of an order by using the `status` attribute: ```php theme={null} $order->status; ``` Or you can use some of the helper methods offers by the `Order` model: ```php theme={null} $order->paid(); ``` Aside from that, you can run two other checks: refunded, and partially refunded. If the order is refunded, you can utilize the refunded\_at timestamp: ```blade theme={null} @if ($order->refunded()) Order {{ $order->polar_id }} was refunded on {{ $order->refunded_at->toFormattedDateString() }} @endif ``` You may also see if an order was for a certain product: ```php theme={null} if ($order->hasProduct('product_id_123')) { // ... } ``` Or for an specific price: ```php theme={null} if ($order->hasPrice('price_id_123')) { // ... } ``` Furthermore, you can check if a consumer has purchased a specific product: ```php theme={null} if ($user->hasPurchasedProduct('product_id_123')) { // ... } ``` Or for an specific price: ```php theme={null} if ($user->hasPurchasedPrice('price_id_123')) { // ... } ``` ### Subscriptions #### Creating Subscriptions Starting a subscription is simple. For this, we require our product's variant id. Copy the product id and start a new subscription checkout using your billable model: ```php theme={null} use Illuminate\Http\Request; Route::get('/subscribe', function (Request $request) { return $request->user()->subscribe('product_id_123'); }); ``` When a customer completes their checkout, the incoming `SubscriptionCreated` event webhook connects it to your billable model in the database. You may then get the subscription from your billable model: ```php theme={null} $subscription = $user->subscription(); ``` #### Checking Subscription Status Once a consumer has subscribed to your services, you can use a variety of methods to check on the status of their subscription. The most basic example is to check if a customer has a valid subscription. ```php theme={null} if ($user->subscribed()) { // ... } ``` You can utilize this in a variety of locations in your app, such as middleware, rules, and so on, to provide services. To determine whether an individual subscription is valid, you can use the `valid` method: ```php theme={null} if ($user->subscription()->valid()) { // ... } ``` This method, like the subscribed method, returns true if your membership is active, on trial, past due, or cancelled during its grace period. You may also check if a subscription is for a certain product: ```php theme={null} if ($user->subscription()->hasProduct('product_id_123')) { // ... } ``` Or for a certain price: ```php theme={null} if ($user->subscription()->hasPrice('price_id_123')) { // ... } ``` If you wish to check if a subscription is on a specific price while being valid, you can use: ```php theme={null} if ($user->subscribedToPrice('price_id_123')) { // ... } ``` Alternatively, if you use different [subscription types](#multiple-subscriptions), you can pass a type as an additional parameter: ```php theme={null} if ($user->subscribed('swimming')) { // ... } if ($user->subscribedToPrice('price_id_123', 'swimming')) { // ... } ``` #### Cancelled Status To see if a user has cancelled their subscription, you can use the cancelled method: ```php theme={null} if ($user->subscription()->cancelled()) { // ... } ``` When they are in their grace period, you can utilize the `onGracePeriod` check. ```php theme={null} if ($user->subscription()->onGracePeriod()) { // ... } ``` #### Past Due Status If a recurring payment fails, the subscription will become past due. This indicates that the subscription is still valid, but your customer's payments will be retried in two weeks. ```php theme={null} if ($user->subscription()->pastDue()) { // ... } ``` #### Subscription Scopes There are several subscription scopes available for querying subscriptions in specific states: ```php theme={null} // Get all active subscriptions... $subscriptions = Subscription::query()->active()->get(); // Get all of the cancelled subscriptions for a specific user... $subscriptions = $user->subscriptions()->cancelled()->get(); ``` Here's all available scopes: ```php theme={null} Subscription::query()->incomplete(); Subscription::query()->incompleteExpired(); Subscription::query()->onTrial(); Subscription::query()->active(); Subscription::query()->pastDue(); Subscription::query()->unpaid(); Subscription::query()->cancelled(); ``` #### Changing Plans When a consumer is on a monthly plan, they may desire to upgrade to a better plan, alter their payments to an annual plan, or drop to a lower-cost plan. In these cases, you can allow them to swap plans by giving a different product id to the `swap` method: ```php theme={null} use App\Models\User; $user = User::find(1); $user->subscription()->swap('product_id_123'); ``` This will change the customer's subscription plan, however billing will not occur until the next payment cycle. If you want to immediately invoice the customer, you can use the `swapAndInvoice` method instead. ```php theme={null} $user = User::find(1); $user->subscription()->swapAndInvoice('product_id_123'); ``` #### Multiple Subscriptions In certain situations, you may wish to allow your consumer to subscribe to numerous subscription kinds. For example, a gym may provide a swimming and weight lifting subscription. You can let your customers subscribe to one or both. To handle the various subscriptions, you can offer a type of subscription as the second argument when creating a new one: ```php theme={null} $user = User::find(1); $checkout = $user->subscribe('product_id_123', 'swimming'); ``` You can now always refer to this specific subscription type by passing the type argument when getting it: ```php theme={null} $user = User::find(1); // Retrieve the swimming subscription type... $subscription = $user->subscription('swimming'); // Swap plans for the gym subscription type... $user->subscription('gym')->swap('product_id_123'); // Cancel the swimming subscription... $user->subscription('swimming')->cancel(); ``` #### Cancelling Subscriptions To cancel a subscription, call the `cancel` method. ```php theme={null} $user = User::find(1); $user->subscription()->cancel(); ``` This will cause your subscription to be cancelled. If you cancel your subscription in the middle of the cycle, it will enter a grace period, and the ends\_at column will be updated. The customer will continue to have access to the services offered for the duration of the period. You may check the grace period by calling the `onGracePeriod` method: ```php theme={null} if ($user->subscription()->onGracePeriod()) { // ... } ``` Polar does not offer immediate cancellation. To resume a subscription while it is still in its grace period, use the resume method. ```php theme={null} $user->subscription()->resume(); ``` When a cancelled subscription approaches the end of its grace period, it becomes expired and cannot be resumed. ### Handling Webhooks Polar can send webhooks to your app, allowing you to react. By default, this package handles the majority of the work for you. If you have properly configured webhooks, it will listen for incoming events and update your database accordingly. We recommend activating all event kinds so you may easily upgrade in the future. #### Webhook Events * `Danestves\LaravelPolar\Events\BenefitGrantCreated` * `Danestves\LaravelPolar\Events\BenefitGrantUpdated` * `Danestves\LaravelPolar\Events\BenefitGrantRevoked` * `Danestves\LaravelPolar\Events\OrderCreated` * `Danestves\LaravelPolar\Events\OrderRefunded` * `Danestves\LaravelPolar\Events\SubscriptionActive` * `Danestves\LaravelPolar\Events\SubscriptionCanceled` * `Danestves\LaravelPolar\Events\SubscriptionCreated` * `Danestves\LaravelPolar\Events\SubscriptionRevoked` * `Danestves\LaravelPolar\Events\SubscriptionUpdated` Each of these events has a billable `$model` object and an event `$payload`. The subscription events also include the `$subscription` object. These can be accessed via the public properties. If you wish to respond to these events, you must establish listeners for them. For example, you may wish to react when a subscription is updated. ```php theme={null} payload['type'] === 'subscription.updated') { // Handle the incoming event... } } } ``` The [Polar documentation](https://polar.sh/docs/integrate/webhooks/events) includes an example payload. Laravel v11 and up will automatically discover the listener. If you're using Laravel v10 or lower, you should configure it in your app's `EventServiceProvider`: ```php theme={null} [ PolarEventListener::class, ], ]; } ``` ## Testing ```bash Terminal theme={null} composer test ``` ## Changelog Please see [CHANGELOG](https://github.com/danestves/laravel-polar/blob/main/CHANGELOG.md) for more information on what has changed recently. ## License The MIT License (MIT). Please see [License File](https://github.com/danestves/laravel-polar/blob/main/LICENSE.md) for more information. # Next.js Source: https://polar.sh/docs/integrate/sdk/adapters/nextjs Payments and Checkouts made dead simple with Next.js ## Examples * [With Next.js](https://github.com/polarsource/examples/tree/main/with-nextjs) * [With Next.js and Upstash QStash](https://github.com/polarsource/examples/tree/main/with-nextjs-qstash-schedule-downgrades) * [With Next.js, Better Auth and Cloudflare Workers](https://github.com/polarsource/examples/tree/main/with-nextjs-better-auth-cloudflare-workers) ## Installation Install the required Polar packages using the following command: ```bash Terminal theme={null} npm install zod @polar-sh/nextjs ``` ```bash Terminal theme={null} yarn add zod @polar-sh/nextjs ``` ```bash Terminal theme={null} pnpm add zod @polar-sh/nextjs ``` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript icon="square-js" checkout/route.ts theme={null} import { Checkout } from "@polar-sh/nextjs"; export const GET = Checkout({ accessToken: process.env.POLAR_ACCESS_TOKEN, successUrl: process.env.SUCCESS_URL, returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Checkout server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise theme: "dark", // Enforces the theme - System-preferred theme will be set if left omitted }); ``` ### Query Params Pass query params to this route. * products `?products=123` * customerId (optional) `?products=123&customerId=xxx` * customerExternalId (optional) `?products=123&customerExternalId=xxx` * customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com` * customerName (optional) `?products=123&customerName=Jane` * metadata (optional) `URL-Encoded JSON string` ## Customer Portal Create a customer portal where your customer can view orders and subscriptions. ```typescript icon="square-js" portal/route.ts theme={null} import { CustomerPortal } from "@polar-sh/nextjs"; export const GET = CustomerPortal({ accessToken: process.env.POLAR_ACCESS_TOKEN, getCustomerId: (req: NextRequest) => "", // Function to resolve a Polar Customer ID returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Customer Portal server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }); ``` ## Webhooks A simple utility which resolves incoming webhook payloads by signing the webhook secret properly. ```typescript icon="square-js" api/webhook/polar/route.ts theme={null} import { Webhooks } from "@polar-sh/nextjs"; export const POST = Webhooks({ webhookSecret: process.env.POLAR_WEBHOOK_SECRET!, onPayload: async (payload) => { // Handle the payload // No need to return an acknowledge response }, }); ``` ### Payload Handlers The Webhook handler also supports granular handlers for easy integration. * `onPayload` - Catch-all handler for any incoming Webhook event * `onCheckoutCreated` - Triggered when a checkout is created * `onCheckoutUpdated` - Triggered when a checkout is updated * `onOrderCreated` - Triggered when an order is created * `onOrderPaid` - Triggered when an order is paid * `onOrderRefunded` - Triggered when an order is refunded * `onRefundCreated` - Triggered when a refund is created * `onRefundUpdated` - Triggered when a refund is updated * `onSubscriptionCreated` - Triggered when a subscription is created * `onSubscriptionUpdated` - Triggered when a subscription is updated * `onSubscriptionActive` - Triggered when a subscription becomes active * `onSubscriptionCanceled` - Triggered when a subscription is canceled * `onSubscriptionRevoked` - Triggered when a subscription is revoked * `onSubscriptionUncanceled` - Triggered when a subscription cancellation is reversed * `onProductCreated` - Triggered when a product is created * `onProductUpdated` - Triggered when a product is updated * `onOrganizationUpdated` - Triggered when an organization is updated * `onBenefitCreated` - Triggered when a benefit is created * `onBenefitUpdated` - Triggered when a benefit is updated * `onBenefitGrantCreated` - Triggered when a benefit grant is created * `onBenefitGrantUpdated` - Triggered when a benefit grant is updated * `onBenefitGrantRevoked` - Triggered when a benefit grant is revoked * `onCustomerCreated` - Triggered when a customer is created * `onCustomerUpdated` - Triggered when a customer is updated * `onCustomerDeleted` - Triggered when a customer is deleted * `onCustomerStateChanged` - Triggered when a customer state changes # Nuxt Source: https://polar.sh/docs/integrate/sdk/adapters/nuxt Payments and Checkouts made dead simple with Nuxt ## Examples * [With Nuxt](https://github.com/polarsource/examples/tree/main/with-nuxt) ## Installation Install the required Polar packages using the following command: ```bash Terminal theme={null} npm install zod @polar-sh/nuxt ``` ```bash Terminal theme={null} yarn add zod @polar-sh/nuxt ``` ```bash Terminal theme={null} pnpm add zod @polar-sh/nuxt ``` ### Register the module Add the module to your `nuxt.config.ts`: ```typescript theme={null} export default defineNuxtConfig({ modules: ["@polar-sh/nuxt"], }); ``` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript icon="square-js" server/routes/api/checkout.post.ts theme={null} export default defineEventHandler((event) => { const { private: { polarAccessToken, polarCheckoutSuccessUrl, polarServer }, } = useRuntimeConfig(); const checkoutHandler = Checkout({ accessToken: polarAccessToken, successUrl: polarCheckoutSuccessUrl, returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Checkout server: polarServer as "sandbox" | "production", theme: "dark", // Enforces the theme - System-preferred theme will be set if left omitted }); return checkoutHandler(event); }); ``` ### Query Params Pass query params to this route. * products `?products=123` * customerId (optional) `?products=123&customerId=xxx` * customerExternalId (optional) `?products=123&customerExternalId=xxx` * customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com` * customerName (optional) `?products=123&customerName=Jane` * metadata (optional) `URL-Encoded JSON string` ## Customer Portal Create a customer portal where your customer can view orders and subscriptions. ```typescript icon="square-js" server/routes/api/portal.get.ts theme={null} export default defineEventHandler((event) => { const { private: { polarAccessToken, polarCheckoutSuccessUrl, polarServer }, } = useRuntimeConfig(); const customerPortalHandler = CustomerPortal({ accessToken: polarAccessToken, returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Customer Portal server: polarServer as "sandbox" | "production", getCustomerId: (event) => { // Use your own logic to get the customer ID - from a database, session, etc. return Promise.resolve("9d89909b-216d-475e-8005-053dba7cff07"); }, }); return customerPortalHandler(event); }); ``` ## Webhooks A simple utility which resolves incoming webhook payloads by signing the webhook secret properly. ```typescript icon="square-js" server/routes/webhook/polar.post.ts theme={null} export default defineEventHandler((event) => { const { private: { polarWebhookSecret }, } = useRuntimeConfig(); const webhooksHandler = Webhooks({ webhookSecret: polarWebhookSecret, onPayload: async (payload) => { // Handle the payload // No need to return an acknowledge response }, }); return webhooksHandler(event); }); ``` ### Payload Handlers The Webhook handler also supports granular handlers for easy integration. * `onPayload` - Catch-all handler for any incoming Webhook event * `onCheckoutCreated` - Triggered when a checkout is created * `onCheckoutUpdated` - Triggered when a checkout is updated * `onOrderCreated` - Triggered when an order is created * `onOrderPaid` - Triggered when an order is paid * `onOrderRefunded` - Triggered when an order is refunded * `onRefundCreated` - Triggered when a refund is created * `onRefundUpdated` - Triggered when a refund is updated * `onSubscriptionCreated` - Triggered when a subscription is created * `onSubscriptionUpdated` - Triggered when a subscription is updated * `onSubscriptionActive` - Triggered when a subscription becomes active * `onSubscriptionCanceled` - Triggered when a subscription is canceled * `onSubscriptionRevoked` - Triggered when a subscription is revoked * `onSubscriptionUncanceled` - Triggered when a subscription cancellation is reversed * `onProductCreated` - Triggered when a product is created * `onProductUpdated` - Triggered when a product is updated * `onOrganizationUpdated` - Triggered when an organization is updated * `onBenefitCreated` - Triggered when a benefit is created * `onBenefitUpdated` - Triggered when a benefit is updated * `onBenefitGrantCreated` - Triggered when a benefit grant is created * `onBenefitGrantUpdated` - Triggered when a benefit grant is updated * `onBenefitGrantRevoked` - Triggered when a benefit grant is revoked * `onCustomerCreated` - Triggered when a customer is created * `onCustomerUpdated` - Triggered when a customer is updated * `onCustomerDeleted` - Triggered when a customer is deleted * `onCustomerStateChanged` - Triggered when a customer state changes # Remix Source: https://polar.sh/docs/integrate/sdk/adapters/remix Payments and Checkouts made dead simple with Remix ## Examples * [With Remix](https://github.com/polarsource/examples/tree/main/with-remix) ## Installation Install the required Polar packages using the following command: ```bash Terminal theme={null} npm install zod @polar-sh/remix ``` ```bash Terminal theme={null} yarn add zod @polar-sh/remix ``` ```bash Terminal theme={null} pnpm add zod @polar-sh/remix ``` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript icon="square-js" app/routes/checkout.tsx theme={null} import { Checkout } from "@polar-sh/remix"; export const loader = Checkout({ accessToken: "xxx", // Or set an environment variable to POLAR_ACCESS_TOKEN successUrl: process.env.SUCCESS_URL, returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Checkout server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise theme: "dark", // Enforces the theme - System-preferred theme will be set if left omitted }); ``` ### Query Params Pass query params to this route. * products `?products=123` * customerId (optional) `?products=123&customerId=xxx` * customerExternalId (optional) `?products=123&customerExternalId=xxx` * customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com` * customerName (optional) `?products=123&customerName=Jane` * metadata (optional) `URL-Encoded JSON string` ## Customer Portal Create a customer portal where your customer can view orders and subscriptions. ```typescript icon="square-js" app/routes/customer-portal.tsx theme={null} import { CustomerPortal } from "@polar-sh/remix"; export const loader = CustomerPortal({ accessToken: "xxx", // Or set an environment variable to POLAR_ACCESS_TOKEN getCustomerId: (event) => "", // Function to resolve a Polar Customer ID returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Customer Portal server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }); ``` ## Webhooks A simple utility which resolves incoming webhook payloads by signing the webhook secret properly. ```typescript icon="square-js" app/routes/webhook.tsx theme={null} import { Webhooks } from "@polar-sh/remix"; export const action = Webhooks({ webhookSecret: process.env.POLAR_WEBHOOK_SECRET!, onPayload: async (payload) => /** Handle payload */, }) ``` ### Payload Handlers The Webhook handler also supports granular handlers for easy integration. * `onPayload` - Catch-all handler for any incoming Webhook event * `onCheckoutCreated` - Triggered when a checkout is created * `onCheckoutUpdated` - Triggered when a checkout is updated * `onOrderCreated` - Triggered when an order is created * `onOrderPaid` - Triggered when an order is paid * `onOrderRefunded` - Triggered when an order is refunded * `onRefundCreated` - Triggered when a refund is created * `onRefundUpdated` - Triggered when a refund is updated * `onSubscriptionCreated` - Triggered when a subscription is created * `onSubscriptionUpdated` - Triggered when a subscription is updated * `onSubscriptionActive` - Triggered when a subscription becomes active * `onSubscriptionCanceled` - Triggered when a subscription is canceled * `onSubscriptionRevoked` - Triggered when a subscription is revoked * `onSubscriptionUncanceled` - Triggered when a subscription cancellation is reversed * `onProductCreated` - Triggered when a product is created * `onProductUpdated` - Triggered when a product is updated * `onOrganizationUpdated` - Triggered when an organization is updated * `onBenefitCreated` - Triggered when a benefit is created * `onBenefitUpdated` - Triggered when a benefit is updated * `onBenefitGrantCreated` - Triggered when a benefit grant is created * `onBenefitGrantUpdated` - Triggered when a benefit grant is updated * `onBenefitGrantRevoked` - Triggered when a benefit grant is revoked * `onCustomerCreated` - Triggered when a customer is created * `onCustomerUpdated` - Triggered when a customer is updated * `onCustomerDeleted` - Triggered when a customer is deleted * `onCustomerStateChanged` - Triggered when a customer state changes # Supabase Source: https://polar.sh/docs/integrate/sdk/adapters/supabase Payments and Checkouts made dead simple with Supabase ## Examples * [With Supabase and React Router v7](https://github.com/polarsource/examples/tree/main/with-react-router-supabase) ## Installation Install the required Polar packages using the following command: ```bash Terminal theme={null} npm install zod @polar-sh/supabase ``` ```bash Terminal theme={null} yarn add zod @polar-sh/supabase ``` ```bash Terminal theme={null} pnpm add zod @polar-sh/supabase ``` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript theme={null} import { Checkout } from "@polar-sh/supabase"; export const GET = Checkout({ accessToken: POLAR_ACCESS_TOKEN, successUrl: POLAR_SUCCESS_URL, returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Checkout server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise theme: "dark", // Enforces the theme - System-preferred theme will be set if left omitted }); ``` ### Query Params Pass query params to this route. * products `?products=123` * customerId (optional) `?products=123&customerId=xxx` * customerExternalId (optional) `?products=123&customerExternalId=xxx` * customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com` * customerName (optional) `?products=123&customerName=Jane` * metadata (optional) `URL-Encoded JSON string` ## Customer Portal Create a customer portal where your customer can view orders and subscriptions. ```typescript theme={null} import { CustomerPortal } from "@polar-sh/supabase"; export const GET = CustomerPortal({ accessToken: POLAR_ACCESS_TOKEN, getCustomerId: (event) => "", // Function to resolve a Polar Customer ID returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Customer Portal server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }); ``` ## Webhooks A simple utility which resolves incoming webhook payloads by signing the webhook secret properly. ```typescript theme={null} import { Webhooks } from '@polar-sh/supabase'; export const POST = Webhooks({ webhookSecret: POLAR_WEBHOOK_SECRET, onPayload: async (payload) => /** Handle payload */, }) ``` ### Payload Handlers The Webhook handler also supports granular handlers for easy integration. * `onPayload` - Catch-all handler for any incoming Webhook event * `onCheckoutCreated` - Triggered when a checkout is created * `onCheckoutUpdated` - Triggered when a checkout is updated * `onOrderCreated` - Triggered when an order is created * `onOrderPaid` - Triggered when an order is paid * `onOrderRefunded` - Triggered when an order is refunded * `onRefundCreated` - Triggered when a refund is created * `onRefundUpdated` - Triggered when a refund is updated * `onSubscriptionCreated` - Triggered when a subscription is created * `onSubscriptionUpdated` - Triggered when a subscription is updated * `onSubscriptionActive` - Triggered when a subscription becomes active * `onSubscriptionCanceled` - Triggered when a subscription is canceled * `onSubscriptionRevoked` - Triggered when a subscription is revoked * `onSubscriptionUncanceled` - Triggered when a subscription cancellation is reversed * `onProductCreated` - Triggered when a product is created * `onProductUpdated` - Triggered when a product is updated * `onOrganizationUpdated` - Triggered when an organization is updated * `onBenefitCreated` - Triggered when a benefit is created * `onBenefitUpdated` - Triggered when a benefit is updated * `onBenefitGrantCreated` - Triggered when a benefit grant is created * `onBenefitGrantUpdated` - Triggered when a benefit grant is updated * `onBenefitGrantRevoked` - Triggered when a benefit grant is revoked * `onCustomerCreated` - Triggered when a customer is created * `onCustomerUpdated` - Triggered when a customer is updated * `onCustomerDeleted` - Triggered when a customer is deleted * `onCustomerStateChanged` - Triggered when a customer state changes # Sveltekit Source: https://polar.sh/docs/integrate/sdk/adapters/sveltekit Payments and Checkouts made dead simple with Sveltekit ## Examples * [With SvelteKit](https://github.com/polarsource/examples/tree/main/with-sveltekit) ## Installation Install the required Polar packages using the following command: ```bash Terminal theme={null} npm install zod @polar-sh/sveltekit ``` ```bash Terminal theme={null} yarn add zod @polar-sh/sveltekit ``` ```bash Terminal theme={null} pnpm add zod @polar-sh/sveltekit ``` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript icon="square-js" src/routes/checkout/+server.ts theme={null} import { Checkout } from "@polar-sh/sveltekit"; export const GET = Checkout({ accessToken: process.env.POLAR_ACCESS_TOKEN, successUrl: process.env.SUCCESS_URL, returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Checkout server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise theme: "dark", // Enforces the theme - System-preferred theme will be set if left omitted }); ``` ### Query Params Pass query params to this route. * products `?products=123` * customerId (optional) `?products=123&customerId=xxx` * customerExternalId (optional) `?products=123&customerExternalId=xxx` * customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com` * customerName (optional) `?products=123&customerName=Jane` * metadata (optional) `URL-Encoded JSON string` ## Customer Portal Create a customer portal where your customer can view orders and subscriptions. ```typescript icon="square-js" src/routes/portal/+server.ts theme={null} import { CustomerPortal } from "@polar-sh/sveltekit"; export const GET = CustomerPortal({ server: process.env.POLAR_MODE, // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise accessToken: process.env.POLAR_ACCESS_TOKEN, returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Customer Portal getCustomerId: (event) => "", // Function to resolve a Polar Customer ID }); ``` ## Webhooks A simple utility which resolves incoming webhook payloads by signing the webhook secret properly. ```typescript icon="square-js" src/routes/api/webhooks/polar/+server.ts theme={null} import { Webhooks } from "@polar-sh/sveltekit"; export const POST = Webhooks({ webhookSecret: process.env.POLAR_WEBHOOK_SECRET, onPayload: async (payload) => { // Handle the payload console.log(payload) }, }); ``` ### Payload Handlers The Webhook handler also supports granular handlers for easy integration. * `onPayload` - Catch-all handler for any incoming Webhook event * `onCheckoutCreated` - Triggered when a checkout is created * `onCheckoutUpdated` - Triggered when a checkout is updated * `onOrderCreated` - Triggered when an order is created * `onOrderPaid` - Triggered when an order is paid * `onOrderRefunded` - Triggered when an order is refunded * `onRefundCreated` - Triggered when a refund is created * `onRefundUpdated` - Triggered when a refund is updated * `onSubscriptionCreated` - Triggered when a subscription is created * `onSubscriptionUpdated` - Triggered when a subscription is updated * `onSubscriptionActive` - Triggered when a subscription becomes active * `onSubscriptionCanceled` - Triggered when a subscription is canceled * `onSubscriptionRevoked` - Triggered when a subscription is revoked * `onSubscriptionUncanceled` - Triggered when a subscription cancellation is reversed * `onProductCreated` - Triggered when a product is created * `onProductUpdated` - Triggered when a product is updated * `onOrganizationUpdated` - Triggered when an organization is updated * `onBenefitCreated` - Triggered when a benefit is created * `onBenefitUpdated` - Triggered when a benefit is updated * `onBenefitGrantCreated` - Triggered when a benefit grant is created * `onBenefitGrantUpdated` - Triggered when a benefit grant is updated * `onBenefitGrantRevoked` - Triggered when a benefit grant is revoked * `onCustomerCreated` - Triggered when a customer is created * `onCustomerUpdated` - Triggered when a customer is updated * `onCustomerDeleted` - Triggered when a customer is deleted * `onCustomerStateChanged` - Triggered when a customer state changes # TanStack Start Source: https://polar.sh/docs/integrate/sdk/adapters/tanstack-start Payments and Checkouts made dead simple with TanStack Start ## Examples * [With TanStack Start](https://github.com/polarsource/examples/tree/main/with-tanstack-start) ## Installation Install the required Polar packages using the following command: ```bash Terminal theme={null} npm install zod @polar-sh/tanstack-start ``` ```bash Terminal theme={null} yarn add zod @polar-sh/tanstack-start ``` ```bash Terminal theme={null} pnpm add zod @polar-sh/tanstack-start ``` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript icon="square-js" routes/api/checkout.ts theme={null} import { Checkout } from "@polar-sh/tanstack-start"; import { createFileRoute } from "@tanstack/react-start"; export const Route = createFileRoute("/api/checkout")({ server: { handlers: { GET: Checkout({ accessToken: process.env.POLAR_ACCESS_TOKEN, successUrl: process.env.SUCCESS_URL, returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Checkout server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise theme: "dark", // Enforces the theme - System-preferred theme will be set if left omitted }), }, }, }); ``` ### Query Params Pass query params to this route. * products `?products=123` * customerId (optional) `?products=123&customerId=xxx` * customerExternalId (optional) `?products=123&customerExternalId=xxx` * customerEmail (optional) `?products=123&customerEmail=janedoe@gmail.com` * customerName (optional) `?products=123&customerName=Jane` * metadata (optional) `URL-Encoded JSON string` ## Customer Portal Create a customer portal where your customer can view orders and subscriptions. ```typescript icon="square-js" routes/api/portal.ts theme={null} import { CustomerPortal } from "@polar-sh/tanstack-start"; import { createFileRoute } from "@tanstack/react-start"; import { getSupabaseServerClient } from "~/servers/supabase-server"; export const Route = createFileRoute("/api/portal")({ server: { handlers: { GET: CustomerPortal({ accessToken: POLAR_ACCESS_TOKEN, getCustomerId: async (request: Request) => "", // Function to resolve a Polar Customer ID returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Checkout server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }), }, }, }); ``` ## Webhooks A simple utility which resolves incoming webhook payloads by signing the webhook secret properly. ```typescript icon="square-js" routes/api/webhook/polar.ts theme={null} import { Webhooks } from "@polar-sh/tanstack-start"; import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/api/webhook/polar")({ server: { handlers: { POST: Webhooks({ webhookSecret: process.env.POLAR_WEBHOOK_SECRET!, onPayload: async (payload) => { // Handle the payload // No need to return an acknowledge response }, }), }, }, }); ``` #### Payload Handlers The Webhook handler also supports granular handlers for easy integration. * `onPayload` - Catch-all handler for any incoming Webhook event * `onCheckoutCreated` - Triggered when a checkout is created * `onCheckoutUpdated` - Triggered when a checkout is updated * `onOrderCreated` - Triggered when an order is created * `onOrderPaid` - Triggered when an order is paid * `onOrderRefunded` - Triggered when an order is refunded * `onRefundCreated` - Triggered when a refund is created * `onRefundUpdated` - Triggered when a refund is updated * `onSubscriptionCreated` - Triggered when a subscription is created * `onSubscriptionUpdated` - Triggered when a subscription is updated * `onSubscriptionActive` - Triggered when a subscription becomes active * `onSubscriptionCanceled` - Triggered when a subscription is canceled * `onSubscriptionRevoked` - Triggered when a subscription is revoked * `onSubscriptionUncanceled` - Triggered when a subscription cancellation is reversed * `onProductCreated` - Triggered when a product is created * `onProductUpdated` - Triggered when a product is updated * `onOrganizationUpdated` - Triggered when an organization is updated * `onBenefitCreated` - Triggered when a benefit is created * `onBenefitUpdated` - Triggered when a benefit is updated * `onBenefitGrantCreated` - Triggered when a benefit grant is created * `onBenefitGrantUpdated` - Triggered when a benefit grant is updated * `onBenefitGrantRevoked` - Triggered when a benefit grant is revoked * `onCustomerCreated` - Triggered when a customer is created * `onCustomerUpdated` - Triggered when a customer is updated * `onCustomerDeleted` - Triggered when a customer is deleted * `onCustomerStateChanged` - Triggered when a customer state changes # Go SDK Source: https://polar.sh/docs/integrate/sdk/golang SDK for Golang ## Examples * [With Go (Gin)](https://github.com/polarsource/examples/tree/main/with-golang-gin) ## Installation ```bash theme={null} go get github.com/polarsource/polar-go ``` ## Quickstart ```go icon="golang" main.go theme={null} package main import ( "context" polargo "github.com/polarsource/polar-go" "log" "os" ) func main() { ctx := context.Background() s := polargo.New( polargo.WithSecurity(os.Getenv("POLAR_ACCESS_TOKEN")), ) res, err := s.Organizations.List(ctx, nil, polargo.Pointer[int64](1), polargo.Pointer[int64](10), nil) if err != nil { log.Fatal(err) } if res.ListResourceOrganization != nil { for { // handle items res, err = res.Next() if err != nil { // handle error } if res == nil { break } } } } ``` # PHP SDK Source: https://polar.sh/docs/integrate/sdk/php A fully-featured PHP SDK for the Polar API. ### Quickstart ```bash Terminal theme={null} composer require polar-sh/sdk ``` ```php theme={null} declare(strict_types=1); require 'vendor/autoload.php'; use Polar; $sdk = Polar\Polar::builder() ->setSecurity('') ->build(); $responses = $sdk->organizations->list( page: 1, limit: 10 ); foreach ($responses as $response) { if ($response->statusCode === 200) { // handle response } } ``` [Read more](https://github.com/polarsource/polar-php) ### Sandbox Environment You can configure the SDK so it hits the [sandbox environment](/integrate/sandbox) instead of the production one. You just need to set the server when building the client: ```php theme={null} $sdk = Polar\Polar::builder() ->setServer('sandbox') ->setSecurity('') ->build(); ``` # Python SDK Source: https://polar.sh/docs/integrate/sdk/python Fully type-hinted and allows both synchronous and asynchronous usage, thanks to [HTTPX](https://www.python-httpx.org/). Under the hood, schemas are validated by [Pydantic](https://docs.pydantic.dev/latest/). ### Quickstart ```bash Terminal theme={null} pip install polar-sdk ``` ```python theme={null} # Synchronous Example from polar_sdk import Polar s = Polar( access_token="", ) res = s.users.benefits.list() if res is not None: while True: # handle items res = res.Next() if res is None: break ``` [Read more](https://github.com/polarsource/polar-python) ### Sandbox Environment You can configure the SDK so it hits the [sandbox environment](/integrate/sandbox) instead of the production one. You just need to add the `server` argument when instantiating the client: ```python theme={null} s = Polar( server="sandbox", access_token="", ) ``` # TypeScript SDK Source: https://polar.sh/docs/integrate/sdk/typescript SDK for JavaScript runtimes (Node.js and Browser) ## Examples * [With Node.js](https://github.com/polarsource/examples/tree/main/with-nodejs) ## Installation ```bash Terminal theme={null} npm install @polar-sh/sdk ``` ```bash Terminal theme={null} yarn add @polar-sh/sdk ``` ```bash Terminal theme={null} pnpm add @polar-sh/sdk ``` ## Quickstart ```typescript icon="square-js" index.js theme={null} import { Polar } from '@polar-sh/sdk' const polar = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN, server: process.env.POLAR_MODE || 'sandbox' // sandbox or production }) async function run() { const result = await polar.users.benefits.list({}) for await (const page of result) { // Handle the page console.log(page) } } run() ``` **camelCase vs. snake\_case** Our API (and docs) is designed with `snake_case`. However, our TS SDK currently converts this to camelCase to follow JS/TS convention. You should automatically see the camelCase parameters suggested in modern IDEs due to typing, but it's worth keeping in mind switching between code & docs. We aim to introduce the ability to toggle this in the future, i.e using `snake_case` even in TypeScript to more easily map it to our documentation and design of the API itself. ### Framework Adapters Implement Checkout & Webhook handlers in few lines of code. * [Astro](/integrate/sdk/adapters/astro) * [Better Auth](/integrate/sdk/adapters/better-auth) * [Deno](/integrate/sdk/adapters/deno) * [Elysia](/integrate/sdk/adapters/elysia) * [Express](/integrate/sdk/adapters/express) * [Hono](/integrate/sdk/adapters/hono) * [Fastify](/integrate/sdk/adapters/fastify) * [Next.js](/integrate/sdk/adapters/nextjs) * [Nuxt](/integrate/sdk/adapters/nuxt) * [Remix](/integrate/sdk/adapters/remix) * [Sveltekit](/integrate/sdk/adapters/sveltekit) * [TanStack Start](/integrate/sdk/adapters/tanstack-start) # Handle & monitor webhook deliveries Source: https://polar.sh/docs/integrate/webhooks/delivery How to parse, validate and handle webhooks and monitor their deliveries on Polar Once a webhook endpoint is setup you will have access to the delivery overview page. Here you can: * See historic deliveries * Review payload sent * Trigger redelivery in case of failure Now, let's integrate our endpoint route to validate, parse & handle incoming webhooks. ## Validate & parse webhooks You now need to setup a route handler for the endpoint registered on Polar to receive, validate and parse webhooks before handling them according to your needs. ### Using our SDKs Our TypeScript & Python SDKs come with a built-in helper function to easily validate and parse the webhook event - see full examples below. ```typescript icon="square-js" JS (Express) theme={null} 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 res.status(202).send('') } catch (error) { if (error instanceof WebhookVerificationError) { res.status(403).send('') } throw error } }, ) ``` ```python Python (Flask) theme={null} import os from flask import Flask, request from polar_sdk.webhooks import validate_event, WebhookVerificationError app = Flask(__name__) @app.route('/webhook', methods=['POST']) def webhook(): try: event = validate_event( body=request.data, headers=request.headers, secret=os.getenv('POLAR_WEBHOOK_SECRET', ''), ) # Process the event return "", 202 except WebhookVerificationError as e: return "", 403 ``` Both examples above expect an environment variable named `POLAR_WEBHOOK_SECRET` to be set to the secret you configured during the endpoint setup. ### Custom validation We follow the [Standard Webhooks](https://www.standardwebhooks.com/) standard which offers [many libraries across languages](https://github.com/standard-webhooks/standard-webhooks/tree/main/libraries) to easily validate signatures. Or you can follow their [specification](https://github.com/standard-webhooks/standard-webhooks/blob/main/spec/standard-webhooks.md) in case you want to roll your own. **Note: Secret needs to be base64 encoded** One common gotcha with the specification is that the webhook secret is expected to be base64 encoded. You don't have to do this with our SDK as it takes care of the implementation details with better developer ergonomics. ## IP Allowlist If you are using a firewall or a reverse proxy that requires IP allowlisting, here are the IPs range you need to allow: **New IP ranges** Starting **October 27th, 2025**, new IP ranges will be added: ``` 74.220.50.0/24 74.220.58.0/24 ``` ```txt Production theme={null} 3.134.238.10 3.129.111.220 52.15.118.168 74.220.50.0/24 74.220.58.0/24 ``` ```txt Sandbox theme={null} 3.134.238.10 3.129.111.220 52.15.118.168 74.220.50.0/24 74.220.58.0/24 ``` ## Failure Handling ### Delivery Retries If we hit an error while trying to reach your endpoint, whether it is a temporary network error or a bug, we'll retry to send the event up to **10 times** with an exponential backoff. ### Delivery Timeouts We currently timeout our requests to your endpoint after **10 seconds**, triggering a retry attempt after a delay as explained above. However, we strongly recommend you optimize your endpoint route to respond within **2 seconds** to ensure reliable delivery. We may lower the timeout threshold in the future, so we advise implementing your webhook handler to queue a background worker task to handle the payload asynchronously. ### Endpoint Disabling Webhook endpoints are automatically disabled after **10 consecutive failed deliveries** (non-2xx responses). When this happens: * The endpoint is marked as disabled and will no longer receive new events. * Admin of the organization will receive an email notification. To re-enable a disabled endpoint, go to your organization's webhook settings in the dashboard and manually enable it. Before re-enabling, ensure your endpoint is properly configured and reachable to avoid repeated disabling. ## Troubleshooting ### Not receiving webhooks Seeing deliveries on Polar, but not receiving them on your end? Below are some common techniques to resolve the issue depending on the reported error status. **General** *Start ngrok or similar* Make sure you have started `ngrok` or whatever tunneling service you're using during local development. *Add excessive logging* E.g `console.log('webhook.handler_called')`, `console.log('webhook.validate_signature')`, `console.log('webhook.signature_validated')` etc. So you can easily confirm if the handler is called and how far it gets before any issues arise. `HTTP 404` * Try `curl -vvv -X POST ` in your terminal to confirm the route exists and see any issues along the way * Try adding trailing `/` to the URL on Polar. Often `/foo` is resolved to `/foo/` by frameworks. `HTTP 403` * Using middleware for authorization? Make sure to exclude the webhook route from it since it needs to be publicly accessible * Using Cloudflare? * Check the firewall logs to verify if they are blocking our requests and setup a custom WAF rule to accept incoming requests from Polar. * Webhook delivery failures with 403 errors can occur when Cloudflare's Bot Fight Mode is enabled. Bot Fight Mode automatically blocks requests it identifies as bots, including legitimate webhook requests from Polar. Adding Polar's IP addresses to your IP Allow List or creating custom WAF rules will not resolve this issue. To fix webhook delivery problems, disable Bot Fight Mode in your Cloudflare dashboard under Security > Bots. Alternatively, you can check your Cloudflare firewall logs to confirm if requests are being blocked and create appropriate firewall rules if needed. ### Invalid signature exceptions Rolling your own webhook validation logic? Make sure to base64 encode the secret you configured on Polar in your code before generating the signature to validate against. # Setup Webhooks Source: https://polar.sh/docs/integrate/webhooks/endpoints Get notifications asynchronously when events occur instead of having to poll for updates Our webhook implementation follows the [Standard Webhooks](https://www.standardwebhooks.com/) specification and our SDKs offer: * Built-in webhook signature validation for security * Fully typed webhook payloads In addition, our webhooks offer built-in support for **Slack** & **Discord** formatting. Making it a breeze to setup in-chat notifications for your team. ## Get Started **Use our sandbox environment during development** So you can easily test purchases, subscriptions, cancellations and refunds to automatically trigger webhook events without spending a dime. Head over to your organization settings and click on the `Add Endpoint` button to create a new webhook. Enter the URL to which the webhook events should be sent. **Developing locally?** Use a tool like [ngrok](https://ngrok.com/) to tunnel webhook events to your local development environment. This will allow you to test your webhook handlers without deploying them to a live server. Once you have `ngrok` you can easily start a tunnel: ```bash Terminal theme={null} ngrok http 3000 ``` Just be sure to provide the URL ngrok gives you as the webhook endpoint on Polar. For standard, custom integrations, leave this parameter on **Raw**. This will send a payload in JSON format. If you wish to send notifications to a Discord or Slack channel, you can select the corresponding format here. Polar will then adapt the payload so properly formatted messages are sent to your channel. If you paste a Discord or Slack Webhook URL, the format will be automatically selected. We cryptographically sign the requests using this secret. So you can easily verify them using our SDKs to ensure they are legitimate webhook payloads from Polar. You can set your own or generate a random one. Finally, select all the events you want to be notified about and you're done ๐ŸŽ‰ [Now, it's time to integrate our endpoint to receive events โ†’](/integrate/webhooks/delivery) # Webhook Events Source: https://polar.sh/docs/integrate/webhooks/events Our webhook events and in which context they are useful ## Billing Events ### Checkout ### Customers Fired when a new customer has been created. Fired when a customer has been updated. Fired when a customer has been deleted. Fired when a customer's state has changed. Includes active subscriptions and granted benefits. ### Subscriptions In order to properly implement logic for handling subscriptions, you should look into the following events. Fired when a new subscription has been created. Fired when a subscription payment has failed. The customer can recover by updating their payment method. Use this event if you want to handle cancellations, un-cancellations, etc. The updated event is a catch-all event for `subscription.active`, `subscription.canceled`, `subscription.uncanceled`, `subscription.past_due` and `subscription.revoked`. In case you want to do logic when a subscription is renewed, you should listen to `order.created` and the `billing_reason` field. It can be `purchase`, `subscription_create`, `subscription_cycle` and `subscription_update`. `subscription_cycle` is used when subscriptions renew. #### Cancellation Sequences When a subscription is canceled, the events triggered depend on whether the cancellation is immediate or scheduled for the end of the billing period. **End-of-Period Cancellation (default)** When a subscription is **canceled** (by customer action from the portal or by the merchant from the dashboard/API), the following events are sent immediately: 1. `subscription.updated` 2. `subscription.canceled` Both events contain the same subscription data. The subscription will still have `active` status, but the `cancel_at_period_end` flag will be set to `true`. When the end of the current billing period arrives, the subscription is definitively revoked: billing cycles stop and benefits are revoked. The following events are then sent: 3. `subscription.updated` 4. `subscription.revoked` Both events contain the same subscription data. The subscription will have the `canceled` status. **Immediate Revocation** When a merchant cancels a subscription with **immediate revocation**, those events are sent at once: 1. `subscription.updated` 2. `subscription.canceled` 3. `subscription.revoked` All three events contain the same subscription data. The subscription will have the `canceled` status immediately. #### Renewal Sequences When a subscription is renewed for a new cycle, the webhook events are triggered in a specific sequence to help you track the renewal process and handle billing logic appropriately. **Initial Renewal Events** When a subscription reaches its renewal date, the following events are sent immediately (if enabled on the webhook): 1. `subscription.updated` 2. `order.created` The subscription data will reflect the new billing period through the `current_period_start` and `current_period_end` properties, showing the updated cycle dates. The order data represents the new invoice for the upcoming cycle, with a total representing what the customer will pay for this new period. If usage-based billing is involved, their consumption for the past period will be included in the total. The status of this order is `pending` at this stage. **Payment Processing Events** Shortly after the initial renewal events, the platform will trigger a payment for the new order. Once the payment is successfully processed, the following events are sent: 3. `order.updated` 4. `order.paid` Both events will contain the same order data, with the order status changed to `paid`. ### Orders ### Refunds ### Benefit Grants ## Organization Events ### Benefits ### Products ### Organization # Polar: Turn Your Software into a Business Source: https://polar.sh/docs/introduction The next generation unicorns will be built by smaller teams. Polar makes that dream possible. ## What is Polar? Turn your software into a business with Polar. Sell digital products, subscriptions, and more without the hassle of traditional payment systems. Unlike Stripe that only handles transactions, we provide complete billing infrastructure with tax compliance, product management, and automated access & delivery. We handle all international tax compliance, so you can sell globally without worrying about VAT, GST, or sales tax regulations. ## Problems We Solve **The Problem**: Selling digital products globally means dealing with VAT, GST, and sales tax in dozens of jurisdictions, each with different rates, rules, and filing requirements. Most developers either ignore this (risky) or avoid international sales entirely. **Polar's Solution**: As your Merchant of Record, we handle all international tax compliance. We calculate, collect, and remit taxes worldwide. You focus on building; we handle the paperwork. **The Problem**: Building subscription billing, product catalogs, customer portals, and payment flows from scratch takes months of development time and ongoing maintenance. **Polar's Solution**: Complete billing infrastructure out-of-the-box with APIs that let you integrate in minutes. No need to build customer portals, handle subscription lifecycle, or manage failed payments. **The Problem**: Manually sending license keys, granting repository access, or managing Discord invites for every purchase doesn't scale and creates delays for customers. **Polar's Solution**: Automated benefit delivery for common developer needs - license keys, file downloads, GitHub repo access, Discord roles, and more. Customers get instant access. **The Problem**: Traditional MoR solutions charge 5-8% per transaction plus monthly fees, eating into your profits before you even start. **Polar's Solution**: 20% lower fees at just 4% + 40ยข per transaction with no monthly minimums. We earn when you earn. ## Core Features ### Flexible Product Management Sell digital products, courses, templates, or software licenses with instant delivery Recurring billing with automatic renewals and dunning management Fixed price, pay-what-you-want, or free products with optional minimums ### Powerful Checkout Experience No-code solution for quick product sales. Create and share instantly. Integrate seamlessly into your website with customizable branding. Programmatically create dynamic checkout sessions for custom flows. ### Automated Benefits (Entitlements) **Set it and forget it**: Configure once, and customers get instant access to their benefits automatically. No manual work required. Generate and deliver software licenses automatically with custom formats Secure delivery of digital assets up to 10GB with download tracking Auto-invite customers to private repositories and manage permissions Automatic role assignment and server invites for community access ### Global Merchant of Record * **Worldwide Tax Compliance** - We handle VAT, GST, and sales tax in all jurisdictions * **EU VAT Handling** - Proper B2B reverse charge and B2C tax collection * **Automatic Tax Calculation** - Real-time tax rates for every transaction ## Quick Start Guide [Sign up for Polar](https://polar.sh/signup) using GitHub, Google, or email. Create an organization to manage your products and customers. Set up a digital product in minutes: * Choose between one-time purchase or subscription * Set your pricing (fixed, pay-what-you-want, or free) * Configure automated benefits for instant delivery Learn more about [Products โ†’](/features/products) Pick the approach that fits your needs: Perfect for getting started quickly: * Create [Checkout Links](/features/checkout/links) from your dashboard * Share via email, social media, or embed in websites * Start accepting payments immediately Integrate into your existing website: * Add our [Embedded Checkout](/features/checkout/embed) component * Maintain your site's look and feel * Customers never leave your domain Maximum flexibility for custom workflows: * Use our [SDKs](/integrate/sdk/typescript) for any language * Build custom checkout flows and experiences * Integrate with your existing tech stack Stay synchronized with customer events: * Configure webhook endpoints in your dashboard * React to purchases, subscription changes, and customer events * Keep your database in sync automatically Read the [Webhooks guide โ†’](/integrate/webhooks/endpoints) ## Integration Options ### Framework Adapters (Recommended) React-based full-stack framework with App Router support Payments and billing empowered by authentication & authorization PHP web application framework with Eloquent ORM integration A modern runtime for TypeScript Vue.js framework React framework Fast Node.js Cloudflare Workers Modern runtime Full-stack React Bun framework Static site generator Flexible Node.js framework Full-stack framework ### Native SDKs For web and Node.js applications For Django, Flask, FastAPI frameworks For Go web services and applications For WordPress, Laravel, and PHP apps ## Why Choose Polar? Focus on your product, not billing infrastructure. Get to market weeks faster. Sell worldwide without worrying about tax compliance or regional restrictions. License keys and downloads handled automatically. No manual work required. 20% cheaper than competitors with transparent, pay-as-you-earn pricing. Complete billing solution without months of custom development work. Pay only when you earn, no monthly minimums or setup fees. Multiple team members can manage products, customers, and analytics. Branded experience that builds customer trust and increases conversions. Advanced analytics, custom fields, bulk operations, and priority support. Full programmatic control over products, customers, orders, and subscriptions. Reliable real-time synchronization with your systems and database. ## Transparent Pricing **Per successful transaction** Simple, transparent pricing with no surprises **Monthly fees or setup costs** Pay only when you earn, no fixed costs **Additional fees may apply**: Some transactions may incur additional fees (international cards, subscriptions). Payout fees are charged by payment providers. See our [detailed fees page](/merchant-of-record/fees) for complete information. ## Open Source & Community Polar is built in the open with full transparency and a growing community of contributors. Apache 2.0 license with 36+ contributors and growing Feature requests, roadmap, and issues - all developed in public No hidden fees or surprise charges. What you see is what you pay. Join our Discord for help, feedback, and feature discussions While self-hosting is technically possible, we recommend using our hosted service to get the full Merchant of Record benefits including global tax compliance. ## Ready to Start? **Free signup, no credit card required** Get started in under 2 minutes **Framework-specific tutorials** Step-by-step integration guides **Complete API documentation** Interactive examples and SDKs **Get help from our team** Active community and support # null Source: https://polar.sh/docs/merchant-of-record/acceptable-use As your Merchant of Record (MoR), we are the reseller of all digital goods and services and focus exclusively on digital products. Therefore we cannot support physical goods or entirely human services, e.g consultation or support. In addition to not accepting the sale of anything illegal, harmful, abusive, deceptive or sketchy. ## Acceptable Products & Businesses * Software & SaaS * Digital products: Templates, eBooks, PDFs, code, icons, fonts, design assets, photos, videos, audio etc * Premium content & access: Discord server, GitHub repositories, courses and content requiring a subscription. **General rule of acceptable services** Digital goods, software or services that can be fulfilled byโ€ฆ 1. Polar on your behalf (License Keys, File Downloads, GitHub- or Discord invites or private links, e.g premium YouTube videos etc) 2. Your site/service using our APIs to grant immediate access to digital assets or services for customers with a one-time purchase or subscriptions Combined with being something youโ€™d proudly boast about in public, i.e nothing illegal, unfair, deceptive, abusive, harmful or shady. Donโ€™t hesitate to [reach out to us](/support) in advance in case youโ€™re unsure if your use case would be approved. ## Prohibited Businesses **Not an exhaustive list** We reserve the right to add to it at any time. Combined with placing your account under further review or suspend it in case we consider the usage deceptive, fraudulent, high-risk or of low quality for consumers with high refund/chargeback risks. * Illegal or age restricted, e.g drugs, alcohol, tobacco or vaping products * Violates laws in the jurisdictions where your business is located or to which your business is targeted * Violates any rules or regulations from payment processors & credit card networks, e.g [Stripe](https://stripe.com/en-se/legal/restricted-businesses) * Reselling or distributing customer data to other parties for commercial, promotional or any other reason (disclosed service providers are accepted). * Threatens reputation of Polar or any of our partners and payment providers * Causes or has a significant risk of refunds, chargebacks, fines, damages, or harm and liability * Services used by-, intended for or advertised towards minors * Physical goods of any kind. Including SaaS services offering or requiring fulfilment via physical delivery or human services. * Human services, e.g marketing, design, web development and consulting in general. * Donations or charity, i.e price is greater than product value or there is no exchange at all (pure money transfer). Open source maintainers with sponsorship can be supported - reach out. * Marketplaces. Selling othersโ€™ products or services using Polar against an upfront payment or with an agreed upon revenue share. * Adult services or content. Including by AI or proxy, e.g * AI Girlfriend/Boyfriend services. * OnlyFans related services. * Explicit/NSFW content generated with AI * Low-quality products, services or sites, e.g * E-books generated with AI or 4 pages sold for \$50 * Quickly & poorly executed websites, products or services * Services with a lot of bugs and issues * Products, services or websites we determine to have a low trust score * Fake testimonials, reviews, social proof, and review inflation platforms. It's deceptive to consumers which is behaviour we do not tolerate. * Trademark violations * "Get rich" schemes or content * Gambling & betting services (including lootboxes, mystery boxes and pack openings of random nature) * Regulated services or products * Counterfeit goods * Job boards * NFT & Crypto assets. * Cheating: Utilizing macros, cheat codes, hacks, or any unauthorized modifications that alter gameplay or provide an unfair advantage. * Reselling Licenses: Selling, distributing, or otherwise transferring software licenses at reduced prices or without proper authorization. * Services to circumvent rules or terms of other services: Attempting to bypass, manipulate, or undermine any established rules, gameplay mechanics, or pricing structures of other vendors/games. * Financial services, e.g facilitating transactions, investments or balances for customers. * Financial trading, trading bots, brokerage, or investment advisory services (including insights platforms). * Financial advice, e.g content or services related to tax guidance, wealth management, trading signals, investment strategies etc. * IPTV services * Virus & Spyware * Telecommunication & eSIM Services * Products you donโ€™t own the IP of or have the required licenses to resell * Advertising & unsolicited marketing services. Including services to: * Generate, scrape or sell leads * Send SMS/WhatsApp messages in bulk * Automate outreach (spam risks) * Automate mass content generation & submission across sites * API & IP cloaking services, e.g services to circumvent IP bans, API rate limits etc. * Products or services associated with pseudo-science; clairvoyance, horoscopes, fortune-telling etc. * Travel services, reservation services, travel clubs and timeshares * Medical advice services or products, e.g. pharmaceutical, weight loss, muscle building * Watermark removal services ## Restricted Businesses Requires closer review and a higher bar of quality, execution, trust and compliance standards to be accepted. * Directories & boards * Marketing services * Pre-orders & Paid waitlist * Ticket sales * eBooks ## FAQ **Why do directories & boards require closer review?** They often sell premium placement, i.e ads, without meeting compliance requirements for advertising. Or even where it's their sole purpose to sell placement. **Why do marketing services require closer review?** Too many services offer sketchy marketing tactics and mass outreach (unsolicited) features. There is no short-cut to sales beyond offering a great product & service. We love marketing services that reflect that and focus on the long game vs. shortcuts and hacks. **Can I sell pre-orders or use paid waitlists for my service to validate demand before build?** Generally, no. It's a high risk category for us as the Merchant of Record. Sellers could withdraw funds and never deliver the service or not as promised. Causing consumers to demand refunds or dispute the sale against us at a later date. For high-trust cases from developers with a track record, we're able to make exceptions, but simultaneously need to adapt our payout process to withhold all funds until verified fulfilment. **Why are marketplaces or human services (consultancy) not allowed?** We hope to change this status quo amongst Merchants of Record long-term, but both come with additional compliance and risk challenges. Since fulfilment is not digital, immediate or between known parties to us, we cannot fulfil our compliance & risk requirements or effectively mitigate potential disputes. **Why are OnlyFans services not allowed?** Close & blurred lines between the service and the content & service provided on OnlyFans, i.e often adult content. In addition to us having seen fraudulent & deceptive behavior in the category. We're simply not comfortable acting as the Merchant of Record here. # null Source: https://polar.sh/docs/merchant-of-record/account-reviews As a Merchant of Record (MoR), we act as the reseller of digital goods and services. Therefore, we need to make sure that businesses using Polar complies with our [acceptable products & use](/merchant-of-record/acceptable-use) policies. Combined with continuously monitoring, reviewing and preventing fraud, unacceptable use, bad actors and high risk accounts. Account reviews are typically completed within a week on average. However, sometimes it can take longer due to weekends, holidays etc. We process them as quickly as possible and resolve every single one. ### First payout review You will need to go through our main review ahead of the initial payout. Weโ€™ll reach out over email for: 1. A quick survey about your business, products and intended use case with Polar. 2. Identity verification (KYC) using Passport/ID Card/Driver License and a selfie. Itโ€™s secure, easy and quick to submit using Stripe Identity. We need to perform this review to ensure compliance with our [acceptable products & use](/merchant-of-record/acceptable-use) policy. Combined with meeting our own KYC/AML requirements as a billing platform. **Submit upfront (Soon)** Weโ€™ll soon offer the ability to submit all of this information in advance to speed up the initial payout even further and without concern of any issues or delays. ### Continuous reviews (Async) We continuously monitor all transactions across our platform to proactively prevent fraud. In addition to performing asynchronous reviews of accounts at certain sale thresholds. These reviews are often completed within a day or two and without any additional information required from you. Youโ€™ll get notified over email that a review is taking place. Payouts will be paused during this time, but it has no impact on your customersโ€™ experience or ability to purchase, subscribe or checkout at any time. We look at: * Risk scores across historic transactions * Refunds & Chargebacks ratio * Appropriate next sales threshold for a review given the above **High chargeback ratios** Credit card networks, e.g Visa/Mastercard, consider 0.7% of sales in chargebacks excessive. Exceeding it can lead to monitoring programs with high costs, penalties and ultimately termination. We therefore reach out proactively to collaborate on maintaining a low chargeback ratio and reducing it ahead of getting close to these thresholds. ## Operational Guidelines To maintain platform integrity and ensure smooth operations, we have established clear guidelines for merchants using Polar. #### Expected Responsiveness We expect merchants to maintain high standards of customer support and responsiveness to their customers: * **Support Ticket Management**: Maintain a low rate of support tickets from customers * **Merchant Communication**: When we include you in customer support communications, we require a response within 48 hours * If no response is received within 48 hours, we may issue refunds to affected customers and a warning to merchants * In case of repeated issues, we'll have to offboard unresponsive merchants * **Customer Service Quality**: We evaluate your customer service history and approach when determining appropriate actions #### Test Transactions To maintain platform security and prevent abuse: * **Use Sandbox Environment or 100% Discounts**: All testing must be conducted using our sandbox environment. Want to test in production? Use a Free Product or a 100% Discount code to avoid using real money. * **No Real Money Testing**: Any such transactions are prohibited and will be refunded. It's against the terms of payment service providers, triggers our account reviews and can potentially lead to the card or account getting blocked since it can be flagged as "card testing". #### Chargeback Management We monitor chargeback rates closely to protect both merchants and customers: * **Acceptable Threshold**: We maintain a chargeback rate threshold of 0.4% for merchants * **Timeframe**: Chargebacks can be filed up to 120 days from the original transaction date * **Consequences of High Chargeback Rates**: We work proactively to maintain a low chargeback rate throughout the platform and strive to collaborate with merchants at risk of exceeding our thresholds to reverse the trend. However, we reserve the right to (in order of severity): * Refund transactions as needed * Pause payouts pending review โ€“ up until the timeframe for chargebacks have been surpassed * Pause future payments * Block accounts and refund customers We don't take this responsibility or actions lightly, and always strive to mitigate and avoid them, but have to take appropriate and proactive actions in case of issues. We also have integrations with credit card networks to receive early chargeback signals before they're officially filed. We automatically refund such transactions under a certain value and cancel any subscriptions associated with the customer to reduce chargebacks proactively. #### Policy Violations For merchants who violate our [acceptable use policies](/merchant-of-record/acceptable-use) (and don't have high chargeback rates): * **Immediate Action**: We will offboard merchants who violate our policies * **Payment Processing**: All payment processing will be blocked * **Payout Management**: Payouts will be paused pending review * **Resolution Process**: * We may conduct test transactions to verify account status * In case of strong suspicion of fraud or intentional abuse, we block the account immediately * We reach out merchants about the issue and give them 48 hours to respond * Failure to respond may result in refunds to affected customers * We pause future payments in the meantime if deemed necessary * We pause payouts during the resolution process * We strive to collaborate with merchants on the best possible path forward. Clear fraud or abuse, however, is immediately blocked. * We have to cancel subscriptions and refund payments made in violation of our acceptable use policies for compliance and risk, and strive to do so in collaboration with the merchant. ## Frequently Asked Questions ### Why is my account under review again? Your account may go through multiple reviews as your business grows. We perform [continuous reviews](#continuous-reviews-async) at certain sales thresholds to maintain platform integrity and prevent fraud. This is a standard practice across payment platforms and is part of our ongoing risk management process. ### Why do you need my social media in settings? We request social media information as part of our identity verification and fraud prevention processes. This helps us: * Verify that you're a real business or creator with an online presence * Understand your products and services better * Ensure compliance with our [acceptable use policies](/merchant-of-record/acceptable-use) Providing accurate social media information helps speed up the review process and demonstrates the legitimacy of your business. ### Are social media settings visible publicly? No, your social media settings are not publicly visible. This information is used internally for verification and compliance purposes only. We treat all merchant information with strict confidentiality and use it solely for risk assessment and account review processes. ### Why do I need to share a video recording showing the product working? To help us verify that everything is working correctly in line with our acceptable use policy, our team would ask you to share a 100% discount code by email. This is our preferred method, as it allows the team to go through the full journey themselves and confirm the automated fulfillment from an unpaid user to a paid user. Alternatively, you can provide a video recording that clearly shows the complete flow from an unpaid user to a paid user, including how the product is automatically accessible after purchase. ### How does changing the admin of an organization work? To change the admin of an organization: 1. Invite the new admin to the team via `Settings` > `Members` in the Polar dashboard 2. Ask that new admin to complete identity verification under `Finance` > `Account` after logging in via that email in the Polar dashboard 3. Make sure no payout is pending 4. Send an email from the current admin email to our support confirming the transfer to the new admin If you need assistance with changing organization ownership or have special circumstances, please contact [support@polar.sh](mailto:support@polar.sh). # Fees Source: https://polar.sh/docs/merchant-of-record/fees Transparent fees at a 20% discount vs. other MoRs ## Transaction Fees All transactions on Polar come with a small fee of 4% + 40ยข - applied to the entire transaction amount. Polar is currently built on Stripe, and we cover their 2.9% + 30ยข fee from ours. However, they impose a few additional fees for certain transactions that we need to pass on. ### **Additional Fees** * +1.5% for international cards (non-US) * +0.5% for subscription payments * *We also reserve the right to pass on any other fees Stripe might impose in the future* **Example** Let's look at an example breakdown with all these additional fees applied. Below is a payment of a \$30 subscription from Sweden (25% VAT). | Item | Amount | | ------------------------------ | ---------- | | Product Price | \$30 | | VAT (25%) | \$7.5 | | **Total Transaction Value** | **\$37.5** | | Transaction Fee (4% + 40ยข) | \$1.9 | | International Card (+1.5%) | \$0.56 | | Subscription (+0.5%) | \$0.19 | | **Total Fees (Before Payout)** | **\$2.65** | ### Refunds You can issue both full or partial refunds on Polar to your customers. However, the initial transaction fees are not refunded to you since credit card networks and PSPs charge them regardless of a future refund. Please note: Polar reserves the right to issue refunds at our own discretion up to 60 days after the purchase as part of our efforts to continuously and proactively reduce disputes & chargebacks which costs you \$15/dispute. We only leverage this right for this purpose and in the interest of reducing chargebacks and fees for you. ### Dispute/Chargeback Fees Sometimes, customers can open a **dispute/chargeback** via their bank for a purchase. **Disputes cost \$15 per dispute** regardless of outcome and is deducted from your balance directly. This fee is charged by the underlying credit card networks & PSPs regardless of outcome and therefore something we cannot refund. However, we continuously work to proactively reduce the rate of chargebacks across Polar to be at or lower than industry standards. Credit card networks impose monitoring programs, penalties and higher chargeback costs for sellers with high chargeback rates (\~0.7%+). Since Polar is the Merchant of Record, we therefore always monitor and proactively prevent our rate coming close to these thresholds. Therefore, we might need to intervene and even suspend your account unless swift and proactive measures are taken to reduce chargebacks to an acceptable industry standard. ## Payout Fees While payouts may incur fees charged by our payout provider (Stripe), Polar does not add any extra fees or markup. These are strictly Stripeโ€™s fees, and Polar does not profit from them. In addition, Polar offers manual withdrawals for developers. Keeping you in control of when to issue payouts. *Unless you have a Polar balance that you haven't withdrawn for several months, at which point we'll eventually need to trigger a payout on your behalf.* **Fees (Stripe)** * \$2 per month of active payout(s) * 0.25% + \$0.25 per payout * Cross border fees (currency conversion): 0.25% (EU) - 1% in other countries. ## Volume pricing Large or fast-growing business? We can offer custom pricing to better fit your needs. [Reach out to us](/support). # Merchant of Record Source: https://polar.sh/docs/merchant-of-record/introduction An open source and transparent Merchant of Record ### What is a Merchant of Record? We take on the liability of international sales taxes globally for you. So you can focus on growing your business vs. accounting bills. Leave billing infrastructure and international sales tax headaches to us. ### Payment Service Providers vs. Merchants of Record **Payment Service Providers (PSPs)** Stripe and other Payment Service Providers (PSPs) offer an accessible and convenient abstraction to faciliate transactions on top of underlying credit card networks & banks. * โœ… Powerful, flexibile & low-level APIs to facilitate transactions * โœ… Can be used to power all business- and pricing models under the sun. * โŒ You are responsible for all liabilities associated with transactions, e.g international taxes * โŒ Low-level APIs require more development even for common use cases **Merchants of Record (MoRs)** Merchants of Record offer yet another layer of convenient abstraction to facilitate digital orders on top of the underlying PSPs and transactions. E.g Polar is built on Stripe (+ more PSPs in the future). * โœ… Higher-level Dashboard, APIs & SDKs to better facilitate digital products, services & orders beyond the underlying transactions * โœ… The platform (Polar) handles international taxes by being a reseller of your digital goods & services. Of course, without being in the way of your relationship with your customers. * โŒ Less flexibility & control in terms of advanced business- and pricing models. * โŒ Higher fees per payment **What should you choose?** **Ship with what you feel comfortable with vs. others tell you to** Just like in programming, abstractions are super helpful to ship faster with fewer low-level concerns, but in exchange for reduced flexibility and higher costs. So what's the right level of abstraction for you? As always, it depends (tm). **Go with Stripe (PSP) if...** * You've already integrated it? Just ship already - we salute builders however they ship * You're comfortable with the Stripe API and prefer absolute control with low-level APIs. * You're looking for the lowest fees possible. * You're fine with handling international taxes yourself (you absolutely can). **Go with Polar (MoR) if...** * You want product-, customer-, order- and subscription management via an intuitive and easy dashboard * You want to offer file downloads, license keys, Discord- and/or private GitHub repository invites with ease - with more built-in automations to come. * You prefer a more high-level API optimized for making monetization easier. We're only getting started here and have some big things coming * You want us to handle international taxes for you ### Polar MoR **We take on the liability of international sales taxes globally for you. So you can focus on building your passion. Leaving billing infrastructure and sales tax headaches to us.** So how does Polar offer a Merchant of Record (MoR) service and handle international sale taxes? All other Merchants of Record simply state they handle it internationally - don't worry about it. We do too. But we believe in transparency and don't want to scare customers into thinking it's impossible to manage it themselves. So below we'll share how exactly we go about doing this. #### International Sales Taxes Most countries, states and jurisdictions globally impose sales taxes on digital goods and services (VAT, GST, US Sales Tax etc). Regardless of whether the merchant (seller) is a resident there or not - they're doing business there. For example, a \$10/month subscription should cost \$12.5/month for a Swedish (25% VAT) consumer, but \$10/month for a Swedish business with VAT registration (reverse charge). Merchants are responsible for 1) capturing & 2) remitting sales taxes to the local tax authorities. What does that mean in our example? 1. **Capturing**. Charging the Swedish consumer \$12.5/month and saving \$2.5/month for the Swedish tax authorities. Stripe Tax is an excellent service to automate this and the one Polar uses today. 2. **Remitting**. Filing & paying the captured sales taxes with the tax authorities on time. Stripe Tax does not do this, i.e the merchant is liable to register, file and pay taxes to local tax authorities. Many jurisdictions, however, don't require this until you reach a certain threshold in terms of sales volume. But others require registration even before the first sale - or after a very low threshold. In addition to having different rates and rules on which goods are taxable and whether they're deductable or not for business customers. For example, United Kingdom and EU countries require upfront registration for international companies, but Texas (United States) does not until you've sold for more than \$500,000 ๐Ÿ‡บ๐Ÿ‡ธ๐Ÿฆ… In short: It's complex and hard. Even large and well-known businesses don't do it perfectly. Arguably, it's almost impossible and at least highly impracticle and expensive to comply perfectly upfront. Many companies even delay compliance as a calculated risk, i.e focus on validating & growing their business with the risk of paying back taxes + penalities later. **PSP (Stripe)** * โœ… Your volume alone is what counts towards international thresholds vs. the MoR platform, i.e customers might not need to pay sales taxes with you, but would via a MoR. * โœ… You can deduct inbound VAT against purchases your business does with VAT * โŒ You're liable for capturing & remitting international sales taxes * โŒ Stripe Tax is great to monitor & automate capturing, but registration and remittance is up to you. **MoR (Polar)** * โœ… We are liable for all of the above as your reseller, i.e we have to worry about it vs. you. * โœ… Offer EU VAT for B2B sales (expected and desired within EU for businesses) without having to register, capture and remit it yourself. * โŒ Sales taxes would be added for more customers vs. with you selling directly * โŒ You cannot leverage inbound VAT towards VAT expense deductions yourself Merchants of Record (MoR) handles sales taxes, e.g US Sales Tax, EU VAT, Canadian GST etc. **However, you're always responsible for your own income/revenue tax** in your country of residency. #### Polar Coverage **We support global payments and are liable for all international sales taxes. We continuously monitor and work with our accounting firms to expand registrations as needed on our end.** **Global Payments & Tax Liabilities** As your Merchant of Record, Polar is liable for tax compliance globally on all sales internationally via our platform, hosted- or embedded checkoutsfrom payments anywhere in the world. **Current Polar Tax Registrations** 1. Polar Software Inc. is incorporated as a US Delaware C Corp and will register for US State Sales Taxes upon reaching thresholds 2. EU VAT (Irish OSS VAT) 3. UK VAT No Merchant of Record (MoR) or business registers upfront in all global jurisdictions. Since it would be 1) unnecessary in case of thresholds & 2) incredibly expensive with uncertain return on investment (ROI) in all markets. We work with global accounting firms specialized in registering, filing and remitting taxes in all countries. So we can easily scale registrations and remittance as needed. Below is our process and evaluation for expanding registrations. **Expanding Registrations** Below are the fees the global acounting firms we work with charge us - on average per market: * \~\$500 upfront for registration * \~\$300 per filing and remittance (\~quarterly) * *Excluding consultations (billed hourly) and our internal efforts and automations to transform Stripe Tax reports into correct output for the accounting firms.* So on average \$1,700 in year one and \$1,200 therafter for each market at a minimum. Businesses (and you if you handle this yourself) therefore need to ask themselves: Do I anticipate more in sales from a given market vs. costs of operating there? Let's imagine a country with 20% sales tax. 1. At \$6,000+ the tax liability start outgrowing the accounting costs for you standalone (\$1,200/20%) 2. Polar with a 1.1% premium vs. Stripe would need to help facilitate \$109,090 in sales for the given market in order for it to cover our accounting costs (\$1,200/1.1%) Our customers are selling mostly in the US, UK & EU. Given US thresholds and our current registrations, it's therefore a non-issue. In markets we're not registered, we still have the liability and take it on (#1) to assess the potential for our customers and us long-term. In addition to being comfortable betting on markets a lot earlier than it becomes profitable for us (#2). However, in case of neither we reserve the right to block payments from such countries in the short-term until the opportunity for our customers and us changes in the given market. **Want to do this yourself?** Selling a lot and want to handle this yourself, i.e worth the ongoing costs? Feel free to reach out and we'd be happy to introduce you to our contacts at the accounting firms we use. We consider MoR a key value-add to Polar, but not the sole reason for Polar to exist. Our ambition is to be the easiest way to monetize for developers. However, we're never going to be the right solution for all use cases. But we'll always salute and help anyone who ships software - regardless of billing platform. ## Frequently Asked Questions Polar's VAT number is `EU372061545`. This is an EU One Stop Shop (OSS) registration, which allows non-EU businesses like Polar (a Delaware C Corporation) to handle VAT for all EU countries through a single registration in Ireland. OSS VAT numbers use the `EU` prefix instead of country-specific prefixes (like `IE` for Ireland). Some accounting software predates the OSS program or lacks support for this format. You can manually enter the number if your software allows overriding validation, or contact your software vendor to request OSS number support. # null Source: https://polar.sh/docs/merchant-of-record/supported-countries ### Payments & Merchant of Record We support payments globally except from countries with US sanctions (Cuba, Russia, Iran, North Korea, and Syria). As your Merchant of Record (MoR) we take on the [liability for international sales taxes](/merchant-of-record/introduction). ### Payouts Polar uses Stripe Connect Express to issue payouts to residents or businesses in any of the countries below. **FAQ: Stripe is not supported in my country** Stripe Connect Express for payouts is a separate product from Stripe Payments. In some cases, Stripe Payments might not be available for merchants in your country, but Stripe Connect Express is for payouts using cross-border transfers. Since Polar is the Merchant of Record and uses Stripe Connect Express for payouts, we're able to support sellers in all of the countries below. * ๐Ÿ‡ฆ๐Ÿ‡ฑ Albania * ๐Ÿ‡ฉ๐Ÿ‡ฟ Algeria * ๐Ÿ‡ฆ๐Ÿ‡ด Angola * ๐Ÿ‡ฆ๐Ÿ‡ฌ Antigua and Barbuda * ๐Ÿ‡ฆ๐Ÿ‡ท Argentina * ๐Ÿ‡ฆ๐Ÿ‡ฒ Armenia * ๐Ÿ‡ฆ๐Ÿ‡บ Australia * ๐Ÿ‡ฆ๐Ÿ‡น Austria * ๐Ÿ‡ฆ๐Ÿ‡ฟ Azerbaijan * ๐Ÿ‡ง๐Ÿ‡ธ Bahamas * ๐Ÿ‡ง๐Ÿ‡ญ Bahrain * ๐Ÿ‡ง๐Ÿ‡ฉ Bangladesh * ๐Ÿ‡ง๐Ÿ‡ช Belgium * ๐Ÿ‡ง๐Ÿ‡ฏ Benin * ๐Ÿ‡ง๐Ÿ‡น Bhutan * ๐Ÿ‡ง๐Ÿ‡ด Bolivia * ๐Ÿ‡ง๐Ÿ‡ฆ Bosnia and Herzegovina * ๐Ÿ‡ง๐Ÿ‡ผ Botswana * ๐Ÿ‡ง๐Ÿ‡ณ Brunei * ๐Ÿ‡ง๐Ÿ‡ฌ Bulgaria * ๐Ÿ‡ฐ๐Ÿ‡ญ Cambodia * ๐Ÿ‡จ๐Ÿ‡ฆ Canada * ๐Ÿ‡จ๐Ÿ‡ฑ Chile * ๐Ÿ‡จ๐Ÿ‡ด Colombia * ๐Ÿ‡จ๐Ÿ‡ท Costa Rica * ๐Ÿ‡ญ๐Ÿ‡ท Croatia * ๐Ÿ‡จ๐Ÿ‡พ Cyprus * ๐Ÿ‡จ๐Ÿ‡ฟ Czech Republic * ๐Ÿ‡ฉ๐Ÿ‡ฐ Denmark * ๐Ÿ‡ฉ๐Ÿ‡ด Dominican Republic * ๐Ÿ‡ช๐Ÿ‡จ Ecuador * ๐Ÿ‡ช๐Ÿ‡ฌ Egypt * ๐Ÿ‡ธ๐Ÿ‡ป El Salvador * ๐Ÿ‡ช๐Ÿ‡ช Estonia * ๐Ÿ‡ช๐Ÿ‡น Ethiopia * ๐Ÿ‡ซ๐Ÿ‡ฎ Finland * ๐Ÿ‡ซ๐Ÿ‡ท France * ๐Ÿ‡ฌ๐Ÿ‡ฆ Gabon * ๐Ÿ‡ฌ๐Ÿ‡ฒ Gambia * ๐Ÿ‡ฉ๐Ÿ‡ช Germany * ๐Ÿ‡ฌ๐Ÿ‡ญ Ghana * ๐Ÿ‡ฌ๐Ÿ‡ท Greece * ๐Ÿ‡ฌ๐Ÿ‡น Guatemala * ๐Ÿ‡ฌ๐Ÿ‡พ Guyana * ๐Ÿ‡ญ๐Ÿ‡ฐ Hong Kong * ๐Ÿ‡ญ๐Ÿ‡บ Hungary * ๐Ÿ‡ฎ๐Ÿ‡ธ Iceland * ๐Ÿ‡ฎ๐Ÿ‡ณ India * ๐Ÿ‡ฎ๐Ÿ‡ฉ Indonesia * ๐Ÿ‡ฎ๐Ÿ‡ช Ireland * ๐Ÿ‡ฎ๐Ÿ‡ฑ Israel * ๐Ÿ‡ฎ๐Ÿ‡น Italy * ๐Ÿ‡จ๐Ÿ‡ฎ Ivory Coast * ๐Ÿ‡ฏ๐Ÿ‡ฒ Jamaica * ๐Ÿ‡ฏ๐Ÿ‡ต Japan * ๐Ÿ‡ฏ๐Ÿ‡ด Jordan * ๐Ÿ‡ฐ๐Ÿ‡ฟ Kazakhstan * ๐Ÿ‡ฐ๐Ÿ‡ช Kenya * ๐Ÿ‡ฐ๐Ÿ‡ผ Kuwait * ๐Ÿ‡ฑ๐Ÿ‡ฆ Laos * ๐Ÿ‡ฑ๐Ÿ‡ป Latvia * ๐Ÿ‡ฑ๐Ÿ‡ฎ Liechtenstein * ๐Ÿ‡ฑ๐Ÿ‡น Lithuania * ๐Ÿ‡ฑ๐Ÿ‡บ Luxembourg * ๐Ÿ‡ฒ๐Ÿ‡ด Macao * ๐Ÿ‡ฒ๐Ÿ‡ฌ Madagascar * ๐Ÿ‡ฒ๐Ÿ‡พ Malaysia * ๐Ÿ‡ฒ๐Ÿ‡น Malta * ๐Ÿ‡ฒ๐Ÿ‡บ Mauritius * ๐Ÿ‡ฒ๐Ÿ‡ฝ Mexico * ๐Ÿ‡ฒ๐Ÿ‡ฉ Moldova * ๐Ÿ‡ฒ๐Ÿ‡จ Monaco * ๐Ÿ‡ฒ๐Ÿ‡ณ Mongolia * ๐Ÿ‡ฒ๐Ÿ‡ฆ Morocco * ๐Ÿ‡ฒ๐Ÿ‡ฟ Mozambique * ๐Ÿ‡ณ๐Ÿ‡ฆ Namibia * ๐Ÿ‡ณ๐Ÿ‡ฑ Netherlands * ๐Ÿ‡ณ๐Ÿ‡ฟ New Zealand * ๐Ÿ‡ณ๐Ÿ‡ช Niger * ๐Ÿ‡ณ๐Ÿ‡ฌ Nigeria * ๐Ÿ‡ฒ๐Ÿ‡ฐ North Macedonia * ๐Ÿ‡ณ๐Ÿ‡ด Norway * ๐Ÿ‡ด๐Ÿ‡ฒ Oman * ๐Ÿ‡ต๐Ÿ‡ฐ Pakistan * ๐Ÿ‡ต๐Ÿ‡ฆ Panama * ๐Ÿ‡ต๐Ÿ‡พ Paraguay * ๐Ÿ‡ต๐Ÿ‡ช Peru * ๐Ÿ‡ต๐Ÿ‡ญ Philippines * ๐Ÿ‡ต๐Ÿ‡ฑ Poland * ๐Ÿ‡ต๐Ÿ‡น Portugal * ๐Ÿ‡ถ๐Ÿ‡ฆ Qatar * ๐Ÿ‡ท๐Ÿ‡ด Romania * ๐Ÿ‡ท๐Ÿ‡ผ Rwanda * ๐Ÿ‡ฑ๐Ÿ‡จ Saint Lucia * ๐Ÿ‡ธ๐Ÿ‡ฒ San Marino * ๐Ÿ‡ธ๐Ÿ‡ฆ Saudi Arabia * ๐Ÿ‡ธ๐Ÿ‡ณ Senegal * ๐Ÿ‡ท๐Ÿ‡ธ Serbia * ๐Ÿ‡ธ๐Ÿ‡ฌ Singapore * ๐Ÿ‡ธ๐Ÿ‡ฐ Slovakia * ๐Ÿ‡ธ๐Ÿ‡ฎ Slovenia * ๐Ÿ‡ฟ๐Ÿ‡ฆ South Africa * ๐Ÿ‡ฐ๐Ÿ‡ท South Korea * ๐Ÿ‡ช๐Ÿ‡ธ Spain * ๐Ÿ‡ฑ๐Ÿ‡ฐ Sri Lanka * ๐Ÿ‡ธ๐Ÿ‡ช Sweden * ๐Ÿ‡จ๐Ÿ‡ญ Switzerland * ๐Ÿ‡น๐Ÿ‡ผ Taiwan * ๐Ÿ‡น๐Ÿ‡ฟ Tanzania * ๐Ÿ‡น๐Ÿ‡ญ Thailand * ๐Ÿ‡น๐Ÿ‡น Trinidad and Tobago * ๐Ÿ‡น๐Ÿ‡ณ Tunisia * ๐Ÿ‡น๐Ÿ‡ท Turkey * ๐Ÿ‡ฆ๐Ÿ‡ช United Arab Emirates * ๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom * ๐Ÿ‡บ๐Ÿ‡ธ United States * ๐Ÿ‡บ๐Ÿ‡พ Uruguay * ๐Ÿ‡บ๐Ÿ‡ฟ Uzbekistan * ๐Ÿ‡ป๐Ÿ‡ณ Vietnam ## Frequently Asked Questions Stripe Connect Express is a different product than the regular Stripe payments. Yes, any individual or company operating in our [supported countries](/merchant-of-record/supported-countries) can receive payouts from Polar even if Stripe standalone is invite-only there. This is possible as Polar is the Merchant of Record, all payments from customers are made to Polar (US). [Stripe Connect Express](https://docs.stripe.com/connect/express-accounts) is then used to issue payouts, and is supported in more countries via cross-border transfer than Stripe Payments standalone. You might still see a warning in Stripe Connect Express that payments are invite-only, but don't worry. No direct sales are made directly to the Stripe Connect Express account. They're all made to Polar (US) as a platform and the merchant of record. We only use the transfer and payout feature of Stripe Connect Express which is available in all of our [supported countries](/merchant-of-record/supported-countries). Yes, given that Stripe Connect Express supports individual as a business type in your region. To know which business type is supported in your country, follow steps as below: * Open required [verification information](https://docs.stripe.com/connect/required-verification-information#US+RS+express+recipient+individual+transfers) by Stripe to set up a business or personal account in your country. * Ensure `Platform Country` is set to `United States (US)`. * Ensure `Dashboard Type` is set to `express`. * Ensure `Service Agreement` is set to `recipient`. * Ensure `Capability` is set to `transfers`. * Select the correct `Account Country` relevant to you. * Click on the toggle for `Business Type` which will allow you know if individual, business, company or LLC/LLP is supported by Stripe Connect Express in that region. # Migrate to Polar Source: https://polar.sh/docs/migrate Get set up on Polar in minutes from an existing store ## Lemon Squeezy Ready to make the jump from Lemon Squeezy to Polar? Use the `polar-migrate` CLI tool to quickly and easily migrate your existing Lemon Squeezy products to Polar. ### Getting Started ```bash Terminal theme={null} npx polar-migrate@latest ``` ### Supported Migrations * Products & Variants * License Keys * Associated Files * Discount Codes * Customers This tool is not able to move **active** subscriptions from your Lemon Squeezy store. ### Open Source The code for the CLI is open source and available on GitHub [View Code on GitHub](https://github.com/polarsource/polar-migrate) # Support Source: https://polar.sh/docs/support This page outlines Polar's support plans, available channels, and policies. To learn how to access support, please refer to the [Support channels](#support-channels) section. Identify the channels available to you based on your usage and follow the links to navigate to the relevant information. ## Support channels The support channels you can access differ according to your use of Polar. | Support channels | Community support | Billing support | Standard support | | :--------------------------------------------------- | :---------------: | :-------------: | :--------------: | | [Discord Server](#discord) (not an official channel) | โœ“ | โœ— | โœ— | | [Email](#email) | - | โœ“ | โœ“ | ## Email You can reach us at [support@polar.sh](mailto:support@polar.sh). ## GitHub * [Found a bug?](https://github.com/polarsource/polar/issues) * [Have a feature request?](https://github.com/orgs/polarsource/discussions/categories/feature-requests) ## Discord [Join our Discord](https://dub.sh/polar-discord) to chat with us and fellow Polar developers. ## Etiquette Regardless of the method or location through which Polar provides Support, communication should be professional and respectful. Any communication that is deemed objectionable by Polar staff is not tolerated. This includes but is not limited to any communication that is abusive or contains profane language. Polar reserves the right to terminate Support Services in the event of any such objectionable communication. ## Customer responsibilities To ensure efficient resolution of issues, customers are expected to (1) provide detailed information about the issue, (2) cooperate with the Support team during troubleshooting, and (3) utilize available self-service resources for basic inquiries. ## Changes to the support policy We reserve the right to modify, amend, or update this Support Policy, including the types of support offered, support hours, response times, and support plans, at any time and at our sole discretion. Any changes to the Support Policy will be effective immediately upon posting a revised version of this Support Policy. Continued use of our services after such modifications will constitute acknowledgment and acceptance of the changes.