Skip to main content

Klaviyo

Server Source code Package Beta

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.

Where this fits

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
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',
        },
      },
    },
  },
});

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

PropertyTypeDescriptionMore
apiKey*stringKlaviyo private API key. Starts with pk_. Find it under Settings > API Keys in your Klaviyo account.
emailstringwalkerOS mapping value path to resolve email from each event (like user.email).
phoneNumberstringwalkerOS mapping value path to resolve phone number in E.164 format from each event.
externalIdstringwalkerOS mapping value path to resolve external ID from each event (like user.id).
identifyanyDestination-level identity mapping. Resolves to profile attributes { firstName?, lastName?, organization?, properties? }. Fires createOrUpdateProfile() on first push and re-fires when values change.
currencystringDefault ISO 4217 currency code for revenue events (like USD, EUR). Sets valueCurrency on Klaviyo events.
uniqueIdanyDestination-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.
* Required fields

Mapping

Per-event rules under config.mapping. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.

PropertyTypeDescriptionMore
identifyanyPer-event identify mapping. Resolves to profile attributes for createOrUpdateProfile(). Use with rule-level silent: true on login/signup events.
valueanyRevenue 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.
uniqueIdanyDedup 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.

Event
{
  "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"
  }
}
Mapping
{
  "name": "Placed Order",
  "data": {
    "map": {
      "OrderId": "data.id"
    }
  },
  "settings": {
    "uniqueId": "data.id"
  }
}
Out
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.

Event
{
  "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"
  }
}
Out
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.

Event
{
  "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"
  }
}
Out
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.

Event
{
  "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"
  }
}
Out
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.

Event
{
  "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"
  }
}
Mapping
{
  "name": "Viewed Product",
  "data": {
    "map": {
      "ProductName": "data.name",
      "ProductID": "data.id",
      "Price": "data.price"
    }
  }
}
Out
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.

Event
{
  "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"
  }
}
Mapping
{
  "name": "Placed Order",
  "data": {
    "map": {
      "OrderId": "data.id",
      "ItemNames": "data.itemNames"
    }
  },
  "settings": {
    "value": "data.total"
  }
}
Out
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.

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"
  }
}
Mapping
{
  "silent": true,
  "settings": {
    "identify": {
      "map": {
        "firstName": "data.firstName",
        "lastName": "data.lastName",
        "organization": "data.company",
        "properties": {
          "map": {
            "plan": "data.plan"
          }
        }
      }
    }
  }
}
Out
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 EventKlaviyo MetricUnlocks
product viewViewed ProductProduct analytics
product addAdded to CartCart abandonment flows
order completePlaced OrderRevenue reporting, CLV
💡 Need implementation support?
elbwalker offers hands-on support: setup review, measurement planning, destination mapping, and live troubleshooting. Book a 2-hour session (€399)