# Create Benefit Source: https://docs.polar.sh/api-reference/benefits/create post /v1/benefits/ Create a benefit. **Scopes**: `benefits:write` # Delete Benefit Source: https://docs.polar.sh/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://docs.polar.sh/api-reference/benefits/get get /v1/benefits/{id} Get a benefit by ID. **Scopes**: `benefits:read` `benefits:write` # List Benefits Source: https://docs.polar.sh/api-reference/benefits/list get /v1/benefits/ List benefits. **Scopes**: `benefits:read` `benefits:write` # List Benefit Grants Source: https://docs.polar.sh/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://docs.polar.sh/api-reference/benefits/update patch /v1/benefits/{id} Update a benefit. **Scopes**: `benefits:write` # Create Checkout Link Source: https://docs.polar.sh/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://docs.polar.sh/api-reference/checkout-links/delete delete /v1/checkout-links/{id} Delete a checkout link. **Scopes**: `checkout_links:write` # Get Checkout Link Source: https://docs.polar.sh/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://docs.polar.sh/api-reference/checkout-links/list get /v1/checkout-links/ List checkout links. **Scopes**: `checkout_links:read` `checkout_links:write` # Update Checkout Link Source: https://docs.polar.sh/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://docs.polar.sh/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://docs.polar.sh/api-reference/checkouts/create-session post /v1/checkouts/ Create a checkout session. **Scopes**: `checkouts:write` # Get Checkout Session Source: https://docs.polar.sh/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://docs.polar.sh/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://docs.polar.sh/api-reference/checkouts/list-sessions get /v1/checkouts/ List checkout sessions. **Scopes**: `checkouts:read` `checkouts:write` # Update Checkout Session Source: https://docs.polar.sh/api-reference/checkouts/update-session patch /v1/checkouts/{id} Update a checkout session. **Scopes**: `checkouts:write` # Update Checkout Session from Client Source: https://docs.polar.sh/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://docs.polar.sh/api-reference/custom-fields/create post /v1/custom-fields/ Create a custom field. **Scopes**: `custom_fields:write` # Delete Custom Field Source: https://docs.polar.sh/api-reference/custom-fields/delete delete /v1/custom-fields/{id} Delete a custom field. **Scopes**: `custom_fields:write` # Get Custom Field Source: https://docs.polar.sh/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://docs.polar.sh/api-reference/custom-fields/list get /v1/custom-fields/ List custom fields. **Scopes**: `custom_fields:read` `custom_fields:write` # Update Custom Field Source: https://docs.polar.sh/api-reference/custom-fields/update patch /v1/custom-fields/{id} Update a custom field. **Scopes**: `custom_fields:write` # Get Customer Meter Source: https://docs.polar.sh/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://docs.polar.sh/api-reference/customer-meters/list get /v1/customer-meters/ List customer meters. **Scopes**: `customer_meters:read` # Get Downloadable Source: https://docs.polar.sh/api-reference/customer-portal/downloadables/get get /v1/customer-portal/downloadables/{token} # List Downloadables Source: https://docs.polar.sh/api-reference/customer-portal/downloadables/list get /v1/customer-portal/downloadables/ **Scopes**: `customer_portal:read` `customer_portal:write` # Get Customer Source: https://docs.polar.sh/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://docs.polar.sh/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://docs.polar.sh/api-reference/customer-portal/license-keys/activate post /v1/customer-portal/license-keys/activate Activate a license key instance. # Deactivate License Key Source: https://docs.polar.sh/api-reference/customer-portal/license-keys/deactivate post /v1/customer-portal/license-keys/deactivate Deactivate a license key instance. # Get License Key Source: https://docs.polar.sh/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://docs.polar.sh/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://docs.polar.sh/api-reference/customer-portal/license-keys/validate post /v1/customer-portal/license-keys/validate Validate a license key. # Get Order Source: https://docs.polar.sh/api-reference/customer-portal/orders/get get /v1/customer-portal/orders/{id} Get an order by ID for the authenticated customer. **Scopes**: `customer_portal:read` `customer_portal:write` # Get Order Invoice Source: https://docs.polar.sh/api-reference/customer-portal/orders/get-invoice get /v1/customer-portal/orders/{id}/invoice Get an order's invoice data. **Scopes**: `customer_portal:read` `customer_portal:write` 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://docs.polar.sh/api-reference/customer-portal/orders/list get /v1/customer-portal/orders/ List orders of the authenticated customer. **Scopes**: `customer_portal:read` `customer_portal:write` # Update Order Source: https://docs.polar.sh/api-reference/customer-portal/orders/patch patch /v1/customer-portal/orders/{id} Update an order for the authenticated customer. **Scopes**: `customer_portal:write` # Generate Order Invoice Source: https://docs.polar.sh/api-reference/customer-portal/orders/post-invoice post /v1/customer-portal/orders/{id}/invoice Trigger generation of an order's invoice. **Scopes**: `customer_portal:read` `customer_portal:write` 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). # Create Customer Session Source: https://docs.polar.sh/api-reference/customer-portal/sessions/create post /v1/customer-sessions/ Create a customer session. **Scopes**: `customer_sessions:write` # Cancel Subscription Source: https://docs.polar.sh/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://docs.polar.sh/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://docs.polar.sh/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://docs.polar.sh/api-reference/customer-portal/subscriptions/update patch /v1/customer-portal/subscriptions/{id} Update a subscription of the authenticated customer. **Scopes**: `customer_portal:write` # Create Customer Source: https://docs.polar.sh/api-reference/customers/create post /v1/customers/ Create a customer. **Scopes**: `customers:write` # Delete Customer Source: https://docs.polar.sh/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://docs.polar.sh/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://docs.polar.sh/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://docs.polar.sh/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://docs.polar.sh/api-reference/customers/list get /v1/customers/ List customers. **Scopes**: `customers:read` `customers:write` # Get Customer State Source: https://docs.polar.sh/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://docs.polar.sh/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://docs.polar.sh/api-reference/customers/update patch /v1/customers/{id} Update a customer. **Scopes**: `customers:write` # Update Customer by External ID Source: https://docs.polar.sh/api-reference/customers/update-external patch /v1/customers/external/{external_id} Update a customer by external ID. **Scopes**: `customers:write` # Create Discount Source: https://docs.polar.sh/api-reference/discounts/create post /v1/discounts/ Create a discount. **Scopes**: `discounts:write` # Delete Discount Source: https://docs.polar.sh/api-reference/discounts/delete delete /v1/discounts/{id} Delete a discount. **Scopes**: `discounts:write` # Get Discount Source: https://docs.polar.sh/api-reference/discounts/get get /v1/discounts/{id} Get a discount by ID. **Scopes**: `discounts:read` `discounts:write` # List Discounts Source: https://docs.polar.sh/api-reference/discounts/list get /v1/discounts/ List discounts. **Scopes**: `discounts:read` `discounts:write` # Update Discount Source: https://docs.polar.sh/api-reference/discounts/update patch /v1/discounts/{id} Update a discount. **Scopes**: `discounts:write` # Get Event Source: https://docs.polar.sh/api-reference/events/get get /v1/events/{id} Get an event by ID. **Scopes**: `events:read` `events:write` # Ingest Events Source: https://docs.polar.sh/api-reference/events/ingest post /v1/events/ingest Ingest batch of events. **Scopes**: `events:write` # List Events Source: https://docs.polar.sh/api-reference/events/list get /v1/events/ List events. **Scopes**: `events:read` `events:write` # Complete File Upload Source: https://docs.polar.sh/api-reference/files/complete-upload post /v1/files/{id}/uploaded Complete a file upload. **Scopes**: `files:write` # Create File Source: https://docs.polar.sh/api-reference/files/create post /v1/files/ Create a file. **Scopes**: `files:write` # Delete File Source: https://docs.polar.sh/api-reference/files/delete delete /v1/files/{id} Delete a file. **Scopes**: `files:write` # List Files Source: https://docs.polar.sh/api-reference/files/list get /v1/files/ List files. **Scopes**: `files:read` `files:write` # Update File Source: https://docs.polar.sh/api-reference/files/update patch /v1/files/{id} Update a file. **Scopes**: `files:write` # Get License Key Source: https://docs.polar.sh/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://docs.polar.sh/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` # null Source: https://docs.polar.sh/api-reference/license-keys/list get /v1/license-keys # Update License Key Source: https://docs.polar.sh/api-reference/license-keys/update patch /v1/license-keys/{id} Update a license key. **Scopes**: `license_keys:write` # Create Meter Source: https://docs.polar.sh/api-reference/meters/create post /v1/meters/ Create a meter. **Scopes**: `meters:write` # Get Meter Source: https://docs.polar.sh/api-reference/meters/get get /v1/meters/{id} Get a meter by ID. **Scopes**: `meters:read` `meters:write` # Get Meter Quantities Source: https://docs.polar.sh/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://docs.polar.sh/api-reference/meters/list get /v1/meters/ List meters. **Scopes**: `meters:read` `meters:write` # Update Meter Source: https://docs.polar.sh/api-reference/meters/update patch /v1/meters/{id} Update a meter. **Scopes**: `meters:write` # Get Metrics Source: https://docs.polar.sh/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://docs.polar.sh/api-reference/metrics/get-limits get /v1/metrics/limits Get the interval limits for the metrics endpoint. **Scopes**: `metrics:read` # Create Client Source: https://docs.polar.sh/api-reference/oauth2/clients/create post /v1/oauth2/register Create an OAuth2 client. # Delete Client Source: https://docs.polar.sh/api-reference/oauth2/clients/delete delete /v1/oauth2/register/{client_id} Delete an OAuth2 client. # Get Client Source: https://docs.polar.sh/api-reference/oauth2/clients/get get /v1/oauth2/register/{client_id} Get an OAuth2 client by Client ID. # List Clients Source: https://docs.polar.sh/api-reference/oauth2/clients/list get /v1/oauth2/ List OAuth2 clients. # Update Client Source: https://docs.polar.sh/api-reference/oauth2/clients/update put /v1/oauth2/register/{client_id} Update an OAuth2 client. # Authorize Source: https://docs.polar.sh/api-reference/oauth2/connect/authorize get /v1/oauth2/authorize # Get User Info Source: https://docs.polar.sh/api-reference/oauth2/connect/get-user-info get /v1/oauth2/userinfo Get information about the authenticated user. # Introspect Token Source: https://docs.polar.sh/api-reference/oauth2/connect/introspect-token post /v1/oauth2/introspect Get information about an access token. # Request Token Source: https://docs.polar.sh/api-reference/oauth2/connect/request-token post /v1/oauth2/token Request an access token using a valid grant. # Revoke Token Source: https://docs.polar.sh/api-reference/oauth2/connect/revoke-token post /v1/oauth2/revoke Revoke an access token or a refresh token. # Get Order Source: https://docs.polar.sh/api-reference/orders/get get /v1/orders/{id} Get an order by ID. **Scopes**: `orders:read` # Get Order Invoice Source: https://docs.polar.sh/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://docs.polar.sh/api-reference/orders/list get /v1/orders/ List orders. **Scopes**: `orders:read` # Update Order Source: https://docs.polar.sh/api-reference/orders/patch patch /v1/orders/{id} Update an order. **Scopes**: `orders:write` # Generate Order Invoice Source: https://docs.polar.sh/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://docs.polar.sh/api-reference/organizations/create post /v1/organizations/ Create an organization. **Scopes**: `organizations:write` # Get Organization Source: https://docs.polar.sh/api-reference/organizations/get GET /v1/organizations/{id} Get an organization by ID. **Scopes**: `organizations:read` `organizations:write` Hello there. Just testing a custom intro. | Property | Description | | -------- | ------------------------------------- | | Name | Full name of user | | Age | Reported age | | Joined | Whether the user joined the community | Continuing here. Some update to the endpoint here # List Organizations Source: https://docs.polar.sh/api-reference/organizations/list get /v1/organizations/ List organizations. **Scopes**: `organizations:read` `organizations:write` # Update Organization Source: https://docs.polar.sh/api-reference/organizations/update patch /v1/organizations/{id} Update an organization. **Scopes**: `organizations:write` # Create Product Source: https://docs.polar.sh/api-reference/products/create post /v1/products/ Create a product. **Scopes**: `products:write` # Get Product Source: https://docs.polar.sh/api-reference/products/get get /v1/products/{id} Get a product by ID. **Scopes**: `products:read` `products:write` # List Products Source: https://docs.polar.sh/api-reference/products/list get /v1/products/ List products. **Scopes**: `products:read` `products:write` # Update Product Source: https://docs.polar.sh/api-reference/products/update patch /v1/products/{id} Update a product. **Scopes**: `products:write` # Update Product Benefits Source: https://docs.polar.sh/api-reference/products/update-benefits post /v1/products/{id}/benefits Update benefits granted by a product. **Scopes**: `products:write` # API Rate Limits Source: https://docs.polar.sh/api-reference/rate-limits Polar API rate limits and best practices for handling them. Polar API has rate limits to ensure fair usage and maintain performance. The limits are as follows: * **100 requests per second** per IP address. 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. # Create Refund Source: https://docs.polar.sh/api-reference/refunds/create post /v1/refunds/ Create a refund. **Scopes**: `refunds:write` # List Refunds Source: https://docs.polar.sh/api-reference/refunds/list get /v1/refunds/ List products. **Scopes**: `refunds:read` `refunds:write` # Get Subscription Source: https://docs.polar.sh/api-reference/subscriptions/get get /v1/subscriptions/{id} Get a subscription by ID. **Scopes**: `subscriptions:write` # List Subscriptions Source: https://docs.polar.sh/api-reference/subscriptions/list get /v1/subscriptions/ List subscriptions. **Scopes**: `subscriptions:read` `subscriptions:write` # Revoke Subscription Source: https://docs.polar.sh/api-reference/subscriptions/revoke delete /v1/subscriptions/{id} Revoke a subscription, i.e cancel immediately. **Scopes**: `subscriptions:write` # Update Subscription Source: https://docs.polar.sh/api-reference/subscriptions/update patch /v1/subscriptions/{id} Update a subscription. **Scopes**: `subscriptions:write` # benefit.created Source: https://docs.polar.sh/api-reference/webhooks/benefit.created # benefit.updated Source: https://docs.polar.sh/api-reference/webhooks/benefit.updated # benefit_grant.created Source: https://docs.polar.sh/api-reference/webhooks/benefit_grant.created # benefit_grant.cycled Source: https://docs.polar.sh/api-reference/webhooks/benefit_grant.cycled # benefit_grant.revoked Source: https://docs.polar.sh/api-reference/webhooks/benefit_grant.revoked # benefit_grant.updated Source: https://docs.polar.sh/api-reference/webhooks/benefit_grant.updated # checkout.created Source: https://docs.polar.sh/api-reference/webhooks/checkout.created # checkout.updated Source: https://docs.polar.sh/api-reference/webhooks/checkout.updated # customer.created Source: https://docs.polar.sh/api-reference/webhooks/customer.created # customer.deleted Source: https://docs.polar.sh/api-reference/webhooks/customer.deleted # customer.state_changed Source: https://docs.polar.sh/api-reference/webhooks/customer.state_changed # customer.updated Source: https://docs.polar.sh/api-reference/webhooks/customer.updated # Create Webhook Endpoint Source: https://docs.polar.sh/api-reference/webhooks/endpoints/create post /v1/webhooks/endpoints Create a webhook endpoint. **Scopes**: `webhooks:write` # Delete Webhook Endpoint Source: https://docs.polar.sh/api-reference/webhooks/endpoints/delete delete /v1/webhooks/endpoints/{id} Delete a webhook endpoint. **Scopes**: `webhooks:write` # Get Webhook Endpoint Source: https://docs.polar.sh/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://docs.polar.sh/api-reference/webhooks/endpoints/list get /v1/webhooks/endpoints List webhook endpoints. **Scopes**: `webhooks:read` `webhooks:write` # Update Webhook Endpoint Source: https://docs.polar.sh/api-reference/webhooks/endpoints/update patch /v1/webhooks/endpoints/{id} Update a webhook endpoint. **Scopes**: `webhooks:write` # order.created Source: https://docs.polar.sh/api-reference/webhooks/order.created # order.paid Source: https://docs.polar.sh/api-reference/webhooks/order.paid # order.refunded Source: https://docs.polar.sh/api-reference/webhooks/order.refunded # order.updated Source: https://docs.polar.sh/api-reference/webhooks/order.updated # organization.updated Source: https://docs.polar.sh/api-reference/webhooks/organization.updated # product.created Source: https://docs.polar.sh/api-reference/webhooks/product.created # product.updated Source: https://docs.polar.sh/api-reference/webhooks/product.updated # refund.created Source: https://docs.polar.sh/api-reference/webhooks/refund.created # refund.updated Source: https://docs.polar.sh/api-reference/webhooks/refund.updated # subscription.active Source: https://docs.polar.sh/api-reference/webhooks/subscription.active # subscription.canceled Source: https://docs.polar.sh/api-reference/webhooks/subscription.canceled # subscription.created Source: https://docs.polar.sh/api-reference/webhooks/subscription.created # subscription.revoked Source: https://docs.polar.sh/api-reference/webhooks/subscription.revoked # subscription.uncanceled Source: https://docs.polar.sh/api-reference/webhooks/subscription.uncanceled # subscription.updated Source: https://docs.polar.sh/api-reference/webhooks/subscription.updated # API Changelog Source: https://docs.polar.sh/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://docs.polar.sh/changelog/recent Stay up to date with the latest changes and improvements to Polar. ## 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`. New documentation platform. More improvements incoming. # Analytics Source: https://docs.polar.sh/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. # Credits Benefit Source: https://docs.polar.sh/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://docs.polar.sh/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://docs.polar.sh/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://docs.polar.sh/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://docs.polar.sh/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. **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://docs.polar.sh/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://docs.polar.sh/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 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 { "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 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 { "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://docs.polar.sh/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 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 // 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 // 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://docs.polar.sh/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://docs.polar.sh/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://docs.polar.sh/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://docs.polar.sh/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://docs.polar.sh/features/integrations/zapier Connect Polar to hundreds of other apps with Zapier export const ZapierEmbed = () => { if (typeof document === "undefined") { return null; } else { setTimeout(() => { const script = document.createElement("script"); script.type = "module"; script.src = "https://cdn.zapier.com/packages/partner-sdk/v0/zapier-elements/zapier-elements.esm.js"; document.head.appendChild(script); const stylesheet = document.createElement("link"); stylesheet.rel = "stylesheet"; stylesheet.href = "https://cdn.zapier.com/packages/partner-sdk/v0/zapier-elements/zapier-elements.css"; document.head.appendChild(stylesheet); const element = document.createElement("zapier-workflow"); element.clientId = "Zci4gpfx7Co47mBoFOYm0m8bmnzB5UPcw7eGhpSR"; element.theme = document.querySelector("html").classList.contains("dark") ? "dark" : "light"; element.introCopyDisplay = "hide"; element.manageZapsDisplay = "hide"; element.guessZapDisplay = "hide"; const container = document.querySelector("#zapier-container") || document.body; container.appendChild(element); }, 1); return
; } }; [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://docs.polar.sh/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://docs.polar.sh/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. ### 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). ## Subscription Trials Polar does not have a built-in trial period. However, you can achieve similar functionality by associating a discount with the subscription product. # Manage Refunds Source: https://docs.polar.sh/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. # Billing Source: https://docs.polar.sh/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. 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://docs.polar.sh/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://docs.polar.sh/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 event. For example, `ai_usage`, `video_streamed` or `file_uploaded`. * A `customer_id` or `external_customer_id`, which is the 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 { "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 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://docs.polar.sh/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 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 { "customerId": "123", "name": "execution-time", "metadata": { "deltaTime": 1000 } } ``` # Strategy Introduction Source: https://docs.polar.sh/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://docs.polar.sh/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 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"))) .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: request.headers.get("X-Polar-Customer-Id") ?? "", }); const { text } = await generateText({ model, system: "You are a helpful assistant.", prompt, }); return Response.json({ text }); } ``` #### Ingestion Payload ```json { "customerId": "123", "name": "openai-usage", "metadata": { "promptTokens": 100, "completionTokens": 200 } } ``` ## 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 pip install polar-sdk ``` ```bash uv 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 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 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 { "name": "ai_usage", "external_customer_id": "CUSTOMER_ID", "metadata": { "requests": 1, "total_tokens": 78, "request_tokens": 58, "response_tokens": 20 } } ``` # S3 Strategy Source: https://docs.polar.sh/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 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 { "customerId": "123", "name": "s3-uploads", "metadata": { "bytes": 100, "bucket": "my-bucket", "key": "my-key", "contentType": "application/text" } } ``` # Stream Strategy Source: https://docs.polar.sh/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 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 { "customerId": "123", "name": "my-stream", "metadata": { "bytes": 100 } } ``` # Introduction Source: https://docs.polar.sh/features/usage-based-billing/introduction Usage based billing using ingested events Usage Based Billing is currently in Alpha & subject to change. ## 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 event. For example, `ai_usage`, `video_streamed` or `file_uploaded`. * A `customer_id` or `external_customer_id`, which is the 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 { "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. ### Diagram Here is a diagram of how the different components of Usage Based Billing work together: ```mermaid flowchart TD App[Your Application] User[User] subgraph Polar["Polar"] API[Events Ingestion API] DB[(Events database)] Meters[Meters] Products[Products] Benefit[Meter Credits Benefit] subgraph MeteredPrice["Metered Prices"] Unit[Unit Pricing] end end User -->|Uses| App App -->|Sends events| API API -->|Stores events| DB Benefit -->|Stores credit events| DB DB -->|Filtered & aggregated by| Meters Meters -->|Associated with| Products Benefit -->|Associated with| Meters Products -.->|Apply| MeteredPrice ``` ## Quickstart Get up and running in 5 minutes Usage Based Billing is currently in Alpha. Enable it by navigating to the Organization Settings and toggling the "Usage Based Billing" switch. 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://docs.polar.sh/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. Consider the following events: ```json [ { "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 } } ] ``` Here is the result of each aggregation function, over the `total_tokens` metadata property: * **Count**: 3 units * **Sum**: 60 units * **Average**: 20 units * **Minimum**: 10 units * **Maximum**: 30 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: ### Updating a Meter You may update a meter's filters or aggregation function as long as the meter doesn't have any processed events. ### Deleting a Meter Meters are permanent. Once created, they cannot be deleted. # Integrate Polar with Laravel Source: https://docs.polar.sh/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 // routes/web.php Route::get('/products', [ProductsController::class, 'handle']); ``` Next up, create the `ProductsController` class in the `app/Http/Controllers` directory: ```php // 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 // 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 // routes/web.php Route::get('/checkout', [CheckoutController::class, 'handle']); ``` Next, create the `CheckoutController` class in the `app/Http/Controllers` directory: ```php // 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 // routes/web.php Route::get('/confirmation', [ConfirmationController::class, 'handle']); ``` Next, create the `ConfirmationController` class in the `app/Http/Controllers` directory: ```php // 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 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 # .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 composer require standard-webhooks/standard-webhooks:dev-main ``` Go and add a `routes/api.php` file and add the following entry: ```php // routes/api.php Route::webhooks('/webhook/polar'); ``` Make sure that it is included in the Bootstrap file. ```php // 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 composer require spatie/laravel-webhook-client ``` Let's publish the config: ```bash 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 // 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 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 // 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 // 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://docs.polar.sh/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 # 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 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 // 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 // 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 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 # .env POLAR_ACCESS_TOKEN="polar_pat..." POLAR_WEBHOOK_SECRET="..." ``` ### Setting up the Webhook handler ```typescript // 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 // 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). # Authentication Source: https://docs.polar.sh/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) - 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://docs.polar.sh/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 as Model Context Protocol (MCP) Source: https://docs.polar.sh/integrate/mcp Extend the capabilities of your AI Agents with Polar as 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 has MCP support built into the Polar TypeScript SDK. ## How does it work? You need a MCP-capable Agent environment to use Polar as an MCP server. A few of them are Claude and Cursor. ## Using Polar as an MCP server ### Claude Add the following server definition to your claude\_desktop\_config.json file: ```json { "mcpServers": { "Polar": { "command": "npx", "args": [ "-y", "--package", "@polar-sh/sdk", "--", "mcp", "start", "--access-token", "..." ] } } } ``` ### Cursor Go to Cursor Settings > Features > MCP Servers > Add new MCP server and use the following settings: * Name: Polar * Type: command * Command: ```bash npx -y --package @polar-sh/sdk -- mcp start --access-token ... ``` ### Help For a full list of server arguments, run: ```bash npx -y --package @polar-sh/sdk -- mcp start --help ``` # OAuth 2.0 Connect Source: https://docs.polar.sh/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. If they allow it, 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. #### 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 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 { "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). #### User vs Organization Access Tokens We support the concept of access tokens at **organization level**. Contrary to the standard access tokens, those tokens are not tied to a user but to an organization. They can be used to make requests operating only on a specific organization data, improving privacy and security. To ask for an organization access token, you need to add the parameter `sub_type=organization` 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=organization ``` At this point, the user will be prompted to select one of their organization before allowing access to their data. The rest of the flow remains unchanged. The access token you'll get will be tied to the selected organization. Bear in mind that some endpoints might not support organization access tokens. Typically, user-specific endpoints like `/v1/users/benefit` will not work with organization access tokens. #### 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 curl -X GET https://api.polar.sh/v1/oauth2/userinfo \ -H 'Authorization: Bearer polar_at_XXX' ``` # Introduction Source: https://docs.polar.sh/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://docs.polar.sh/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. **HTTPS URL are required unless the hostname is** `localhost`. * *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://docs.polar.sh/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 const polar = new Polar({ server: 'sandbox', accessToken: process.env['POLAR_ACCESS_TOKEN'] ?? '', }) ``` ```py Python s = Polar( server="sandbox", access_token="", ) ``` ```go Go s := polargo.New( polargo.WithServer("sandbox"), polargo.WithSecurity(os.Getenv("POLAR_ACCESS_TOKEN")), ) ``` ```php PHP $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://docs.polar.sh/integrate/sdk/adapters/astro Payments and Checkouts made dead simple with Astro `pnpm install @polar-sh/astro zod` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript 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, server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }); ``` ### 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 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 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 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. * onCheckoutCreated: (payload) => * onCheckoutUpdated: (payload) => * onOrderCreated: (payload) => * onSubscriptionCreated: (payload) => * onSubscriptionUpdated: (payload) => * onSubscriptionActive: (payload) => * onSubscriptionCanceled: (payload) => * onSubscriptionRevoked: (payload) => * onProductCreated: (payload) => * onProductUpdated: (payload) => * onOrganizationUpdated: (payload) => * onBenefitCreated: (payload) => * onBenefitUpdated: (payload) => * onBenefitGrantCreated: (payload) => * onBenefitGrantUpdated: (payload) => * onBenefitGrantRevoked: (payload) => * onCustomerCreated: (payload) => * onCustomerUpdated: (payload) => * onCustomerDeleted: (payload) => * onCustomerStateChanged: (payload) => # BetterAuth Source: https://docs.polar.sh/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) * [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) ## Installation Install the BetterAuth and Polar required libraries using the following command: ```bash npm install better-auth @polar-sh/better-auth @polar-sh/sdk ``` Install the BetterAuth and Polar required libraries using the following command: ```bash yarn add better-auth @polar-sh/better-auth @polar-sh/sdk ``` Install the BetterAuth and Polar required libraries using the following command: ```bash 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 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 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 // ... 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 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. ## 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. * **`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. Update the `use` property of the Polar plugin for BetterAuth client to have the `checkout` plugin. ```typescript icon="square-js" Checkout Plugin Example 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 ++] // Wheather you want to allow unauthenticated checkout sessions or not authenticatedUsersOnly: true // [!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 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 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 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 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 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 /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 POLAR_WEBHOOK_SECRET="..." ``` Pass the `webhooks` plugin in the `use` property. ```typescript icon="square-js" Webhooks Plugin Example 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 is created ## 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 import { polar, checkout, portal // [!code ++] } from "@polar-sh/better-auth"; const auth = betterAuth({ // ... Better Auth config plugins: [ polar({ ... use: [ checkout(...), 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 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 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://docs.polar.sh/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 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 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 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 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://docs.polar.sh/integrate/sdk/adapters/deno Payments and Checkouts made dead simple with Deno ## Checkout Create a Checkout handler which takes care of redirections. ```typescript import { Checkout } from "jsr:@polar-sh/deno"; Deno.serve(Checkout({ accessToken: "xxx" })); ``` ### 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 import { CustomerPortal } from "jsr:@polar-sh/deno"; Deno.serve( CustomerPortal({ accessToken: "xxx", getCustomerId: (req) => "", server: "sandbox", }) ); ``` ## Webhooks A simple utility which resolves incoming webhook payloads by signing the webhook secret properly. ```typescript 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. * onCheckoutCreated: (payload) => * onCheckoutUpdated: (payload) => * onOrderCreated: (payload) => * onSubscriptionCreated: (payload) => * onSubscriptionUpdated: (payload) => * onSubscriptionActive: (payload) => * onSubscriptionCanceled: (payload) => * onSubscriptionRevoked: (payload) => * onProductCreated: (payload) => * onProductUpdated: (payload) => * onOrganizationUpdated: (payload) => * onBenefitCreated: (payload) => * onBenefitUpdated: (payload) => * onBenefitGrantCreated: (payload) => * onBenefitGrantUpdated: (payload) => * onBenefitGrantRevoked: (payload) => * onCustomerCreated: (payload) => * onCustomerUpdated: (payload) => * onCustomerDeleted: (payload) => * onCustomerStateChanged: (payload) => # Elysia Source: https://docs.polar.sh/integrate/sdk/adapters/elysia Payments and Checkouts made dead simple with Elysia ```bash pnpm install @polar-sh/elysia zod ``` ### Checkout Create a Checkout handler which takes care of redirections. ```typescript 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, server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }) ); ``` #### 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 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 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 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. * onCheckoutCreated: (payload) => * onCheckoutUpdated: (payload) => * onOrderCreated: (payload) => * onSubscriptionCreated: (payload) => * onSubscriptionUpdated: (payload) => * onSubscriptionActive: (payload) => * onSubscriptionCanceled: (payload) => * onSubscriptionRevoked: (payload) => * onProductCreated: (payload) => * onProductUpdated: (payload) => * onOrganizationUpdated: (payload) => * onBenefitCreated: (payload) => * onBenefitUpdated: (payload) => * onBenefitGrantCreated: (payload) => * onBenefitGrantUpdated: (payload) => * onBenefitGrantRevoked: (payload) => * onCustomerCreated: (payload) => * onCustomerUpdated: (payload) => * onCustomerDeleted: (payload) => * onCustomerStateChanged: (payload) => # Express Source: https://docs.polar.sh/integrate/sdk/adapters/express Payments and Checkouts made dead simple with Express ```bash pnpm install @polar-sh/express zod ``` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript 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, server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }) ); ``` ### 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 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 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 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. * onCheckoutCreated: (payload) => * onCheckoutUpdated: (payload) => * onOrderCreated: (payload) => * onSubscriptionCreated: (payload) => * onSubscriptionUpdated: (payload) => * onSubscriptionActive: (payload) => * onSubscriptionCanceled: (payload) => * onSubscriptionRevoked: (payload) => * onProductCreated: (payload) => * onProductUpdated: (payload) => * onOrganizationUpdated: (payload) => * onBenefitCreated: (payload) => * onBenefitUpdated: (payload) => * onBenefitGrantCreated: (payload) => * onBenefitGrantUpdated: (payload) => * onBenefitGrantRevoked: (payload) => * onCustomerCreated: (payload) => * onCustomerUpdated: (payload) => * onCustomerDeleted: (payload) => * onCustomerStateChanged: (payload) => # Fastify Source: https://docs.polar.sh/integrate/sdk/adapters/fastify Payments and Checkouts made dead simple with Fastify ```bash pnpm install @polar-sh/fastify zod ``` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript 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, server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }) ); ``` ### 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 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 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 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. * onCheckoutCreated: (payload) => * onCheckoutUpdated: (payload) => * onOrderCreated: (payload) => * onSubscriptionCreated: (payload) => * onSubscriptionUpdated: (payload) => * onSubscriptionActive: (payload) => * onSubscriptionCanceled: (payload) => * onSubscriptionRevoked: (payload) => * onProductCreated: (payload) => * onProductUpdated: (payload) => * onOrganizationUpdated: (payload) => * onBenefitCreated: (payload) => * onBenefitUpdated: (payload) => * onBenefitGrantCreated: (payload) => * onBenefitGrantUpdated: (payload) => * onBenefitGrantRevoked: (payload) => * onCustomerCreated: (payload) => * onCustomerUpdated: (payload) => * onCustomerDeleted: (payload) => * onCustomerStateChanged: (payload) => # Hono Source: https://docs.polar.sh/integrate/sdk/adapters/hono Payments and Checkouts made dead simple with Hono ```bash pnpm install @polar-sh/hono zod ``` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript 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, server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }) ); ``` ### 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 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 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 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. * onCheckoutCreated: (payload) => * onCheckoutUpdated: (payload) => * onOrderCreated: (payload) => * onSubscriptionCreated: (payload) => * onSubscriptionUpdated: (payload) => * onSubscriptionActive: (payload) => * onSubscriptionCanceled: (payload) => * onSubscriptionRevoked: (payload) => * onProductCreated: (payload) => * onProductUpdated: (payload) => * onOrganizationUpdated: (payload) => * onBenefitCreated: (payload) => * onBenefitUpdated: (payload) => * onBenefitGrantCreated: (payload) => * onBenefitGrantUpdated: (payload) => * onBenefitGrantRevoked: (payload) => * onCustomerCreated: (payload) => * onCustomerUpdated: (payload) => * onCustomerDeleted: (payload) => * onCustomerStateChanged: (payload) => # null Source: https://docs.polar.sh/integrate/sdk/adapters/laravel ![](https://banners.beyondco.de/laravel-polar.png?theme=dark\&packageManager=composer+require\&packageName=danestves%2Flaravel-polar\&pattern=pieFactory\&style=style_1\&description=Easily+integrate+your+Laravel+application+with+Polar.sh\&md=1\&showWatermark=1\&fontSize=100px\&images=https%3A%2F%2Flaravel.com%2Fimg%2Flogomark.min.svg "Laravel Polar") # Polar for 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. ## Installation **Step 1:** You can install the package via composer: ```bash composer require danestves/laravel-polar ``` **Step 2:** Run `:install`: ```bash 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 php artisan vendor:publish --tag="polar-migrations" ``` ```bash php artisan vendor:publish --tag="polar-config" ``` ```bash php artisan vendor:publish --tag="polar-views" ``` ```bash php artisan migrate ``` This is the contents of the published config file: ```php 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 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 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 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://docs.polar.sh/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 ... @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 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 ->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 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 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 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 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 ``` 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 ``` 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 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 $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 $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 $request->user()->checkout('variant-id') ->withMetadata(['key' => 'value']); ``` You can also add customer metadata to the checkout session using the `withCustomerMetadata` method: ```php $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://docs.polar.sh/features/customer-portal). To redirect customers to this portal, call the `redirectToCustomerPortal()` method on your billable model (e.g., the User model). ```php 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 $url = $user->customerPortalUrl(); ``` ### Orders #### Retrieving Orders You can retrieve orders by using the `orders` relationship on the billable model: ```blade @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 $order->status; ``` Or you can use some of the helper methods offers by the `Order` model: ```php $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 @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 if ($order->hasProduct('product_id_123')) { // ... } ``` Or for an specific price: ```php if ($order->hasPrice('price_id_123')) { // ... } ``` Furthermore, you can check if a consumer has purchased a specific product: ```php if ($user->hasPurchasedProduct('product_id_123')) { // ... } ``` Or for an specific price: ```php 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 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 $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 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 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 if ($user->subscription()->hasProduct('product_id_123')) { // ... } ``` Or for a certain price: ```php 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 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 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 if ($user->subscription()->cancelled()) { // ... } ``` When they are in their grace period, you can utilize the `onGracePeriod` check. ```php 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 if ($user->subscription()->pastDue()) { // ... } ``` #### Subscription Scopes There are several subscription scopes available for querying subscriptions in specific states: ```php // 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 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 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 $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 $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 $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 $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 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 $user->subscription()->resume(); ``` When a cancelled subscription approaches the end of its grace period, it becomes expired and cannot be resumed. #### Subscription Trials > \[!NOTE] > Coming soon. ### 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 payload['type'] === 'subscription.updated') { // Handle the incoming event... } } } ``` The [Polar documentation](https://docs.polar.sh/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 [ PolarEventListener::class, ], ]; } ``` ## Roadmap * [ ] Add support for trials Polar itself doesn't support trials, but we can manage them by ourselves. ## Testing ```bash composer test ``` ## Changelog Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. ## Contributing Please see [CONTRIBUTING](CONTRIBUTING.md) for details. ## Security Vulnerabilities Please review [our security policy](../../security/policy) on how to report security vulnerabilities. ## Credits * [laravel/cashier (Stripe)](https://github.com/laravel/cashier-stripe) * [laravel/cashier (Paddle)](https://github.com/laravel/cashier-paddle) * [lemonsqueezy/laravel](https://github.com/lmsqueezy/laravel) * [All Contributors](../../contributors) ## License The MIT License (MIT). Please see [License File](LICENSE.md) for more information. # NextJS Source: https://docs.polar.sh/integrate/sdk/adapters/nextjs Payments and Checkouts made dead simple with NextJS ```bash pnpm install @polar-sh/nextjs zod ``` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript // checkout/route.ts import { Checkout } from "@polar-sh/nextjs"; export const GET = Checkout({ accessToken: process.env.POLAR_ACCESS_TOKEN, successUrl: process.env.SUCCESS_URL, server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }); ``` ### 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 // portal/route.ts 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 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 // 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 the payload // No need to return an acknowledge response }, }); ``` ### Payload Handlers The Webhook handler also supports granular handlers for easy integration. * onCheckoutCreated: (payload) => * onCheckoutUpdated: (payload) => * onOrderCreated: (payload) => * onSubscriptionCreated: (payload) => * onSubscriptionUpdated: (payload) => * onSubscriptionActive: (payload) => * onSubscriptionCanceled: (payload) => * onSubscriptionRevoked: (payload) => * onProductCreated: (payload) => * onProductUpdated: (payload) => * onOrganizationUpdated: (payload) => * onBenefitCreated: (payload) => * onBenefitUpdated: (payload) => * onBenefitGrantCreated: (payload) => * onBenefitGrantUpdated: (payload) => * onBenefitGrantRevoked: (payload) => * onCustomerCreated: (payload) => * onCustomerUpdated: (payload) => * onCustomerDeleted: (payload) => * onCustomerStateChanged: (payload) => # Nuxt Source: https://docs.polar.sh/integrate/sdk/adapters/nuxt Payments and Checkouts made dead simple with Nuxt ## Installation Choose your preferred package manager to install the module: `pnpm add @polar-sh/nuxt` ### Register the module Add the module to your `nuxt.config.ts`: ```typescript export default defineNuxtConfig({ modules: ["@polar-sh/nuxt"], }); ``` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript // server/routes/api/checkout.post.ts export default defineEventHandler((event) => { const { private: { polarAccessToken, polarCheckoutSuccessUrl, polarServer }, } = useRuntimeConfig(); const checkoutHandler = Checkout({ accessToken: polarAccessToken, successUrl: polarCheckoutSuccessUrl, server: polarServer as "sandbox" | "production", }); 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 // server/routes/api/portal.get.ts export default defineEventHandler((event) => { const { private: { polarAccessToken, polarCheckoutSuccessUrl, polarServer }, } = useRuntimeConfig(); const customerPortalHandler = CustomerPortal({ accessToken: polarAccessToken, 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 // server/routes/webhook/polar.post.ts 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. * onCheckoutCreated: (payload) => * onCheckoutUpdated: (payload) => * onOrderCreated: (payload) => * onSubscriptionCreated: (payload) => * onSubscriptionUpdated: (payload) => * onSubscriptionActive: (payload) => * onSubscriptionCanceled: (payload) => * onSubscriptionRevoked: (payload) => * onProductCreated: (payload) => * onProductUpdated: (payload) => * onOrganizationUpdated: (payload) => * onBenefitCreated: (payload) => * onBenefitUpdated: (payload) => * onBenefitGrantCreated: (payload) => * onBenefitGrantUpdated: (payload) => * onBenefitGrantRevoked: (payload) => * onCustomerCreated: (payload) => * onCustomerUpdated: (payload) => * onCustomerDeleted: (payload) => * onCustomerStateChanged: (payload) => # Remix Source: https://docs.polar.sh/integrate/sdk/adapters/remix Payments and Checkouts made dead simple with Remix ```bash pnpm install @polar-sh/remix zod ``` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript 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, server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }); ``` ### 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 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 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 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. * onCheckoutCreated: (payload) => * onCheckoutUpdated: (payload) => * onOrderCreated: (payload) => * onSubscriptionCreated: (payload) => * onSubscriptionUpdated: (payload) => * onSubscriptionActive: (payload) => * onSubscriptionCanceled: (payload) => * onSubscriptionRevoked: (payload) => * onProductCreated: (payload) => * onProductUpdated: (payload) => * onOrganizationUpdated: (payload) => * onBenefitCreated: (payload) => * onBenefitUpdated: (payload) => * onBenefitGrantCreated: (payload) => * onBenefitGrantUpdated: (payload) => * onBenefitGrantRevoked: (payload) => * onCustomerCreated: (payload) => * onCustomerUpdated: (payload) => * onCustomerDeleted: (payload) => * onCustomerStateChanged: (payload) => # Sveltekit Source: https://docs.polar.sh/integrate/sdk/adapters/sveltekit Payments and Checkouts made dead simple with Sveltekit ```bash pnpm install @polar-sh/sveltekit zod ``` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript // /api/checkout/+server.ts import { Checkout } from "@polar-sh/sveltekit"; export const GET = Checkout({ accessToken: process.env.POLAR_ACCESS_TOKEN, successUrl: process.env.SUCCESS_URL, server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }); ``` ### 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 // /api/portal/+server.ts import { CustomerPortal } from "@polar-sh/sveltekit"; export const GET = CustomerPortal({ accessToken: process.env.POLAR_ACCESS_TOKEN, getCustomerId: (event) => "", // Function to resolve a Polar Customer ID 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 // api/webhook/polar/route.ts import { Webhooks } from "@polar-sh/sveltekit"; export const POST = Webhooks({ webhookSecret: process.env.POLAR_WEBHOOK_SECRET!, onPayload: async (payload) => { // Handle the payload }, }); ``` ### Payload Handlers The Webhook handler also supports granular handlers for easy integration. * onCheckoutCreated: (payload) => * onCheckoutUpdated: (payload) => * onOrderCreated: (payload) => * onSubscriptionCreated: (payload) => * onSubscriptionUpdated: (payload) => * onSubscriptionActive: (payload) => * onSubscriptionCanceled: (payload) => * onSubscriptionRevoked: (payload) => * onProductCreated: (payload) => * onProductUpdated: (payload) => * onOrganizationUpdated: (payload) => * onBenefitCreated: (payload) => * onBenefitUpdated: (payload) => * onBenefitGrantCreated: (payload) => * onBenefitGrantUpdated: (payload) => * onBenefitGrantRevoked: (payload) => * onCustomerCreated: (payload) => * onCustomerUpdated: (payload) => * onCustomerDeleted: (payload) => * onCustomerStateChanged: (payload) => # TanStack Start Source: https://docs.polar.sh/integrate/sdk/adapters/tanstack-start Payments and Checkouts made dead simple with TanStack Start `pnpm install @polar-sh/tanstack-start zod` ## Checkout Create a Checkout handler which takes care of redirections. ```typescript // routes/api/checkout.ts import { Checkout } from "@polar-sh/tanstack-start"; import { createAPIFileRoute } from "@tanstack/react-start/api"; export const APIRoute = createAPIFileRoute("/api/checkout")({ GET: Checkout({ accessToken: process.env.POLAR_ACCESS_TOKEN, successUrl: process.env.SUCCESS_URL, server: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }), }); ``` ### 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 // routes/api/portal.ts import { CustomerPortal } from "@polar-sh/tanstack-start"; import { createAPIFileRoute } from "@tanstack/react-start/api"; import { getSupabaseServerClient } from "~/servers/supabase-server"; export const APIRoute = createAPIFileRoute("/api/portal")({ GET: CustomerPortal({ accessToken: process.env.POLAR_ACCESS_TOKEN, getCustomerId: async (request: Request) => "", // Function to resolve a Polar Customer ID 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 // api/webhook/polar.ts import { Webhooks } from "@polar-sh/tanstack-start"; import { createAPIFileRoute } from "@tanstack/react-start/api"; export const APIRoute = createAPIFileRoute("/api/webhook/polar")({ 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. * onCheckoutCreated: (payload) => * onCheckoutUpdated: (payload) => * onOrderCreated: (payload) => * onOrderUpdated: (payload) => * onOrderPaid: (payload) => * onSubscriptionCreated: (payload) => * onSubscriptionUpdated: (payload) => * onSubscriptionActive: (payload) => * onSubscriptionCanceled: (payload) => * onSubscriptionRevoked: (payload) => * onProductCreated: (payload) => * onProductUpdated: (payload) => * onOrganizationUpdated: (payload) => * onBenefitCreated: (payload) => * onBenefitUpdated: (payload) => * onBenefitGrantCreated: (payload) => * onBenefitGrantUpdated: (payload) => * onBenefitGrantRevoked: (payload) => * onCustomerCreated: (payload) => * onCustomerUpdated: (payload) => * onCustomerDeleted: (payload) => * onCustomerStateChanged: (payload) => # Go SDK Source: https://docs.polar.sh/integrate/sdk/golang Documentation coming soon. # PHP SDK Source: https://docs.polar.sh/integrate/sdk/php Documentation coming soon. # Python SDK Source: https://docs.polar.sh/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 pip install polar-sdk ``` ```python # 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 s = Polar( server="sandbox", access_token="", ) ``` # TypeScript SDK Source: https://docs.polar.sh/integrate/sdk/typescript SDK for JavaScript runtimes (Node.js and browsers) ### Quickstart ```bash pnpm add @polar-sh/sdk ``` ```typescript import { Polar } from '@polar-sh/sdk' const polar = new Polar({ accessToken: process.env['POLAR_ACCESS_TOKEN'] ?? '', }) async function run() { const result = await polar.users.benefits.list({}) for await (const page of result) { // Handle the page console.log(page) } } run() ``` [Read more](https://github.com/polarsource/polar-js) ### Framework Adapters Implement Checkout & Webhook handlers in 5 lines of code. * [Next.js](/integrate/sdk/adapters/nextjs) * [Astro](/integrate/sdk/adapters/astro) * [Remix](/integrate/sdk/adapters/remix) * [Sveltekit](/integrate/sdk/adapters/sveltekit) * [Deno](/integrate/sdk/adapters/deno) * [Elysia](/integrate/sdk/adapters/elysia) * [Express](/integrate/sdk/adapters/express) * [Fastify](/integrate/sdk/adapters/fastify) * [Hono](/integrate/sdk/adapters/hono) ### 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` property to the configuration object: ```typescript const polar = new Polar({ server: 'sandbox', accessToken: process.env['POLAR_ACCESS_TOKEN'] ?? '', }) ``` # Handle & monitor webhook deliveries Source: https://docs.polar.sh/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 JS (Express) 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) 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( payload=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. ## 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 timeout our requests to your endpoint after **20 seconds**. Triggering a retry attempt after a delay as explained above. However, we strongly recommend you optimize your endpoint route to be fast. A best practice is for your webhook handler to queue a background worker task to handle the payload asynchronously. ## 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. ### 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://docs.polar.sh/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 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://docs.polar.sh/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. 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` 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. ### Order ### Refunds ### Benefit Grants ## Organization Events ### Benefits ### Products ### Organization # Modern Billing for Developers & Designers Source: https://docs.polar.sh/introduction An open source Merchant of Record with 20% lower fees Polar is purpose-built for developers, designers and startups to monetize their digital products and software with ease. **Highlighted Features** We obsess over developer ergonomics and ease. Just checkout our [Next.js](/guides/nextjs) guide below. ```bash pnpm install @polar-sh/nextjs zod ``` ```typescript // checkout/route.ts import { Checkout } from "@polar-sh/nextjs"; export const GET = Checkout({ accessToken: process.env.POLAR_ACCESS_TOKEN, successUrl: process.env.SUCCESS_URL, }); ``` Our dashboard is built to be delightfully intuitive and easy to use even without ever integrating our API. See how you can easily create powerful [Checkout Links](/features/checkout/links). {" "} Sell internationally and leave the headache of calculating, capturing and remitting taxes world-wide to us as your [Merchant of Record](/merchant-of-record/introduction). {" "} * No fixed monthly costs - 4% + 40ยข on transactions - we earn when you do - Some payments may be subject to additional [fees](/merchant-of-record/fees) - Large or fast-growing business? [Reach out](mailto:support@polar.sh) to us for volume discounts Focus on building your core product vs. common product benefits. Use our built-in automations for: * [File Downloads](/features/benefits/file-downloads) * [License Keys](/features/benefits/license-keys) * [GitHub Repo Access](/features/benefits/github-access) * [Discord Access](/features/benefits/github-access) Offer a powerful and custom branded checkout in minutes. * [Checkout Links](/features/checkout/links) * [Embedded Checkout](/features/checkout/embed) on your site * [Checkout API](/features/checkout/embed) to programmatically create dynamic sessions * Our product, code and docs are available on [GitHub](https://github.com/polarsource/polar) (Apache 2.0) * 36 contributors and growing (Contributions welcome) * Feature requests, feedback and issues - all in public * Self-hosting while possible is not supported or encouraged (no MoR benefits) ## Quickstart Head over to [polar.sh/signup](https://polar.sh/login) to create an account. [Sign up](https://polar.sh/signup) to Polar easily using GitHub, Google or email. Create an organization for your products, customers and checkouts. ## Using the Polar SDK Polar comes in the form of an SDK, and is designed to be used in your existing backend infrastructure. We currently have SDKs for: * [JavaScript/TypeScript](/integrate/sdk/typescript) * [Python](/integrate/sdk/python) * [Go](/integrate/sdk/golang) * [PHP](/integrate/sdk/php) Navigate to the relevant SDK page to learn more about the SDK and how to use it. In this particular guide, we'll focus on using the JavaScript/TypeScript SDK. ## Using a Polar Adapter Polar's Adapters are small framework-specific utilities which makes it super-easy to integrate Polar. They can be used to: * Create Checkout Sessions * Create a Customer Portal * Listen for Polar Webhooks (e.g. for events like a customer being created, a subscription being updated, etc.) We currently have adapters for the most popular frameworks: * [Next.js](/integrate/sdk/adapters/nextjs) * [BetterAuth](/integrate/sdk/adapters/better-auth) * [Express](/integrate/sdk/adapters/express) * [Fastify](/integrate/sdk/adapters/fastify) * [Hono](/integrate/sdk/adapters/hono) * [Deno](/integrate/sdk/adapters/deno) * [Nuxt](/integrate/sdk/adapters/nuxt) * [Remix](/integrate/sdk/adapters/remix) * [SvelteKit](/integrate/sdk/adapters/sveltekit) * [Tanstack Start](/integrate/sdk/adapters/tanstack-start) * [Elysia](/integrate/sdk/adapters/elysia) * [Astro](/integrate/sdk/adapters/astro) * [Laravel](/integrate/sdk/adapters/laravel) # null Source: https://docs.polar.sh/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) * 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, and social proof. It's deceptive to consumers which is behaviour we do not tolerate. * Trademark violations * "Get rich" schemes or content * Gambling & betting services * Regulated services or products * Counterfeit goods * NFT & Crypto assets. * Cheating: Utilizing 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 advice, e.g content or services related to tax guidance, wealth management, investment strategies etc. * IPTV services * Virus & Spyware * 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 * 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. ## 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 * 100% AI generated services, websites and products ## FAQ **Why are AI services, products and websites restricted?** We love LLMs and the future opportunities they represent for developers & entrepreneurs. However, some use them poorly and with less valid intents. Generating a quick & basic service or product and a website with fake testimonials only in hopes to make a quick buck. Resulting in low quality, trust and high risk of customer support, refunds and chargebacks. So using AI to build a better product? Zero problem. Using AI to quickly put something together to sell? No thank you. **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://docs.polar.sh/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. ### First payout review (24-48h) You will need to go through our main review ahead of the initial payout. Weโ€™ll reach out over email within 24h (often faster) with: 1. A quick survey about your business, products and intended use case with Polar 2. Identity verification (KYC) using passport (or drivers license) and 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 hours 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 * Refund- & Chargeback 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. # Fees Source: https://docs.polar.sh/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 Polar does not add any additional fees to payouts. All fees below are from the available payout providers and without any premium. 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.* **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. **Open Collective (Deprecated for new users)** * 10% on amount transferred ## 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://docs.polar.sh/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 **tl;dr 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 **tl;dr 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. # null Source: https://docs.polar.sh/merchant-of-record/supported-countries ### Payments & Merchant of Record We support payments globally except from countries with US sanctions. As your Merchant of Record (MoR) we take on the liability for international sales taxes - [read more here](/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 # Migrate to Polar Source: https://docs.polar.sh/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 npx polar-migrate ``` ### 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) ## Paddle, Stripe, Gumroad or others? [Reach out to us](mailto:support@polar.sh) and we'd be happy to help. # Support Source: https://docs.polar.sh/support We love to support customers, quickly. Reach out anytime. Thank you for reaching out and helping us make Polar & our docs better. We greatly appreciate it! ## GitHub * [Ask a question](https://github.com/orgs/polarsource/discussions/categories/q-a) * [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. ## Email You can reach us at [support@polar.sh](mailto:support@polar.sh)