Skip to main content
Checkout sessions provide a secure, hosted payment flow for your products. Create one-time or subscription checkout experiences that handle payment processing, tax calculation, and customer management.

Overview

Checkout sessions are temporary payment flows that:
  • Expire after 30 days (configurable)
  • Support multiple products and prices
  • Handle tax calculation automatically
  • Process payments via Stripe
  • Create orders and subscriptions upon success
  • Grant benefits automatically

Creating Checkout Sessions

Create checkout sessions programmatically via API or from checkout links.
const checkout = await polar.checkouts.custom.create({
  products: [productId1, productId2],
  customer_email: "[email protected]",
  success_url: "https://example.com/success?checkout_id={CHECKOUT_ID}",
  metadata: {
    user_id: "user_123"
  }
});

Checkout Configuration

Products & Pricing

Configure which products customers can purchase:
{
  products: [productId1, productId2],  // Multiple products
  currency: "usd",                     // Optional: force currency
  amount: 5000,                        // Custom amount (for custom pricing only)
}
When multiple products are provided, customers can switch between them in the checkout UI.

Customer Pre-fill

Pre-populate customer information for faster checkout:
{
  customer_id: existingCustomerId,           // Link to existing customer
  external_customer_id: "user_123",          // Or use your external ID
  customer_email: "[email protected]",
  customer_name: "Jane Doe",
  customer_billing_address: {
    line1: "123 Main St",
    city: "San Francisco",
    state: "CA",
    postal_code: "94102",
    country: "US"
  },
  customer_tax_id: "GB123456789",            // VAT/Tax ID
  customer_metadata: {                       // Copied to created customer
    source: "website",
    campaign: "summer_sale"
  }
}

Billing Requirements

By default, customers only need to provide their country for tax calculation.
{ require_billing_address: false }

Seat-Based Configuration

For seat-based products, control the seat selection:
{
  seats: 10,           // Pre-select 10 seats
  min_seats: 5,        // Minimum selectable
  max_seats: 50        // Maximum selectable
}

Trials

Control trial availability at checkout:
{
  allow_trial: false,           // Disable trial even if product has one
  trial_interval: "day",        // Override product trial
  trial_interval_count: 14      // 14-day trial
}
Customers can only redeem one trial per product. Attempting to use another trial will fail.

Discounts

Apply discounts or allow discount codes:
{
  discount_id: discountId,       // Pre-apply specific discount
  allow_discount_codes: true     // Allow customers to enter codes
}

Redirects

{
  success_url: "https://example.com/success?checkout_id={CHECKOUT_ID}",
  return_url: "https://example.com",  // Back button URL
  embed_origin: "https://example.com" // For iframe embedding
}
Use {CHECKOUT_ID} in success_url to retrieve the checkout ID after success.

Checkout Flow

1

Create Session

Create a checkout session via API with product and customer details.
2

Customer Entry

Customer lands on checkout page and sees product details, pricing, and benefits.
3

Fill Details

Customer provides email, billing address, and payment information.
4

Tax Calculation

Taxes are calculated based on customer location and product type.
5

Payment Processing

Payment is processed via Stripe. 3DS authentication if required.
6

Confirmation

  • Order/subscription created
  • Benefits granted
  • Confirmation email sent
  • Redirect to success URL

Checkout Status

Checkouts progress through these statuses:
Initial state. Customer can complete checkout.

Updating Checkout Sessions

Update open checkout sessions to modify configuration:
await polar.checkouts.custom.update(checkoutId, {
  customer_email: "[email protected]",
  customer_billing_address: { /* ... */ },
  amount: 7500  // Update custom amount
});
Only open checkouts can be updated. Confirmed, succeeded, or failed checkouts are immutable.

Client-Side Access

Access checkout sessions from your frontend using the client secret:
// Get checkout details
const checkout = await polar.checkouts.custom.client.get(clientSecret);

// Update customer information
await polar.checkouts.custom.client.update(clientSecret, {
  customer_email: "[email protected]"
});

// Confirm payment (after Stripe payment intent)
await polar.checkouts.custom.client.confirm(clientSecret, {
  confirmation_token_id: stripeConfirmationToken
});

Embedding Checkout

Embed checkout in an iframe for seamless integration:
1

Set embed origin

const checkout = await polar.checkouts.custom.create({
  product_id: productId,
  embed_origin: "https://yourdomain.com"
});
2

Create iframe

<iframe
  src="https://polar.sh/checkout/{client_secret}"
  width="100%"
  height="800px"
  frameborder="0"
></iframe>
3

Listen for events

window.addEventListener('message', (event) => {
  if (event.origin !== 'https://polar.sh') return;
  
  if (event.data.type === 'checkout.succeeded') {
    console.log('Checkout completed!', event.data.checkout_id);
  }
});

Custom Fields

Collect additional data via custom fields attached to products:
// Custom field values are submitted during checkout
// and stored on the resulting order/subscription
{
  custom_field_data: {
    [fieldId]: "Customer's response"
  }
}

Subscription Upgrades

Upgrade free subscriptions to paid tiers via checkout:
{
  subscription_id: freeSubscriptionId,
  product_id: paidProductId
}
Only free subscriptions can be upgraded via checkout. Paid subscription changes use the subscriptions API.

Metadata

Store custom data on checkout sessions:
{
  metadata: {
    order_source: "website",
    affiliate_id: "aff_123",
    campaign: "summer_2024"
  }
}
Metadata is:
  • Copied to created orders and subscriptions
  • Included in webhooks
  • Searchable via API

Event Streaming

Subscribe to real-time checkout updates via Server-Sent Events:
const eventSource = new EventSource(
  `https://api.polar.sh/v1/checkouts/custom/client/${clientSecret}/stream`
);

eventSource.addEventListener('checkout.updated', (event) => {
  const checkout = JSON.parse(event.data);
  console.log('Status:', checkout.status);
});

Payment Methods

Polar uses Stripe to process payments and supports:
  • Credit/debit cards (Visa, Mastercard, Amex, etc.)
  • Apple Pay & Google Pay
  • SEPA Direct Debit (EUR)
  • ACH Direct Debit (USD)
  • Link (Stripe’s one-click checkout)
Payment methods are automatically shown based on customer location and currency.

Tax Handling

Taxes are automatically calculated based on:
  • Customer billing address
  • Customer IP address (fallback)
  • Product tax category
  • Merchant of record jurisdiction
See Merchant of Record for tax details.

Error Handling

Status changes to failed. Customer can retry with a different payment method.
Returns 403 if customer already has an active subscription to the product.
Returns 410. Create a new checkout session.
Returns 403 if customer attempts to use a trial they’ve already redeemed.
Returns 403 if the organization isn’t configured to accept payments.

Best Practices

Pre-fill email and billing information when possible to reduce friction and increase conversion.
Store tracking information (campaign IDs, affiliate codes, etc.) in metadata for attribution.
Always provide a success_url to redirect customers after payment. Include {CHECKOUT_ID} to retrieve details.
Listen for checkout.succeeded webhooks rather than relying solely on redirects.
Use sandbox mode to test the full checkout flow before going live.

API Reference

Create Checkout

Create a new checkout session

Get Checkout

Retrieve checkout details

Update Checkout

Modify open checkout session

Confirm Checkout

Process payment and complete

Build docs developers (and LLMs) love