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

# Checkout

> Learn how to initiate and manage checkout sessions via the Chariow API

The checkout API allows you to programmatically create purchase sessions for your customers. This is useful for custom storefronts, integrations, or automated sales flows.

<Warning>
  **Unsupported product types** — The following cannot be used to initiate a checkout through the API:

  * **Service** products
  * **Coaching** products
  * Products with **pay-what-you-want** pricing

  For these, redirect customers to your [Chariow storefront](https://chariow.com) or use the **Snap Widget** embed on your website.
</Warning>

<Info>
  All sales initiated via the checkout API will have their **Channel** set to **"API"** on your store dashboard. This helps you identify and track sales originating from your API integrations separately from other channels like your storefront or Snap Widget.
</Info>

## Repeat Purchases

The ability to purchase a product multiple times depends on the product type:

| Product Type     | Repeat Purchase | Behaviour                                                                                                 |
| ---------------- | --------------- | --------------------------------------------------------------------------------------------------------- |
| **License**      | Always allowed  | Customers can purchase license products multiple times. Each purchase generates a new unique license key. |
| **Downloadable** | Blocked         | Returns `already_purchased` if customer has an active access grant.                                       |
| **Course**       | Blocked         | Returns `already_purchased` if customer has an active access grant.                                       |
| **Bundle**       | Blocked         | Returns `already_purchased` if customer has an active access grant.                                       |

<Tip>
  For blocked product types, if a customer's access has been **revoked** (e.g., after a refund), they will be able to purchase the product again. The system checks for **active** access grants only.
</Tip>

## Checkout Flow Overview

The Chariow checkout API handles the complete purchase flow from initiation to completion:

<Note>
  The product must be **published** before initiating a checkout. Unpublished products will return a 404 error.
</Note>

<Steps>
  <Step title="Initiate Checkout">
    Call the `/checkout` endpoint with product ID and customer details
  </Step>

  <Step title="Handle Response">
    Check the `step` field in the response:

    * **payment**: Redirect customer to `checkout_url` for payment
    * **completed**: Sale completed immediately (free products)
    * **already\_purchased**: Customer already owns this product
  </Step>

  <Step title="Process Payment">
    Customer completes payment on the secure Chariow payment page
  </Step>

  <Step title="Receive Webhook">
    Get notified of sale status changes via webhooks (recommended)
  </Step>

  <Step title="Deliver Product">
    Customer receives automatic access to files, licenses, courses, etc.
  </Step>
</Steps>

## Product Types Supported

The checkout API supports the following Chariow product types:

* **Downloadable Products**: Digital files (PDFs, software, media)
* **Courses**: Educational content with lessons and chapters
* **Licenses**: Software license keys with activation management
* **Bundles**: Collections of multiple products

<Note>
  **Service** and **Coaching** product types are not supported via the Public API. For these products, redirect customers to your Chariow store or use the Snap Widget. Products using pay-what-you-want pricing are also not supported.
</Note>

## Initiating a Checkout

Create a new checkout session:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.chariow.com/v1/checkout" \
    -H "Authorization: Bearer sk_live_your_api_key" \
    -H "Content-Type: application/json" \
    -d '{
      "product_id": "prd_abc123",
      "email": "customer@example.com",
      "first_name": "John",
      "last_name": "Doe",
      "phone": {
        "number": "1234567890",
        "country_code": "US"
      },
      "discount_code": "SAVE20"
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.chariow.com/v1/checkout', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sk_live_your_api_key',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      product_id: 'prd_abc123',
      email: 'customer@example.com',
      first_name: 'John',
      last_name: 'Doe',
      phone: {
        number: '1234567890',
        country_code: 'US'
      },
      discount_code: 'SAVE20'
    })
  });

  const data = await response.json();
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://api.chariow.com/v1/checkout',
      headers={
          'Authorization': 'Bearer sk_live_your_api_key',
          'Content-Type': 'application/json'
      },
      json={
          'product_id': 'prd_abc123',
          'email': 'customer@example.com',
          'first_name': 'John',
          'last_name': 'Doe',
          'phone': {
              'number': '1234567890',
              'country_code': 'US'
          },
          'discount_code': 'SAVE20'
      }
  )
  ```
</CodeGroup>

### Request Parameters

| Parameter            | Type   | Required | Description                                                                                                              |
| -------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------ |
| `product_id`         | string | Yes      | Product public ID or slug (e.g., `prd_abc123xyz` or `premium-course`)                                                    |
| `email`              | string | Yes      | Customer email address (max 255 characters)                                                                              |
| `first_name`         | string | Yes      | Customer first name (max 50 characters)                                                                                  |
| `last_name`          | string | Yes      | Customer last name (max 50 characters)                                                                                   |
| `phone.number`       | string | Yes      | Phone number (numeric only)                                                                                              |
| `phone.country_code` | string | Yes      | ISO country code (e.g., "US", "FR", "GB")                                                                                |
| `discount_code`      | string | No       | Discount code to apply (max 100 characters)                                                                              |
| `campaign_id`        | string | No       | Campaign public ID or tracking code                                                                                      |
| `custom_fields`      | object | No       | Custom field values (key-value pairs)                                                                                    |
| `payment_currency`   | string | No       | Currency code (ISO 4217, e.g., "USD", "EUR")                                                                             |
| `redirect_url`       | string | No       | Custom redirect URL after payment completion (max 2048 characters)                                                       |
| `custom_metadata`    | object | No       | Custom key-value metadata to store with the sale (max 10 keys, 255 chars per value). Included in Pulse webhook payloads. |
| `customer_ip`        | string | No       | The buyer's IP address, IPv4 or IPv6 (e.g., "203.0.113.42"). See [Buyer IP address](#buyer-ip-address).                  |

### Shipping Address Fields

When the product has "Require shipping address" enabled, you must include shipping address fields in your checkout request:

| Parameter | Type   | Required    | Description                                         |
| --------- | ------ | ----------- | --------------------------------------------------- |
| `address` | string | Conditional | Street address for shipping (max 255 characters)    |
| `city`    | string | Conditional | City for shipping (max 100 characters)              |
| `state`   | string | Conditional | State or region for shipping (max 100 characters)   |
| `country` | string | Conditional | Country code (ISO 3166-1 alpha-2, e.g., "US", "FR") |
| `zip`     | string | Conditional | Postal/ZIP code for shipping (max 20 characters)    |

<Info>
  These fields are **required** only when the product has shipping enabled. If shipping is not required, these fields are ignored.
</Info>

#### Example with Shipping Address

```json theme={null}
{
  "product_id": "prd_abc123xyz",
  "email": "customer@example.com",
  "first_name": "John",
  "last_name": "Doe",
  "phone": {
    "number": "1234567890",
    "country_code": "US"
  },
  "address": "123 Main Street",
  "city": "New York",
  "state": "NY",
  "country": "US",
  "zip": "10001"
}
```

## Checkout Response States

The checkout response includes a `step` field indicating the current state:

### Awaiting Payment

For paid products, you'll receive a payment URL:

```json theme={null}
{
  "data": {
    "step": "payment",
    "message": null,
    "purchase": {
      "id": "sal_xyz789",
      "status": "payment",
      "amount": {
        "value": 79.20,
        "formatted": "$79.20",
        "short": "79",
        "currency": "USD"
      }
    },
    "payment": {
      "checkout_url": "https://payment.chariow.com/checkout?token=abc123",
      "transaction_id": "txn_def456"
    }
  }
}
```

<Info>
  Redirect the customer to `checkout_url` to complete their payment.
</Info>

### Completed (Free Products)

For free products, the sale completes immediately:

```json theme={null}
{
  "data": {
    "step": "completed",
    "message": null,
    "purchase": {
      "id": "sal_xyz789",
      "status": "completed"
    },
    "payment": {
      "checkout_url": null,
      "transaction_id": null
    }
  }
}
```

### Already Purchased

If the customer already owns the product:

```json theme={null}
{
  "data": {
    "step": "already_purchased",
    "message": "You have already purchased this product",
    "purchase": null,
    "payment": null
  }
}
```

## Custom Redirect URLs

You can specify a custom redirect URL to send customers to your own thank-you page after payment completion:

```bash theme={null}
curl -X POST "https://api.chariow.com/v1/checkout" \
  -H "Authorization: Bearer sk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": "prd_abc123xyz",
    "email": "customer@example.com",
    "first_name": "John",
    "last_name": "Doe",
    "phone": {
      "number": "1234567890",
      "country_code": "US"
    },
    "redirect_url": "https://yoursite.com/thank-you?sale={sale_id}"
  }'
```

<Info>
  The redirect URL must be a valid active URL (max 2048 characters). When not provided, customers will be redirected to the default Chariow post-purchase page.
</Info>

## Multi-Currency Support

Specify the payment currency to charge customers in a different currency from your store's default:

```json theme={null}
{
  "product_id": "prd_abc123xyz",
  "email": "customer@example.com",
  "first_name": "John",
  "last_name": "Doe",
  "phone": {
    "number": "1234567890",
    "country_code": "US"
  },
  "payment_currency": "EUR"
}
```

The response will include exchange rate information when currency conversion is applied.

## Applying Discount Codes

Pass a discount code to apply savings:

```json theme={null}
{
  "product_id": "prd_abc123xyz",
  "email": "customer@example.com",
  "first_name": "John",
  "last_name": "Doe",
  "phone": {
    "number": "1234567890",
    "country_code": "US"
  },
  "discount_code": "SAVE20"
}
```

The response will show the discounted amount:

```json theme={null}
{
  "data": {
    "purchase": {
      "original_amount": {
        "value": 99,
        "formatted": "$99.00",
        "short": "99",
        "currency": "USD"
      },
      "amount": {
        "value": 79.20,
        "formatted": "$79.20",
        "short": "79",
        "currency": "USD"
      },
      "discount_amount": {
        "value": 19.80,
        "formatted": "$19.80",
        "short": "20",
        "currency": "USD"
      },
      "discount": {
        "id": "dis_xyz789",
        "code": "SAVE20",
        "type": "percentage",
        "value": 20
      }
    }
  }
}
```

## Custom Fields

If your product has custom fields configured, you can collect and validate them during checkout:

```json theme={null}
{
  "product_id": "prd_abc123xyz",
  "email": "customer@example.com",
  "first_name": "John",
  "last_name": "Doe",
  "phone": {
    "number": "1234567890",
    "country_code": "US"
  },
  "custom_fields": {
    "company_name": "Acme Corp",
    "job_title": "Developer",
    "team_size": "10-50"
  }
}
```

<Info>
  Custom fields must match the product's configured custom field definitions. Invalid or missing required custom fields will result in validation errors.
</Info>

## Campaign Tracking

Track the source of sales by including a campaign ID:

```json theme={null}
{
  "product_id": "prd_abc123xyz",
  "email": "customer@example.com",
  "first_name": "John",
  "last_name": "Doe",
  "phone": {
    "number": "1234567890",
    "country_code": "US"
  },
  "campaign_id": "camp_summer2024"
}
```

This helps you:

* Track which marketing campaigns drive the most sales
* Attribute revenue to specific channels
* Analyse campaign performance in your Chariow dashboard

## Custom Metadata

Store custom data with the sale for your own tracking and integration purposes:

```json theme={null}
{
  "product_id": "prd_abc123xyz",
  "email": "customer@example.com",
  "first_name": "John",
  "last_name": "Doe",
  "phone": {
    "number": "1234567890",
    "country_code": "US"
  },
  "custom_metadata": {
    "order_ref": "ORD-123",
    "source": "landing_page",
    "utm_campaign": "summer_sale"
  }
}
```

### Important Notes

* Maximum 10 keys allowed per sale
* Each value is limited to 255 characters
* Keys should be strings with alphanumeric characters and underscores
* Custom metadata is included in Pulse webhook payloads

<Tip>
  Use custom metadata to link Chariow sales with your CRM, analytics platform, or internal systems. The metadata is returned in all sale-related webhooks.
</Tip>

## Buyer IP Address

The checkout endpoint is called from your server, so the IP address we observe is your own infrastructure, not the buyer's. Pass the buyer's IP in `customer_ip` to correct that:

```json theme={null}
{
  "product_id": "prd_abc123xyz",
  "email": "customer@example.com",
  "first_name": "John",
  "last_name": "Doe",
  "phone": {
    "number": "1234567890",
    "country_code": "US"
  },
  "customer_ip": "203.0.113.42"
}
```

Read the buyer's IP from the request that reaches your own server, typically the leftmost entry of the `X-Forwarded-For` header, or `CF-Connecting-IP` if you sit behind Cloudflare.

### What it improves

* **Payment methods** — the buyer's country is resolved from this IP, and the country determines which payment methods appear on the checkout page
* **Analytics** — sales are attributed to the buyer's country rather than to your server's hosting region
* **Fraud review** — the sale record carries the buyer's real IP

### Important notes

* The field is optional. Omit it and we fall back to the calling IP, exactly as before
* Both IPv4 and IPv6 are accepted; a malformed value returns a `422`
* The field is only honoured on API checkouts, so it cannot be spoofed from a browser

## Error Handling

Common checkout errors and how to handle them:

| HTTP Status | Error                           | Cause                                                    | Solution                                                                 |
| ----------- | ------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------ |
| 401         | Unauthorised                    | Invalid or missing API key                               | Check your API key is correct and included in the `Authorization` header |
| 404         | Product not found               | Invalid product ID or unpublished product                | Verify the product ID/slug exists and is published                       |
| 422         | Validation failed               | Missing or invalid required fields                       | Check all required fields are provided with correct formats              |
| 422         | Pay-what-you-want not supported | Product uses pay-what-you-want pricing                   | Redirect customers to your Chariow store or use the Snap Widget          |
| 422         | Product type not supported      | Product is a Service or Coaching type                    | Redirect customers to your Chariow store or use the Snap Widget          |
| 422         | Invalid discount code           | Discount code expired, invalid, or already used          | Verify the discount code is active and applicable                        |
| 422         | Missing shipping address        | Product requires shipping but address fields are missing | Include `address`, `city`, `state`, `country`, and `zip` fields          |

### Example Error Response

```json theme={null}
{
  "message": "The email field must be a valid email address.",
  "data": [],
  "errors": {
    "email": [
      "The email field must be a valid email address."
    ],
    "phone.number": [
      "The phone.number field is required."
    ]
  }
}
```

<Tip>
  Always check the `errors` object for field-specific validation messages to help users correct their input.
</Tip>

## Best Practices

### Handle All Response Steps

Always check the `step` field and handle all possible states:

```javascript theme={null}
const response = await fetch('https://api.chariow.com/v1/checkout', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer sk_live_your_api_key',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(checkoutData)
});

const result = await response.json();

switch (result.data.step) {
  case 'payment':
    // Redirect to payment URL
    window.location.href = result.data.payment.checkout_url;
    break;

  case 'completed':
    // Show success message for free products
    showSuccessMessage(result.data.purchase);
    break;

  case 'already_purchased':
    // Inform customer they already own this product
    showAlreadyPurchasedMessage(result.data.message);
    break;
}
```

### Use Pulses for Sale Updates

Don't rely solely on redirect URLs to track sale completion. Set up Pulses (webhooks) to receive reliable notifications:

* Sale completed
* Payment received
* Refund processed

See the [Pulses Guide](/en/guides/pulses) for setup instructions.

### Validate Before Checkout

Reduce failed checkouts by validating data before calling the API:

* Email format validation
* Phone number format validation
* Required field checks
* Custom field validation

### Handle Network Errors

Implement proper error handling for network issues:

```javascript theme={null}
try {
  const response = await fetch('https://api.chariow.com/v1/checkout', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sk_live_your_api_key',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(checkoutData)
  });

  if (!response.ok) {
    const error = await response.json();
    handleCheckoutError(error);
    return;
  }

  const data = await response.json();
  handleCheckoutSuccess(data);

} catch (error) {
  // Handle network errors
  console.error('Checkout failed:', error);
  showErrorMessage('Unable to process checkout. Please try again.');
}
```

### Store Sale IDs

Always store the returned sale ID (`purchase.id`) for:

* Customer service inquiries
* Refund processing
* Access management
* Analytics tracking

### Test with Different Scenarios

Test your integration with:

* Free products (immediate completion)
* Paid products (payment flow)
* Products with discount codes
* Products with custom fields
* Invalid product IDs
* Invalid discount codes

## Next Steps

<CardGroup cols={2}>
  <Card title="Sales Guide" icon="receipt" href="/en/guides/sales">
    Learn how to retrieve and manage sales
  </Card>

  <Card title="Pulses" icon="webhook" href="/en/guides/pulses">
    Set up notifications for completed sales
  </Card>

  <Card title="Checkout API" icon="code" href="/api-reference/checkout/init-checkout">
    View the complete Checkout API reference
  </Card>
</CardGroup>
