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.
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:
Create Event: https://developers.klaviyo.com/en/reference/create_event
Events API overview: https://developers.klaviyo.com/en/reference/events_api_overview
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.
Authenticate requests and API overview: https://developers.klaviyo.com/en/reference/api_overview
API versioning policy: https://developers.klaviyo.com/en/docs/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.
API-based website activity events guide: https://developers.klaviyo.com/en/docs/guide_to_setting_up_api_based_website_activity_events
Segment conditions reference: https://help.klaviyo.com/hc/en-us/articles/115005062847
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.
Events API overview: https://developers.klaviyo.com/en/reference/events_api_overview
Segment conditions reference: https://help.klaviyo.com/hc/en-us/articles/115005062847
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:
In Flows, create a new flow with the trigger “Metric” and select your Started Checkout metric. Use the Preview pane to see who would enter and why; add trigger filters like “value is at least 50” or “item_names contains ‘Jeans’.” Klaviyo details metric triggers and troubleshooting in the Help Center.
How to create a metric-triggered flow: https://help.klaviyo.com/hc/en-us/articles/360003057151
Troubleshooting a metric-triggered flow: https://help.klaviyo.com/hc/en-us/articles/12278373016603
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
401 or 403 — Authentication or scope
Verify the Authorization header format and that your private key belongs to the correct account and has proper permissions. See Klaviyo’s API overview for auth guidance: https://developers.klaviyo.com/en/reference/api_overview
415 — Unsupported Media Type
Send JSON with content-type: application/json and include accept: application/json. See the Create Event examples: https://developers.klaviyo.com/en/reference/create_event
400 — Invalid payload
Ensure the JSON:API envelope is correct and you’ve included attributes.profile, attributes.metric.name, attributes.properties, and a valid time. The Events API overview outlines the expected shape: https://developers.klaviyo.com/en/reference/events_api_overview
429 — Rate limited
Back off and honor Retry-After before retrying. Rate limit behavior is described in the API overview: https://developers.klaviyo.com/en/reference/api_overview
Metric not visible
No successful events yet or wrong revision header. Recheck your headers and recent request status in your logs; the Events API overview provides UI verification pointers: https://developers.klaviyo.com/en/reference/events_api_overview
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.
Create Event reference — authoritative endpoint and payload envelope: https://developers.klaviyo.com/en/reference/create_event
Events API overview — structure, properties, and unique_id semantics: https://developers.klaviyo.com/en/reference/events_api_overview
API-based website activity events — end-to-end examples and tips: https://developers.klaviyo.com/en/docs/guide_to_setting_up_api_based_website_activity_events
API versioning and deprecation policy — how the revision header works: https://developers.klaviyo.com/en/docs/api_versioning_and_deprecation_policy