Customer.io
Server-side event delivery to Customer.io via the official customerio-node SDK. Tracks events with TrackClient.track(), manages identities with identify(), and supports the full lifecycle: page views, device registration, suppress/unsuppress, profile merging, and transactional messaging via APIClient.sendEmail() / sendPush().
Customer.io is a server destination in the walkerOS flow:
Receives events server-side from the collector, resolves a `customerId` (or falls back to `anonymousId`), optionally fires `identify()`, then forwards the event to Customer.io for campaigns, broadcasts, and transactional messaging.
Installation
npm install @walkeros/server-destination-customerio- Integrated
- Bundled
import { startFlow } from '@walkeros/collector';
import { destinationCustomerIo } from '@walkeros/server-destination-customerio';
await startFlow({
destinations: {
customerio: {
code: destinationCustomerIo,
config: {
settings: {
siteId: 'YOUR_SITE_ID',
apiKey: 'YOUR_API_KEY',
},
},
},
},
});Add to your flow.json destinations:
"destinations": {
"customerio": {
"package": "@walkeros/server-destination-customerio",
"config": {
"settings": {
"siteId": "YOUR_SITE_ID",
"apiKey": "YOUR_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 |
|---|---|---|---|
siteId | string | Customer.io Site ID. Find it in Settings > Workspace Settings > API Credentials. | |
apiKey | string | Customer.io API Key. Find it in Settings > Workspace Settings > API Credentials. | |
appApiKey | string | App API Key for transactional messaging (sendEmail/sendPush). Find it in Settings > Workspace Settings > API Credentials > App API Keys. | |
region | 'us' | 'eu' | Data center region. Must match where your Customer.io workspace was created. Default: us. | |
timeout | integer | HTTP request timeout in milliseconds. Default: 10000. | |
customerId | string | walkerOS mapping value path to resolve customerId from each event (like user.id). | |
anonymousId | string | walkerOS mapping value path to resolve anonymousId from each event (like user.session). | |
identify | any | Destination-level identity mapping. Resolves to { email?, first_name?, ... } attributes. Fires identify() on first push and re-fires when values change. |
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 attributes. Resolves to { email?, first_name?, ... }. Use with silent: true on login/identify events. | |
page | any | Per-event page view. Resolves to { url, ... }. Calls trackPageView(). Use with silent: true. | |
destroy | boolean | Permanently delete person from Customer.io. Set true on delete events with silent: true. | |
suppress | boolean | Suppress person (stop messaging without deleting data). Set true with silent: true. | |
unsuppress | boolean | Unsuppress person (resume messaging). Set true with silent: true. | |
addDevice | any | Register push device. Resolves to { deviceId, platform, data? }. Use with silent: true. | |
deleteDevice | any | Remove push device. Resolves to { deviceId, platform }. Use with silent: true. | |
merge | any | Merge duplicate profiles. Resolves to { primaryType, primaryId, secondaryType, secondaryId }. Use with silent: true. | |
sendEmail | any | Send transactional email. Resolves to { to, transactional_message_id, message_data?, identifiers? }. Requires appApiKey. Use with silent: true. | |
sendPush | any | Send transactional push. Resolves to { transactional_message_id, message_data?, identifiers? }. Requires appApiKey. Use with silent: true. |
Examples
Anonymous track
When no customer id is resolved the event is sent via trackAnonymous keyed by the session 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": {
"session": "s3ss10n"
},
"nested": [],
"consent": {
"functional": true
},
"id": "0157acc60b5eabe4",
"trigger": "load",
"entity": "product",
"action": "view",
"timestamp": 1700000103,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}trackClient.trackAnonymous("s3ss10n", {
"name": "product view",
"data": {},
"timestamp": 1700000
})Default track
A walkerOS event is forwarded to Customer.io as a track call keyed by the user 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",
"session": "s3ss10n"
},
"nested": [],
"consent": {
"functional": true
},
"id": "c2dea3c29dbf4aa4",
"trigger": "load",
"entity": "product",
"action": "view",
"timestamp": 1700000100,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}trackClient.track("us3r", {
"name": "product view",
"data": {},
"timestamp": 1700000
})Destination identify
Destination-level identify fires a Customer.io identify call once on the first push, before the track, attaching user attributes.
{
"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",
"session": "s3ss10n",
"email": "user@example.com"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "bba41f7ee0c4091c",
"trigger": "load",
"entity": "page",
"action": "view",
"timestamp": 1700000104,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}trackClient.identify("us3r", {
"email": "user@example.com"
});
trackClient.track("us3r", {
"name": "page view",
"data": {},
"timestamp": 1700000
})Destroy person
A user delete event permanently removes the person from Customer.io via trackClient.destroy.
{
"name": "user delete",
"data": {
"string": "foo",
"number": 1,
"boolean": true,
"array": [
0,
"text",
false
]
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"lang": "elb"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "1f081fa0d9c575d9",
"trigger": "test",
"entity": "user",
"action": "delete",
"timestamp": 1700000107,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"silent": true,
"settings": {
"destroy": true
}
}trackClient.destroy("us3r")Mapped properties
A data mapping transforms the event payload into Customer.io track properties for an order.
{
"name": "order complete",
"data": {
"id": "0rd3r1d",
"total": 555,
"currency": "EUR"
},
"context": {
"shopping": [
"complete",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"session": "s3ss10n"
},
"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": "fc521b3f52941d4f",
"trigger": "load",
"entity": "order",
"action": "complete",
"timestamp": 1700000102,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"name": "purchase",
"data": {
"map": {
"order_id": "data.id",
"value": "data.total",
"currency": "data.currency"
}
}
}trackClient.track("us3r", {
"name": "purchase",
"data": {
"order_id": "0rd3r1d",
"value": 555,
"currency": "EUR"
},
"timestamp": 1700000
})Rename event
A mapping rule renames the walker event to a Customer.io-specific event name such as purchase.
{
"name": "order complete",
"data": {
"id": "0rd3r1d",
"currency": "EUR",
"shipping": 5.22,
"taxes": 73.76,
"total": 555
},
"context": {
"shopping": [
"complete",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"session": "s3ss10n"
},
"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": "19c1445a5e40aaba",
"trigger": "load",
"entity": "order",
"action": "complete",
"timestamp": 1700000101,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"name": "purchase"
}trackClient.track("us3r", {
"name": "purchase",
"data": {},
"timestamp": 1700000
})Page view
A page view fires trackPageView with the URL and referrer instead of a generic track call.
{
"name": "page view",
"data": {
"url": "https://example.com/pricing",
"referrer": "https://google.com"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"pagegroup": "docs"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "87f62a1e1c82321d",
"trigger": "load",
"entity": "page",
"action": "view",
"timestamp": 1700000106,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"silent": true,
"settings": {
"page": {
"map": {
"url": "data.url",
"referrer": "data.referrer"
}
}
}
}trackClient.trackPageView("us3r", "https://example.com/pricing", {
"referrer": "https://google.com"
})Suppress person
A user suppress event stops messaging for the person without deleting their profile data.
{
"name": "user suppress",
"data": {
"string": "foo",
"number": 1,
"boolean": true,
"array": [
0,
"text",
false
]
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"lang": "elb"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "c1592f429eb27931",
"trigger": "test",
"entity": "user",
"action": "suppress",
"timestamp": 1700000108,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"silent": true,
"settings": {
"suppress": true
}
}trackClient.suppress("us3r")Unsuppress person
A user unsuppress event resumes messaging for a previously suppressed Customer.io profile.
{
"name": "user unsuppress",
"data": {
"string": "foo",
"number": 1,
"boolean": true,
"array": [
0,
"text",
false
]
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"lang": "elb"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "52cdfff3146bdb70",
"trigger": "test",
"entity": "user",
"action": "unsuppress",
"timestamp": 1700000109,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"silent": true,
"settings": {
"unsuppress": true
}
}trackClient.unsuppress("us3r")User login identify
A user login triggers only a Customer.io identify call with profile attributes, skipping the track.
{
"name": "user login",
"data": {
"email": "user@acme.com",
"first_name": "Jane",
"plan": "premium"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"lang": "elb"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "36fa74557eefbb08",
"trigger": "test",
"entity": "user",
"action": "login",
"timestamp": 1700000105,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"silent": true,
"settings": {
"identify": {
"map": {
"email": "data.email",
"first_name": "data.first_name",
"plan": "data.plan"
}
}
}
}trackClient.identify("us3r", {
"email": "user@acme.com",
"first_name": "Jane",
"plan": "premium"
})Identity is resolved automatically from each event: customerId defaults to user.id and anonymousId defaults to user.session. Customer.io requires a customerId or anonymousId per event. When customerId is missing, the destination automatically falls back to trackAnonymous() so anonymous visitor data is preserved.
Customer lifecycle
Per-rule mapping settings control which lifecycle methods are called in addition to the default track():
| Mapping Setting | SDK Call | Use For |
|---|---|---|
identify | identify() | Login, signup, profile updates |
page | trackPageView() | Page view events |
destroy | destroy() | GDPR delete / user removal |
suppress | suppress() | Stop messaging (keep data) |
unsuppress | unsuppress() | Resume messaging |
addDevice | addDevice() | Register push notification token |
deleteDevice | deleteDevice() | Remove push token |
merge | mergeCustomers() | Consolidate duplicate profiles |
Use skip: true on the rule to fire only the lifecycle call without tracking a second track() event.
Transactional messaging
Set settings.appApiKey to enable sendEmail / sendPush via Customer.io's App API. These mappings resolve to the full request body expected by the SDK.