28 min read

Klaviyo Started Checkout API: A Step-by-Step Setup with Code

Step-by-step guide to send a Klaviyo Started Checkout event via the Events API with copy‑paste cURL, Python, Node, PHP, and Ruby examples and QA tips.

Klaviyo Started Checkout API: A Step-by-Step Setup with Code

You’re about to wire a clean, server-side “Started Checkout” event into Klaviyo so you can trigger abandonment flows, segment by cart value or items, and trust your numbers. We’ll use the Klaviyo Events API with a JSON:API payload, show copy‑paste code in five languages, and walk through validation and troubleshooting.

Key takeaways

  • Use POST https://a.klaviyo.com/api/events with headers: Authorization, accept, content-type, and a date-based revision.

  • Send a JSON:API envelope with data.type = "event", attributes.metric.name = "Started Checkout", attributes.profile, attributes.properties, time, and unique_id.

  • Keep segmentable fields top-level in properties (value, currency, checkout_url, coupon, item_names) and include an items array for detail.

  • Validate in Analytics > Metrics and by creating a metric-triggered flow; use unique_id for safe retries.

  • If you see 401, 403, 415, or missing metrics, double-check authentication, headers, and payload shape.

Quickstart with the Klaviyo Started Checkout API

Here’s the minimal pattern you’ll use for every request. Replace placeholders with your real values. Klaviyo’s newer /api endpoints require a date-stamped revision header.

  • Endpoint: POST https://a.klaviyo.com/api/events

  • Required headers:

    • Authorization: Klaviyo-API-Key

    • accept: application/json

    • content-type: application/json

    • revision: 2024-02-15 (example)

Reference: See Klaviyo’s Create Event and Events API overview for the current endpoint, headers, and JSON:API envelope in the official docs:

Minimal cURL

curl --request POST \
    --url https://a.klaviyo.com/api/events \
    --header 'Authorization: Klaviyo-API-Key <YOUR_PRIVATE_KEY>' \
    --header 'accept: application/json' \
    --header 'content-type: application/json' \
    --header 'revision: 2024-02-15' \
    --data '{
      "data": {
        "type": "event",
        "attributes": {
          "profile": { "email": "customer@example.com" },
          "metric": { "name": "Started Checkout" },
          "properties": { "value": 129.98, "currency": "USD", "checkout_url": "https://store.example.com/checkout?cart=abcd1234" },
          "time": "2026-05-31T12:00:00Z",
          "unique_id": "cart-abcd1234"
        }
      }
    }'
  

According to Klaviyo’s docs, you authenticate using a private key in the Authorization header, and payloads follow a JSON:API-style envelope with a date-based revision header. See the guidance in the official references: the Create Event endpoint and the Events API overview. For background on versioning, review the API versioning and deprecation policy.

Full cURL example for Started Checkout

This is a complete, paste‑ready example with recommended properties that segment well. It’s safe to retry the same event if you reuse the cart or session ID as unique_id.

curl --request POST \
    --url https://a.klaviyo.com/api/events \
    --header 'Authorization: Klaviyo-API-Key <YOUR_PRIVATE_KEY>' \
    --header 'accept: application/json' \
    --header 'content-type: application/json' \
    --header 'revision: 2024-02-15' \
    --data '{
      "data": {
        "type": "event",
        "attributes": {
          "profile": { "email": "customer@example.com" },
          "metric": { "name": "Started Checkout" },
          "properties": {
            "value": 129.98,
            "currency": "USD",
            "checkout_url": "https://store.example.com/checkout?cart=abcd1234",
            "coupon": "SPRING10",
            "item_names": ["Blue Tee - M", "White Jeans - 32"],
            "items": [
              {"ProductID": "tee-001", "ProductName": "Blue Tee - M", "ItemPrice": 29.99, "Quantity": 1},
              {"ProductID": "jeans-032", "ProductName": "White Jeans - 32", "ItemPrice": 99.99, "Quantity": 1}
            ]
          },
          "time": "2026-05-31T12:00:00Z",
          "unique_id": "cart-abcd1234"
        }
      }
    }'
  

Why this modeling works: Klaviyo’s segmentation favors top‑level, non‑object values for filters, so value, currency, checkout_url, coupon, and item_names are easy to use in segments and flow filters. The items array preserves product detail for analytics while you keep a flattened item_names list for filtering. See the Events API overview and segmentation references in Klaviyo’s docs for details on what’s filterable and how properties behave.

Code you can paste

Use the same JSON body across languages; only the client changes.

Python

import requests
  
  url = "https://a.klaviyo.com/api/events"
  headers = {
      "Authorization": "Klaviyo-API-Key <YOUR_PRIVATE_KEY>",
      "accept": "application/json",
      "content-type": "application/json",
      "revision": "2024-02-15"
  }
  payload = {
      "data": {
          "type": "event",
          "attributes": {
              "profile": {"email": "customer@example.com"},
              "metric": {"name": "Started Checkout"},
              "properties": {
                  "value": 129.98,
                  "currency": "USD",
                  "checkout_url": "https://store.example.com/checkout?cart=abcd1234",
                  "coupon": "SPRING10",
                  "item_names": ["Blue Tee - M", "White Jeans - 32"],
                  "items": [
                      {"ProductID": "tee-001", "ProductName": "Blue Tee - M", "ItemPrice": 29.99, "Quantity": 1},
                      {"ProductID": "jeans-032", "ProductName": "White Jeans - 32", "ItemPrice": 99.99, "Quantity": 1}
                  ]
              },
              "time": "2026-05-31T12:00:00Z",
              "unique_id": "cart-abcd1234"
          }
      }
  }
  resp = requests.post(url, headers=headers, json=payload, timeout=20)
  print(resp.status_code, resp.text)
  

Node.js

import fetch from "node-fetch";
  
  const url = "https://a.klaviyo.com/api/events";
  const headers = {
    "Authorization": "Klaviyo-API-Key <YOUR_PRIVATE_KEY>",
    "accept": "application/json",
    "content-type": "application/json",
    "revision": "2024-02-15"
  };
  const payload = {
    data: {
      type: "event",
      attributes: {
        profile: { email: "customer@example.com" },
        metric: { name: "Started Checkout" },
        properties: {
          value: 129.98,
          currency: "USD",
          checkout_url: "https://store.example.com/checkout?cart=abcd1234",
          coupon: "SPRING10",
          item_names: ["Blue Tee - M", "White Jeans - 32"],
          items: [
            { ProductID: "tee-001", ProductName: "Blue Tee - M", ItemPrice: 29.99, Quantity: 1 },
            { ProductID: "jeans-032", ProductName: "White Jeans - 32", ItemPrice: 99.99, Quantity: 1 }
          ]
        },
        time: "2026-05-31T12:00:00Z",
        unique_id: "cart-abcd1234"
      }
    }
  };
  const resp = await fetch(url, { method: "POST", headers, body: JSON.stringify(payload) });
  console.log(resp.status, await resp.text());
  

PHP

<?php
  $ch = curl_init('https://a.klaviyo.com/api/events');
  $headers = [
    'Authorization: Klaviyo-API-Key <YOUR_PRIVATE_KEY>',
    'accept: application/json',
    'content-type: application/json',
    'revision: 2024-02-15'
  ];
  $payload = [
    'data' => [
      'type' => 'event',
      'attributes' => [
        'profile' => ['email' => 'customer@example.com'],
        'metric' => ['name' => 'Started Checkout'],
        'properties' => [
          'value' => 129.98,
          'currency' => 'USD',
          'checkout_url' => 'https://store.example.com/checkout?cart=abcd1234',
          'coupon' => 'SPRING10',
          'item_names' => ['Blue Tee - M', 'White Jeans - 32'],
          'items' => [
            ['ProductID' => 'tee-001', 'ProductName' => 'Blue Tee - M', 'ItemPrice' => 29.99, 'Quantity' => 1],
            ['ProductID' => 'jeans-032', 'ProductName' => 'White Jeans - 32', 'ItemPrice' => 99.99, 'Quantity' => 1]
          ]
        ],
        'time' => '2026-05-31T12:00:00Z',
        'unique_id' => 'cart-abcd1234'
      ]
    ]
  ];
  curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => $headers,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($payload)
  ]);
  $response = curl_exec($ch);
  echo curl_getinfo($ch, CURLINFO_HTTP_CODE) . "\n" . $response;
  curl_close($ch);
  ?>
  

Ruby

require 'net/http'
  require 'json'
  
  uri = URI('https://a.klaviyo.com/api/events')
  req = Net::HTTP::Post.new(uri)
  req['Authorization'] = 'Klaviyo-API-Key <YOUR_PRIVATE_KEY>'
  req['accept'] = 'application/json'
  req['content-type'] = 'application/json'
  req['revision'] = '2024-02-15'
  req.body = {
    data: {
      type: 'event',
      attributes: {
        profile: { email: 'customer@example.com' },
        metric: { name: 'Started Checkout' },
        properties: {
          value: 129.98,
          currency: 'USD',
          checkout_url: 'https://store.example.com/checkout?cart=abcd1234',
          coupon: 'SPRING10',
          item_names: ['Blue Tee - M', 'White Jeans - 32'],
          items: [
            { ProductID: 'tee-001', ProductName: 'Blue Tee - M', ItemPrice: 29.99, Quantity: 1 },
            { ProductID: 'jeans-032', ProductName: 'White Jeans - 32', ItemPrice: 99.99, Quantity: 1 }
          ]
        },
        time: '2026-05-31T12:00:00Z',
        unique_id: 'cart-abcd1234'
      }
    }
  }.to_json
  
  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
  puts res.code
  puts res.body
  

Model your Started Checkout properties for segmentation

Keep frequently filtered fields flat; reserve nested structures for detail.

Property

Type

Why it’s useful

value

number

Filter flows by cart value thresholds

currency

string

Route flows by currency or region

checkout_url

string

Insert deep links into messages

coupon

string

Personalize copy or exclude coupon users

item_names

array[string]

“Contains item” filters work reliably

items

array[object]

Full line-item detail for analytics

cart_id

string

Reuse as unique_id for safe retries

Best practices: avoid property name collisions with profile properties, and if you plan to filter on a value, keep it top-level and non-object. For reference on JSON:API payloads and filterable properties, see Klaviyo’s Events API overview and the segment conditions reference.

Validate and QA in Klaviyo

First, confirm that Klaviyo ingested your event:

  • In Analytics > Metrics, search for the Started Checkout metric and check recent activity. You can also open a known profile to see the event on the activity feed. Klaviyo covers these steps in the Events API overview docs.

Next, create a metric-triggered flow and preview it:

QA notes: Expect a short delay before events appear in the Metrics list. Use ISO‑8601 UTC timestamps and ensure your server clock isn’t in the future. Reuse the same unique_id on retries to prevent duplicates.

Common errors and quick fixes

  1. 401 or 403 — Authentication or scope

  2. 415 — Unsupported Media Type

  3. 400 — Invalid payload

  4. 429 — Rate limited

  5. Metric not visible

Practical example: forwarding events with a tracker

Some teams centralize checkout tracking server-side and forward it to Klaviyo. A platform like Attribuly supports this pattern so your team can standardize event schemas and ship faster while keeping Klaviyo flows powered by reliable Started Checkout signals. This is optional—the API approach above stands on its own.

Next steps and resources

You now have a working Klaviyo Started Checkout API integration you can expand. Consider adding filters for high-value carts, item-based branches, and currency routing in your flows, then iterate on content and timing.