> ## Documentation Index
> Fetch the complete documentation index at: https://docs.creem.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Checkout API

> Create dynamic checkout sessions programmatically with full control over payment flow and tracking.

Checkout sessions give you programmatic control over the payment flow. Unlike static payment links, checkout sessions are generated dynamically, allowing you to:

* Pass custom tracking IDs for each payment
* Pre-fill customer information like email
* Set dynamic success URLs based on your app's context
* Apply discount codes programmatically
* Add metadata for internal tracking

## Prerequisites

Before creating checkout sessions, you'll need:

* **A Creem account** with an API key ([Get your key](https://creem.io/dashboard/developers))
* **At least one product** created in your dashboard. One-time products can be
  paid or free with a `0` price.

<Tip>
  Find your product ID by going to the [Products tab](https://creem.io/dashboard/products), clicking
  on a product, and selecting "Copy ID" from the options menu.
</Tip>

## Creating a Checkout Session

Choose the integration method that works best for your stack:

<Tabs>
  <Tab title="Next.js">
    The Next.js adapter provides a route handler and React component for seamless integration.

    ### Install the package

    <CodeGroup>
      ```bash npm theme={null}
      npm install @creem_io/nextjs
      ```

      ```bash yarn theme={null}
      yarn add @creem_io/nextjs
      ```

      ```bash pnpm theme={null}
      pnpm install @creem_io/nextjs
      ```

      ```bash bun theme={null}
      bun install @creem_io/nextjs
      ```
    </CodeGroup>

    ### Create the checkout route

    ```ts theme={null}
    // app/api/checkout/route.ts
    import { Checkout } from "@creem_io/nextjs";

    export const GET = Checkout({
      apiKey: process.env.CREEM_API_KEY!,
      testMode: process.env.NODE_ENV !== "production",
      defaultSuccessUrl: "/success",
    });
    ```

    ### Add a checkout button

    ```tsx theme={null}
    // app/page.tsx
    "use client"; // Optional: CreemCheckout also works in Server Components

    import { CreemCheckout } from "@creem_io/nextjs";

    export function CheckoutButton() {
      return (
        <CreemCheckout
          productId="prod_YOUR_PRODUCT_ID"
          referenceId="user_123" // Optional: Track this payment in your system
        >
          <button>Buy Now</button>
        </CreemCheckout>
      );
    }
    ```

    The `CreemCheckout` component automatically handles the checkout session creation and redirects the user to the payment page.

    <Card title="Next.js SDK Documentation" icon="react" href="/code/sdks/nextjs">
      Explore advanced features, server components, and webhook handling.
    </Card>
  </Tab>

  <Tab title="TypeScript SDK">
    The TypeScript SDK provides full type-safety and works with any JavaScript framework.

    ### Install the SDK

    <CodeGroup>
      ```bash npm theme={null}
      npm install creem
      ```

      ```bash yarn theme={null}
      yarn add creem
      ```

      ```bash pnpm theme={null}
      pnpm install creem
      ```

      ```bash bun theme={null}
      bun install creem
      ```
    </CodeGroup>

    ### Create a checkout session

    ```typescript theme={null}
    import { Creem } from "creem";

    const creem = new Creem({
      apiKey: process.env.CREEM_API_KEY!,
      server: process.env.NODE_ENV === "production" ? "prod" : "test",
    });

    // Create a checkout session
    const checkout = await creem.checkouts.create({
      productId: "prod_YOUR_PRODUCT_ID",
      requestId: "order_123", // Optional: Track this payment
      successUrl: "https://yoursite.com/success",
      customer: {
        email: "customer@example.com", // Optional: Pre-fill email
      },
    });

    // Redirect to the checkout URL
    console.log(checkout.checkoutUrl);
    // In the browser: window.location.href = checkout.checkoutUrl;
    ```

    <Card title="TypeScript SDK Documentation" icon="code" href="/code/sdks/typescript-core">
      View the full SDK API reference and advanced usage examples.
    </Card>
  </Tab>

  <Tab title="Better Auth">
    The Better Auth integration automatically syncs payments with your authenticated users.

    ### Install the plugin

    ```bash theme={null}
    npm install @creem_io/better-auth better-auth
    ```

    ### Configure Better Auth

    ```typescript theme={null}
    // auth.ts
    import { betterAuth } from "better-auth";
    import { creem } from "@creem_io/better-auth";

    export const auth = betterAuth({
      database: {
        // your database config
      },
      plugins: [
        creem({
          apiKey: process.env.CREEM_API_KEY!,
          testMode: process.env.NODE_ENV !== "production",
          defaultSuccessUrl: "/dashboard",
        }),
      ],
    });
    ```

    ### Client setup

    ```typescript theme={null}
    // lib/auth-client.ts
    import { createAuthClient } from "better-auth/react";
    import { creemClient } from "@creem_io/better-auth/client";

    export const authClient = createAuthClient({
      baseURL: process.env.NEXT_PUBLIC_APP_URL,
      plugins: [creemClient()],
    });
    ```

    ### Create a checkout

    ```typescript theme={null}
    "use client";

    import { authClient } from "@/lib/auth-client";

    export function CheckoutButton({ productId }: { productId: string }) {
      const handleCheckout = async () => {
        const { data, error } = await authClient.creem.createCheckout({
          productId,
          successUrl: "/dashboard",
        });

        if (data?.url) {
          window.location.href = data.url;
        }
      };

      return <button onClick={handleCheckout}>Subscribe Now</button>;
    }
    ```

    The Better Auth integration automatically tracks the authenticated user and syncs subscription status with your database.

    <Card title="Better Auth Integration" icon="lock" href="/code/sdks/better-auth">
      Learn about database persistence, access management, and webhook handling.
    </Card>
  </Tab>

  <Tab title="REST API">
    Use the REST API directly from any language or framework.

    ### Create a checkout session

    <Warning>
      If you're in test mode, use `https://test-api.creem.io` instead of `https://api.creem.io`. Learn
      more about [Test Mode](/getting-started/test-mode).
    </Warning>

    ```bash theme={null}
    curl -X POST https://api.creem.io/v1/checkouts \
      -H "x-api-key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "product_id": "prod_YOUR_PRODUCT_ID",
        "request_id": "order_123",
        "success_url": "https://yoursite.com/success"
      }'
    ```

    ### Response

    ```json theme={null}
    {
      "id": "ch_1QyIQDw9cbFWdA1ry5Qc6I",
      "checkout_url": "https://checkout.creem.io/ch_1QyIQDw9cbFWdA1ry5Qc6I",
      "product_id": "prod_YOUR_PRODUCT_ID",
      "status": "pending"
    }
    ```

    Redirect your user to the `checkout_url` to complete the payment.

    <Card title="API Reference" icon="book" href="/api-reference/endpoint/create-checkout">
      View the complete endpoint documentation with all available parameters.
    </Card>
  </Tab>
</Tabs>

## Handling Successful Payments

After a successful payment, users are redirected to your `success_url` with payment details as query parameters:

```
https://yoursite.com/success?checkout_id=ch_xxx&order_id=ord_xxx&customer_id=cust_xxx&product_id=prod_xxx
```

| Query parameter   | Description                                                                    |
| ----------------- | ------------------------------------------------------------------------------ |
| `checkout_id`     | The ID of the checkout session created for this payment.                       |
| `order_id`        | The ID of the order created after successful payment.                          |
| `customer_id`     | The customer ID, based on the email that executed the successful payment.      |
| `subscription_id` | The subscription ID of the product.                                            |
| `product_id`      | The product ID that the payment is related to.                                 |
| `request_id`      | Optional. The request/reference ID you provided when creating this checkout.   |
| `signature`       | All previous parameters signed by creem using your API-key, verifiable by you. |

<Warning>
  For production applications, we recommend using [Webhooks](/code/webhooks) to handle payment
  events.
</Warning>

### Verifying Redirect Signatures

The `signature` query parameter allows you to verify that the redirect came from Creem. This prevents malicious users from spoofing successful payment redirects.

<Note>
  The signature is a SHA-256 hex digest of the redirect parameters joined with `|`, with `salt=   {apiKey}` appended at the end. **Parameters appear in the order they arrive in the redirect URL**
  (not alphabetically sorted), and **null or empty values are excluded** — only include parameters
  that have actual values.
</Note>

The canonical string is built as:

```text theme={null}
key1=value1|key2=value2|...|keyN=valueN|salt={apiKey}
```

Then hashed with SHA-256 and hex-encoded to produce the `signature` value.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import * as crypto from "crypto";

    interface RedirectParams {
      request_id?: string | null;
      checkout_id: string;
      order_id: string | null;
      customer_id: string | null;
      subscription_id: string | null;
      product_id: string;
      signature: string;
    }

    function verifyRedirectSignature(params: RedirectParams, apiKey: string): boolean {
      const { signature, ...rest } = params;

      // Keep insertion order; exclude null/undefined/empty values.
      const data = Object.entries(rest)
        .filter(([, value]) => value !== null && value !== undefined && value !== "")
        .map(([key, value]) => `${key}=${value}`)
        .concat(`salt=${apiKey}`)
        .join("|");

      const expectedSignature = crypto.createHash("sha256").update(data).digest("hex");

      return signature === expectedSignature;
    }

    // Usage in your success page
    export async function GET(request: Request) {
      const url = new URL(request.url);

      // Read fields in the order they appear in the redirect URL.
      const params: RedirectParams = {
        request_id: url.searchParams.get("request_id"),
        checkout_id: url.searchParams.get("checkout_id")!,
        order_id: url.searchParams.get("order_id"),
        customer_id: url.searchParams.get("customer_id"),
        subscription_id: url.searchParams.get("subscription_id"),
        product_id: url.searchParams.get("product_id")!,
        signature: url.searchParams.get("signature")!,
      };

      const isValid = verifyRedirectSignature(params, process.env.CREEM_API_KEY!);

      if (!isValid) {
        return new Response("Invalid signature", { status: 401 });
      }

      // Proceed with success page...
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import hashlib
    import hmac
    from urllib.parse import urlparse, parse_qsl

    def verify_redirect_signature(query_string: str, api_key: str) -> bool:
        """
        Verify the redirect signature from Creem checkout.

        Args:
            query_string: The raw query string from the redirect URL.
            api_key: Your Creem API key.

        Returns:
            True if signature is valid, False otherwise.
        """
        # parse_qsl preserves the order parameters appear in the URL.
        pairs = parse_qsl(query_string, keep_blank_values=False)

        signature = ''
        parts = []
        for key, value in pairs:
            if key == 'signature':
                signature = value
                continue
            if value in (None, '', 'null'):
                continue
            parts.append(f'{key}={value}')

        parts.append(f'salt={api_key}')
        data = '|'.join(parts)

        expected_signature = hashlib.sha256(data.encode()).hexdigest()
        return hmac.compare_digest(signature, expected_signature)

    # Usage in Flask
    @app.route('/success')
    def success():
        if not verify_redirect_signature(request.query_string.decode(), os.environ['CREEM_API_KEY']):
            return 'Invalid signature', 401

        # Proceed with success page...
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    package main

    import (
        "crypto/sha256"
        "crypto/subtle"
        "encoding/hex"
        "net/url"
        "strings"
    )

    // verifyRedirectSignature checks the redirect signature using the raw query
    // string so parameter order matches what Creem signed.
    func verifyRedirectSignature(rawQuery, apiKey string) bool {
        var signature string
        var parts []string

        for _, pair := range strings.Split(rawQuery, "&") {
            if pair == "" {
                continue
            }
            key, value, _ := strings.Cut(pair, "=")
            decodedKey, err := url.QueryUnescape(key)
            if err != nil {
                return false
            }
            decodedValue, err := url.QueryUnescape(value)
            if err != nil {
                return false
            }

            if decodedKey == "signature" {
                signature = decodedValue
                continue
            }
            if decodedValue == "" || decodedValue == "null" {
                continue
            }
            parts = append(parts, decodedKey+"="+decodedValue)
        }

        parts = append(parts, "salt="+apiKey)
        data := strings.Join(parts, "|")

        sum := sha256.Sum256([]byte(data))
        expected := hex.EncodeToString(sum[:])

        return subtle.ConstantTimeCompare([]byte(signature), []byte(expected)) == 1
    }
    ```
  </Tab>
</Tabs>

<Warning>
  **Important:** Parameters with `null` or empty values (like `order_id` for subscription-only
  checkouts, or `subscription_id` for one-time payments) must be **excluded** from the signed
  string. Including them as `"order_id=null"` will cause verification to fail.
</Warning>

***

## Advanced Features

### Metadata

Add custom metadata to track additional information with each payment. Metadata is included in webhook events and can be retrieved later.

<Tabs>
  <Tab title="Next.js">
    ```tsx theme={null}
    <CreemCheckout
      productId="prod_YOUR_PRODUCT_ID"
      referenceId="user_123"
      metadata={{
        userId: "internal_user_id",
        planType: "premium",
        source: "marketing_campaign",
      }}
    >
      <button>Subscribe</button>
    </CreemCheckout>
    ```
  </Tab>

  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    const checkout = await creem.checkouts.create({
      productId: "prod_YOUR_PRODUCT_ID",
      requestId: "order_123",
      metadata: {
        userId: "internal_user_id",
        planType: "premium",
        source: "marketing_campaign",
      },
    });
    ```
  </Tab>

  <Tab title="Better Auth">
    ```typescript theme={null}
    const { data } = await authClient.creem.createCheckout({
      productId: "prod_YOUR_PRODUCT_ID",
      metadata: {
        planType: "premium",
        source: "marketing_campaign",
      },
    });
    ```
  </Tab>

  <Tab title="REST API">
    ```bash theme={null}
    curl -X POST https://api.creem.io/v1/checkouts \
      -H "x-api-key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "product_id": "prod_YOUR_PRODUCT_ID",
        "metadata": {
          "userId": "internal_user_id",
          "planType": "premium",
          "source": "marketing_campaign"
        }
      }'
    ```
  </Tab>
</Tabs>

<Tip>
  Metadata is especially useful for tracking internal IDs, campaign sources, or any custom
  information you need to associate with a payment.
</Tip>

### Custom Success URL

Override the default success URL on a per-checkout basis. This is useful for directing users to specific pages after payment based on context.

<Tabs>
  <Tab title="Next.js">
    ```tsx theme={null}
    <CreemCheckout
      productId="prod_YOUR_PRODUCT_ID"
      successUrl="/account/welcome" // Overrides defaultSuccessUrl
    >
      <button>Get Started</button>
    </CreemCheckout>
    ```
  </Tab>

  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    const checkout = await creem.checkouts.create({
      productId: "prod_YOUR_PRODUCT_ID",
      successUrl: "https://yoursite.com/account/welcome",
    });
    ```
  </Tab>

  <Tab title="Better Auth">
    ```typescript theme={null}
    const { data } = await authClient.creem.createCheckout({
      productId: "prod_YOUR_PRODUCT_ID",
      successUrl: "/account/welcome",
    });
    ```
  </Tab>

  <Tab title="REST API">
    ```bash theme={null}
    curl -X POST https://api.creem.io/v1/checkouts \
      -H "x-api-key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "product_id": "prod_YOUR_PRODUCT_ID",
        "success_url": "https://yoursite.com/account/welcome"
      }'
    ```
  </Tab>
</Tabs>

### Pre-fill Customer Email

Lock the customer email at checkout to ensure users complete payment with the email they registered with on your platform.

<Tabs>
  <Tab title="Next.js">
    ```tsx theme={null}
    <CreemCheckout productId="prod_YOUR_PRODUCT_ID" customer={{ email: "user@example.com" }}>
      <button>Complete Purchase</button>
    </CreemCheckout>
    ```
  </Tab>

  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    const checkout = await creem.checkouts.create({
      productId: "prod_YOUR_PRODUCT_ID",
      customer: {
        email: "user@example.com",
      },
    });
    ```
  </Tab>

  <Tab title="Better Auth">
    ```typescript theme={null}
    // Email is automatically set from the authenticated user
    const { data } = await authClient.creem.createCheckout({
      productId: "prod_YOUR_PRODUCT_ID",
      customer: {
        email: "user@example.com", // Optional: if you want to overwrite the session
      },
    });
    ```

    <Note>The Better Auth integration automatically uses the authenticated user's email.</Note>
  </Tab>

  <Tab title="REST API">
    ```bash theme={null}
    curl -X POST https://api.creem.io/v1/checkouts \
      -H "x-api-key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "product_id": "prod_YOUR_PRODUCT_ID",
        "customer": {
          "email": "user@example.com"
        }
      }'
    ```
  </Tab>
</Tabs>

### Apply Discount Codes

Apply discount codes programmatically to pre-fill them at checkout.

<Tabs>
  <Tab title="Next.js">
    ```tsx theme={null}
    <CreemCheckout productId="prod_YOUR_PRODUCT_ID" discountCode="LAUNCH50">
      <button>Claim Offer</button>
    </CreemCheckout>
    ```
  </Tab>

  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    const checkout = await creem.checkouts.create({
      productId: "prod_YOUR_PRODUCT_ID",
      discountCode: "LAUNCH50",
    });
    ```
  </Tab>

  <Tab title="Better Auth">
    ```typescript theme={null}
    const { data } = await authClient.creem.createCheckout({
      productId: "prod_YOUR_PRODUCT_ID",
      discountCode: "LAUNCH50",
    });
    ```
  </Tab>

  <Tab title="REST API">
    ```bash theme={null}
    curl -X POST https://api.creem.io/v1/checkouts \
      -H "x-api-key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "product_id": "prod_YOUR_PRODUCT_ID",
        "discount_code": "LAUNCH50"
      }'
    ```
  </Tab>
</Tabs>

<Card title="Discount Codes" icon="tag" href="/features/discounts">
  Learn how to create and manage discount codes in your dashboard.
</Card>

### Seat-Based Billing

Charge for multiple units or seats by specifying the `units` parameter. The total price will be calculated as `base_price × units`.

<Tabs>
  <Tab title="Next.js">
    ```tsx theme={null}
    <CreemCheckout
      productId="prod_YOUR_PRODUCT_ID"
      units={seatCount} // Charge for 5 seats
    >
      <button>Add {seatCount} Seats</button>
    </CreemCheckout>
    ```
  </Tab>

  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    const checkout = await creem.checkouts.create({
      productId: "prod_YOUR_PRODUCT_ID",
      units: 5, // Charge for 5 seats
    });
    ```
  </Tab>

  <Tab title="Better Auth">
    ```typescript theme={null}
    const { data } = await authClient.creem.createCheckout({
      productId: "prod_YOUR_PRODUCT_ID",
      units: 5, // Charge for 5 seats
    });
    ```
  </Tab>

  <Tab title="REST API">
    ```bash theme={null}
    curl -X POST https://api.creem.io/v1/checkouts \
      -H "x-api-key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "product_id": "prod_YOUR_PRODUCT_ID",
        "units": 5
      }'
    ```
  </Tab>
</Tabs>

<Card title="Seat-Based Billing" icon="users" href="/features/seat-based-billing">
  Learn more about implementing and managing seat-based pricing models.
</Card>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Checkout Customization" icon="palette" href="/features/checkout/checkout-customization">
    Brand your checkout with custom colors, logos, and themes
  </Card>

  <Card title="Checkout Custom Fields" icon="input-text" href="/features/checkout/checkout-custom-fields">
    Collect additional information from customers during checkout
  </Card>

  <Card title="Subscriptions" icon="repeat" href="/features/subscriptions/introduction">
    Learn how to manage recurring billing and subscriptions
  </Card>

  <Card title="Revenue Splits" icon="split" href="/features/split-payments">
    Split revenue between multiple parties automatically
  </Card>
</CardGroup>
