Add every WhatsApp opt-in to your Klaviyo profile list, look up subscriber history before personalising a message, and keep email and WhatsApp contact data in sync across both channels.
Before following this guide, read the External API Request step foundation guide. It covers every field in the step interface so this guide can focus on Klaviyo-specific values.
Every Klaviyo API request needs two non-standard headers. First: Authorization: Klaviyo-API-Key YOUR_PRIVATE_KEY, not Bearer, not api-key. The literal prefix is Klaviyo-API-Key. Second: revision: 2025-01-15, the API version. Omitting either returns an error.
All request and response bodies follow the JSON:API specification. Every request body wraps data in {{"data": {{"type": "...", "attributes": {{...}}}}}}. Every response returns data as an object or array with type, id, and attributes. This is different from the flat JSON bodies used by most other APIs in this series.
profiles:read,
profiles:write,
lists:read,
lists:write.Klaviyo REST API: developers.klaviyo.com
Klaviyo list IDs are short alphanumeric strings like
Y6nRLr. Fetch them once and store the ID of the list
where WhatsApp opt-ins should land.
{{
"data": [
{{
"type": "list",
"id": "Y6nRLr",
"attributes": {{
"name": "WhatsApp Opt-Ins",
"profile_count": 3842
}}
}}
]
}}
Copy: data[0].id = "Y6nRLr"
This string ID is used in all list subscription calls.Before adding a contact, check whether they already exist in Klaviyo. The filter query syntax passes the email directly in the URL.
{{
"data": [
{{
"type": "profile",
"id": "01GMTZY2TEKN4KFRP30JZ59KGB",
"attributes": {{
"email": "priya@example.com",
"first_name": "Priya",
"last_name": "Sharma",
"phone_number": "+919820000001",
"created": "2026-01-15T10:00:00Z"
}}
}}
]
}}
Map: data[0].id -> profile_id
data[0].attributes.first_name -> profile_name
If data is empty, the profile does not exist yet — run Step 4.If Step 3 returned an empty data array, create the profile.
All Klaviyo write requests use JSON:API format. The body must have a
data object with a
type and
attributes.
Body (JSON:API format):
{{
"data": {{
"type": "profile",
"attributes": {{
"email": "{{customer_email}}",
"first_name": "{{customer_name}}",
"phone_number": "{{customer_phone}}"
}}
}}
}}
Response (HTTP 201 Created):
{{
"data": {{
"type": "profile",
"id": "01GMTZY2TEKN4KFRP30JZ59KGB",
"attributes": {{
"email": "priya@example.com",
"first_name": "Priya"
}}
}}
}}
Map: data.id -> profile_id
Use this profile_id in Step 5 to subscribe to a list.With
{{profile_id}} and
{{list_id}} (from Step 2) stored as variables,
add the profile to the list:
Body:
{{
"data": [
{{"type": "profile", "id": "{{profile_id}}"}}
]
}}
Response: HTTP 204 No Content (empty body = success)
The profile is now subscribed to the list.
If already subscribed, Klaviyo returns 204 again (idempotent).Klaviyo returns HTTP 204 No Content when a list subscription succeeds. There is no JSON body in the response. This is expected and correct, not an error.
Response field mapping:
| Variable name | Source | Example value |
|---|---|---|
| profile_id | Step 3 data[0].id or Step 4 data.id | 01GMTZY2TEKN4KFRP30JZ59KGB |
| profile_name | Step 3 data[0].attributes.first_name | Priya |
| list_id | Step 2 data[0].id | Y6nRLr |
Store profile_id and list_id as persistent variables to avoid re-fetching them on every automation run.
Trigger: Customer messages 'SUBSCRIBE' on WhatsApp.
Captured: {{customer_email}} = priya@example.com
{{customer_name}} = Priya Sharma
{{customer_phone}} = +919820000001
Step 1 — Search profile:
GET https://a.klaviyo.com/api/profiles/?filter=equals(email,"priya@example.com")
Authorization: Klaviyo-API-Key pk_...
revision: 2025-01-15
Response: data = [] (not found)
Conditions: if data not empty -> use data[0].id as profile_id, skip to Step 3
if data empty -> run Step 2
Step 2 — Create profile:
POST https://a.klaviyo.com/api/profiles/
Body: {{"data":{{"type":"profile","attributes":{{"email":"priya@example.com",
"first_name":"Priya","phone_number":"+919820000001"}}}}}}
Response: data.id = '01GMTZY2TEKN4KFRP30JZ59KGB'
Stored as {{profile_id}}.
Step 3 — Subscribe to list:
POST https://a.klaviyo.com/api/lists/Y6nRLr/relationships/profiles/
Body: {{"data":[{{"type":"profile","id":"{{profile_id}}"}}]}}
Response: HTTP 204 (success, no body)
Bot replies:
'You are now subscribed, Priya! You will receive updates
at priya@example.com.'| Symptom | Likely cause | Fix |
|---|---|---|
| 401 error | Wrong Authorization format | Use 'Authorization: Klaviyo-API-Key YOUR_KEY'. Not Bearer, not api-key. Ensure there is a space between Klaviyo-API-Key and the key value. |
| 403 Forbidden | Missing required scopes on the API key | The private key needs profiles:read, profiles:write, lists:read, lists:write. Delete the key and create a new one with the correct scopes. |
| 400 on POST profiles | Wrong JSON:API body structure | Ensure the body is wrapped as: {data: {type: 'profile', attributes: {...}}}. A flat JSON body returns 400. |
| revision header error | Missing or wrong revision header | Add revision: 2025-01-15 to every request. Without it, Klaviyo may return deprecated behaviour or errors. |
| data array empty on profile search | Profile not in Klaviyo | The email does not exist as a Klaviyo profile. Proceed to create it with POST /profiles/. |
| 204 on list subscription | Normal success response | HTTP 204 No Content means the profile was successfully added to the list. There is no response body. This is not an error. |
Mailchimp API: data centre prefix, anystring auth, subscriber upsert.
Read guide →Free trial, no credit card. If you get stuck, we answer live on WhatsApp.