> ## Documentation Index > Fetch the complete documentation index at: https://polar.sh/docs/llms.txt > Use this file to discover all available pages before exploring further. # Next.js > Payments and Checkouts made dead simple with Next.js ## Examples * [With Next.js](https://github.com/polarsource/examples/tree/main/with-nextjs) * [With Next.js and Upstash QStash](https://github.com/polarsource/examples/tree/main/with-nextjs-qstash-schedule-downgrades) * [With Next.js, Better Auth and Cloudflare Workers](https://github.com/polarsource/examples/tree/main/with-nextjs-better-auth-cloudflare-workers) ## Installation Install the required Polar packages using the following command: ```bash Terminal theme={null} npm install @polar-sh/nextjs ``` ```bash Terminal theme={null} yarn add @polar-sh/nextjs ``` ```bash Terminal theme={null} pnpm add @polar-sh/nextjs ``` ```bash Terminal theme={null} bun add @polar-sh/nextjs ``` The adapter works with App Router route handlers on Next.js 15 and 16. ## Checkout Create a Checkout handler which takes care of redirections. ```typescript icon="square-js" checkout/route.ts theme={null} import { Checkout } from "@polar-sh/nextjs"; export const GET = Checkout({ accessToken: process.env.POLAR_ACCESS_TOKEN!, successUrl: process.env.SUCCESS_URL, returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Checkout environment: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise theme: "dark", // Enforces the theme - System-preferred theme will be set if left omitted }); ``` `successUrl` and `returnUrl` must be absolute URLs. The handler appends `checkout_id={CHECKOUT_ID}` to `successUrl`; pass `includeCheckoutId: false` to turn this off. ### Query Params Pass query params to this route. * products `?products=123` - Repeat the parameter for multiple products: `?products=123&products=456` * customer\_id (optional) `?products=123&customer_id=xxx` * external\_customer\_id (optional) `?products=123&external_customer_id=xxx` * customer\_email (optional) `?products=123&customer_email=janedoe@gmail.com` * customer\_name (optional) `?products=123&customer_name=Jane` * customer\_billing\_address (optional) `URL-Encoded JSON string` * customer\_tax\_id (optional) `?products=123&customer_tax_id=xxx` * customer\_ip\_address (optional) `?products=123&customer_ip_address=xxx` * customer\_metadata (optional) `URL-Encoded JSON string` * allow\_discount\_codes (optional) `?products=123&allow_discount_codes=false` * discount\_id (optional) `?products=123&discount_id=xxx` * discount\_code (optional) `?products=123&discount_code=SAVE20` - Applied before redirecting. `discount_id` takes precedence when both are supplied. * seats (optional) `?products=123&seats=5` - Number of seats for seat-based products * metadata (optional) `URL-Encoded JSON string` The handler returns `400` when `products` is missing. ## Customer Portal Create a customer portal where your customer can view orders and subscriptions. ```typescript icon="square-js" portal/route.ts theme={null} import { CustomerPortal } from "@polar-sh/nextjs"; import type { NextRequest } from "next/server"; export const GET = CustomerPortal({ accessToken: process.env.POLAR_ACCESS_TOKEN!, getCustomerId: async (req: NextRequest) => "", // Function to resolve a Polar Customer ID returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Customer Portal environment: "sandbox", // Use sandbox if you're testing Polar - omit the parameter or pass 'production' otherwise }); ``` If you identify customers by your own user ID, pass `getExternalCustomerId` instead of `getCustomerId`. The handler returns `400` when the function resolves to an empty value. ## Webhooks A simple utility which verifies the signature of incoming webhook payloads with your webhook secret. ```typescript icon="square-js" api/webhook/polar/route.ts theme={null} import { Webhooks } from "@polar-sh/nextjs"; export const POST = Webhooks({ webhookSecret: process.env.POLAR_WEBHOOK_SECRET!, onPayload: async (payload) => { // Handle the payload // No need to return an acknowledge response }, }); ``` ### Payload Handlers The Webhook handler also supports granular handlers for easy integration. Every handler is an `async` function that receives the full webhook payload (`{ type, timestamp, data }`). Fields use the SDK's snake\_case names, for example `payload.data.customer_id`. * `onPayload` - Called for every incoming webhook event, in addition to the matching handler below * `onCheckoutCreated` - Triggered when a checkout is created * `onCheckoutExpired` - Triggered when a checkout expires * `onCheckoutUpdated` - Triggered when a checkout is updated * `onOrderCreated` - Triggered when an order is created * `onOrderUpdated` - Triggered when an order is updated * `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 * `onSubscriptionCycled` - Triggered when a subscription enters a new billing period * `onSubscriptionPastDue` - Triggered when a subscription payment fails and it becomes past due * `onSubscriptionPaused` - Triggered when a subscription is paused * `onSubscriptionResumed` - Triggered when a paused subscription is resumed * `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 * `onBenefitGrantCycled` - Triggered when a benefit grant renews with its subscription * `onBenefitGrantUpdated` - Triggered when a benefit grant is updated * `onBenefitGrantRevoked` - Triggered when a benefit grant is revoked * `onCustomerCreated` - Triggered when a customer is created * `onCustomerUpdated` - Triggered when a customer is updated * `onCustomerDeleted` - Triggered when a customer is deleted * `onCustomerStateChanged` - Triggered when a customer state changes * `onCustomerSeatAssigned` - Triggered when a seat is assigned to a customer * `onCustomerSeatClaimed` - Triggered when a customer claims an assigned seat * `onCustomerSeatRevoked` - Triggered when a seat is revoked from a customer * `onDiscountCreated` - Triggered when a discount is created * `onDiscountUpdated` - Triggered when a discount is updated * `onDiscountDeleted` - Triggered when a discount is deleted * `onMemberCreated` - Triggered when a member is added to a team customer * `onMemberUpdated` - Triggered when a member of a team customer is updated * `onMemberDeleted` - Triggered when a member is removed from a team customer The handler verifies the `webhook-id`, `webhook-timestamp` and `webhook-signature` headers against your webhook secret before calling any handler: * A missing or invalid signature returns `403`. * A malformed payload returns `400`. * A signed event type that the installed SDK doesn't know yet returns `200` and is ignored, so new event types don't cause retries. * If a handler throws, the error propagates and the request fails, so Polar retries the delivery. This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.