Klaviyo
Server-side event delivery to Klaviyo via the official klaviyo-api SDK. Tracks events with EventsApi.createEvent() and manages profiles with ProfilesApi.createOrUpdateProfile(), including revenue tracking, ecommerce metric name mapping, and identify state diffing.
Klaviyo is a server destination in the walkerOS flow:
Receives events server-side from the collector, resolves a profile identifier (email, externalId, or phoneNumber), optionally upserts the profile, then forwards the event to Klaviyo for flows, segmentation, and campaigns.
Installation
npm install @walkeros/server-destination-klaviyo- Integrated
- Bundled
import { startFlow } from '@walkeros/collector';
import { destinationKlaviyo } from '@walkeros/server-destination-klaviyo';
await startFlow({
destinations: {
klaviyo: {
code: destinationKlaviyo,
config: {
settings: {
apiKey: 'YOUR_KLAVIYO_PRIVATE_API_KEY',
},
},
},
},
});Add to your flow.json destinations:
"destinations": {
"klaviyo": {
"package": "@walkeros/server-destination-klaviyo",
"config": {
"settings": {
"apiKey": "YOUR_KLAVIYO_PRIVATE_API_KEY"
}
}
}
}Configuration
This destination uses the standard destination config wrapper (consent, data, env, id, ...). For the shared fields see destination configuration. Package-specific fields live under config.settings and are listed below.
Settings
| Property | Type | Description | More |
|---|---|---|---|
apiKey | string | Klaviyo private API key. Starts with pk_. Find it under Settings > API Keys in your Klaviyo account. | |
email | string | walkerOS mapping value path to resolve email from each event (like user.email). | |
phoneNumber | string | walkerOS mapping value path to resolve phone number in E.164 format from each event. | |
externalId | string | walkerOS mapping value path to resolve external ID from each event (like user.id). | |
identify | any | Destination-level identity mapping. Resolves to profile attributes { firstName?, lastName?, organization?, properties? }. Fires createOrUpdateProfile() on first push and re-fires when values change. | |
currency | string | Default ISO 4217 currency code for revenue events (like USD, EUR). Sets valueCurrency on Klaviyo events. | |
uniqueId | any | Destination-level dedup key mapping. Resolves to the event uniqueId (unique_id on the wire) and defaults to the walkerOS event id. Rule-level mapping.uniqueId overrides it per event. |
Mapping
Per-event rules under config.mapping. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.
| Property | Type | Description | More |
|---|---|---|---|
identify | any | Per-event identify mapping. Resolves to profile attributes for createOrUpdateProfile(). Use with rule-level silent: true on login/signup events. | |
value | any | Revenue value mapping. Resolves to a numeric value for Klaviyo revenue tracking. Sets the event value attribute (value on the wire), plus valueCurrency when settings.currency is set. | |
uniqueId | any | Dedup key mapping. Resolves to the event uniqueId (unique_id on the wire). Klaviyo keeps only the first event with a given value per profile and metric. Without it Klaviyo dedups on the event time truncated to the second. |
Examples
Deduplicated order
An order carries a stable unique id so Klaviyo keeps only the first copy when the same order arrives from another producer.
{
"name": "order complete",
"data": {
"id": "ORD-123",
"total": 49.5
},
"context": {
"shopping": [
"complete",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"email": "user@example.com"
},
"nested": [
{
"entity": "product",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "product",
"data": {
"id": "cc",
"name": "Cool Cap",
"size": "one size",
"price": 42
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "gift",
"data": {
"name": "Surprise"
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
}
],
"consent": {
"functional": true
},
"id": "a1b2c3d4e5f60107",
"trigger": "load",
"entity": "order",
"action": "complete",
"timestamp": 1700000107,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"name": "Placed Order",
"data": {
"map": {
"OrderId": "data.id"
}
},
"settings": {
"uniqueId": "data.id"
}
}eventsApi.createEvent({
"data": {
"type": "event",
"attributes": {
"profile": {
"data": {
"type": "profile",
"attributes": {
"email": "user@example.com",
"externalId": "us3r"
}
}
},
"metric": {
"data": {
"type": "metric",
"attributes": {
"name": "Placed Order"
}
}
},
"properties": {
"OrderId": "ORD-123"
},
"time": "1970-01-20T16:13:20.107Z",
"uniqueId": "ORD-123"
}
}
})Default event
An event is sent to Klaviyo as a metric with an inline profile resolved from the user email and id.
{
"name": "product view",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"detail",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"email": "user@example.com"
},
"nested": [],
"consent": {
"functional": true
},
"id": "a1b2c3d4e5f60100",
"trigger": "load",
"entity": "product",
"action": "view",
"timestamp": 1700000100,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}eventsApi.createEvent({
"data": {
"type": "event",
"attributes": {
"profile": {
"data": {
"type": "profile",
"attributes": {
"email": "user@example.com",
"externalId": "us3r"
}
}
},
"metric": {
"data": {
"type": "metric",
"attributes": {
"name": "product view"
}
}
},
"properties": {},
"time": "1970-01-20T16:13:20.100Z",
"uniqueId": "a1b2c3d4e5f60100"
}
}
})Destination identify
Destination-level identify upserts the Klaviyo profile with a first name before each event is sent.
{
"name": "page view",
"data": {
"domain": "www.example.com",
"title": "walkerOS documentation",
"referrer": "https://www.walkeros.io/",
"search": "?foo=bar",
"hash": "#hash",
"id": "/docs/"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"pagegroup": "docs"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"email": "user@example.com",
"firstName": "Jane"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "a1b2c3d4e5f60104",
"trigger": "load",
"entity": "page",
"action": "view",
"timestamp": 1700000104,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}profilesApi.createOrUpdateProfile({
"data": {
"type": "profile",
"attributes": {
"email": "user@example.com",
"externalId": "us3r",
"firstName": "Jane"
}
}
});
eventsApi.createEvent({
"data": {
"type": "event",
"attributes": {
"profile": {
"data": {
"type": "profile",
"attributes": {
"email": "user@example.com",
"externalId": "us3r"
}
}
},
"metric": {
"data": {
"type": "metric",
"attributes": {
"name": "page view"
}
}
},
"properties": {},
"time": "1970-01-20T16:13:20.104Z",
"uniqueId": "a1b2c3d4e5f60104"
}
}
})Email only
A newsletter signup uses only the email address as the Klaviyo profile identifier, with no external id.
{
"name": "newsletter signup",
"data": {
"string": "foo",
"number": 1,
"boolean": true,
"array": [
0,
"text",
false
]
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"lang": "elb"
},
"custom": {
"completely": "random"
},
"user": {
"email": "subscriber@example.com"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "a1b2c3d4e5f60105",
"trigger": "test",
"entity": "newsletter",
"action": "signup",
"timestamp": 1700000105,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}eventsApi.createEvent({
"data": {
"type": "event",
"attributes": {
"profile": {
"data": {
"type": "profile",
"attributes": {
"email": "subscriber@example.com"
}
}
},
"metric": {
"data": {
"type": "metric",
"attributes": {
"name": "newsletter signup"
}
}
},
"properties": {},
"time": "1970-01-20T16:13:20.105Z",
"uniqueId": "a1b2c3d4e5f60105"
}
}
})Viewed product
A product view is mapped to the Klaviyo Viewed Product metric with properties such as product name and price.
{
"name": "product view",
"data": {
"name": "USB Cable",
"id": "PROD-1",
"price": 9.99
},
"context": {
"shopping": [
"detail",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"email": "user@example.com"
},
"nested": [],
"consent": {
"functional": true
},
"id": "a1b2c3d4e5f60101",
"trigger": "load",
"entity": "product",
"action": "view",
"timestamp": 1700000101,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"name": "Viewed Product",
"data": {
"map": {
"ProductName": "data.name",
"ProductID": "data.id",
"Price": "data.price"
}
}
}eventsApi.createEvent({
"data": {
"type": "event",
"attributes": {
"profile": {
"data": {
"type": "profile",
"attributes": {
"email": "user@example.com",
"externalId": "us3r"
}
}
},
"metric": {
"data": {
"type": "metric",
"attributes": {
"name": "Viewed Product"
}
}
},
"properties": {
"ProductName": "USB Cable",
"ProductID": "PROD-1",
"Price": 9.99
},
"time": "1970-01-20T16:13:20.101Z",
"uniqueId": "a1b2c3d4e5f60101"
}
}
})Placed order
An order complete is sent to Klaviyo as Placed Order with value and currency for revenue attribution.
{
"name": "order complete",
"data": {
"id": "ORD-123",
"total": 99.99,
"itemNames": [
"Widget A",
"Widget B"
]
},
"context": {
"shopping": [
"complete",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"email": "user@example.com"
},
"nested": [
{
"entity": "product",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "product",
"data": {
"id": "cc",
"name": "Cool Cap",
"size": "one size",
"price": 42
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "gift",
"data": {
"name": "Surprise"
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
}
],
"consent": {
"functional": true
},
"id": "a1b2c3d4e5f60102",
"trigger": "load",
"entity": "order",
"action": "complete",
"timestamp": 1700000102,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"name": "Placed Order",
"data": {
"map": {
"OrderId": "data.id",
"ItemNames": "data.itemNames"
}
},
"settings": {
"value": "data.total"
}
}eventsApi.createEvent({
"data": {
"type": "event",
"attributes": {
"profile": {
"data": {
"type": "profile",
"attributes": {
"email": "user@example.com",
"externalId": "us3r"
}
}
},
"metric": {
"data": {
"type": "metric",
"attributes": {
"name": "Placed Order"
}
}
},
"properties": {
"OrderId": "ORD-123",
"ItemNames": [
"Widget A",
"Widget B"
]
},
"time": "1970-01-20T16:13:20.102Z",
"value": 99.99,
"valueCurrency": "EUR",
"uniqueId": "a1b2c3d4e5f60102"
}
}
})User login identify
A user login upserts the Klaviyo profile with name, organization, and custom properties without firing an event.
{
"name": "user login",
"data": {
"firstName": "Jane",
"lastName": "Doe",
"company": "Acme Corp",
"plan": "premium"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"lang": "elb"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"email": "user@acme.com"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "a1b2c3d4e5f60103",
"trigger": "test",
"entity": "user",
"action": "login",
"timestamp": 1700000103,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"silent": true,
"settings": {
"identify": {
"map": {
"firstName": "data.firstName",
"lastName": "data.lastName",
"organization": "data.company",
"properties": {
"map": {
"plan": "data.plan"
}
}
}
}
}
}profilesApi.createOrUpdateProfile({
"data": {
"type": "profile",
"attributes": {
"email": "user@acme.com",
"externalId": "us3r",
"firstName": "Jane",
"lastName": "Doe",
"organization": "Acme Corp",
"properties": {
"plan": "premium"
}
}
}
})Identity is resolved automatically from each event: email defaults to user.email and externalId defaults to user.id. Klaviyo requires at least one identifier (email, phoneNumber, or externalId) per event. Events without any identifier are skipped with a warning.
Revenue tracking
Map a mapping rule's settings.value to a numeric value. The destination sets it as the Klaviyo event's value attribute, the field revenue reporting reads, and when settings.currency is also set it adds valueCurrency alongside it.
Deduplication
Klaviyo keeps only the first event carrying a given uniqueId for a profile and metric. The destination sends the walkerOS event id by default, so two distinct events for the same profile and metric never collide.
Map settings.uniqueId to a stable business identifier whenever the same event can reach Klaviyo from more than one producer: browser tracking alongside this destination, a CSV or backfill import, or a retried delivery. A mapping rule's settings.uniqueId overrides the destination-level one. Numeric ids are coerced to strings, so an order id like data.id works whether it is 'ORD-123' or 90210.
Ecommerce metric naming
Klaviyo's built-in flows and reports key off specific metric names. Use mapping.name to rename walkerOS events to Klaviyo's expected values:
| walkerOS Event | Klaviyo Metric | Unlocks |
|---|---|---|
product view | Viewed Product | Product analytics |
product add | Added to Cart | Cart abandonment flows |
order complete | Placed Order | Revenue reporting, CLV |