> Part of the walkerOS documentation. Project overview and full index: <https://www.walkeros.io/llms.txt>

# Customer.io

<!-- -->

[Server](#)[ ](https://github.com/elbwalker/walkerOS/tree/main/packages/server/destinations/customerio)

<!-- -->

[Source code](https://github.com/elbwalker/walkerOS/tree/main/packages/server/destinations/customerio)[ ](https://www.npmjs.com/package/@walkeros/server-destination-customerio)

<!-- -->

[Package](https://www.npmjs.com/package/@walkeros/server-destination-customerio)

<!-- -->

Beta

Server-side event delivery to [Customer.io](https://customer.io/) via the official [`customerio-node`](https://www.npmjs.com/package/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()`.

<!-- -->

Where this fits

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[​](#installation "Direct link to 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"
      }
    }
  }
}
```

[See bundled mode setup](https://www.walkeros.io/docs/getting-started/modes/bundled.md) | [CLI reference](https://www.walkeros.io/docs/apps/cli.md)

## Configuration[​](#configuration "Direct link to Configuration")

This <!-- -->destination<!-- --> uses the standard <!-- -->destination<!-- --> config wrapper (consent, data, env, id, ...). For the shared fields see [destination<!-- --> configuration](https://www.walkeros.io/docs/destinations.md#configuration). Package-specific fields live under `config.settings` and are listed below.

## Settings[​](#settings "Direct link to 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. |      |

\* Required fields

## Mapping[​](#mapping "Direct link to Mapping")

Per-event rules under `config.mapping`. For the standard rule fields (consent, condition, data, batch, name, policy) see [mapping](https://www.walkeros.io/docs/mapping.md).

| 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.

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": {
    "session": "s3ss10n"
  },
  "nested": [],
  "consent": {
    "functional": true
  },
  "id": "17d23f0403075e3e",
  "trigger": "load",
  "entity": "product",
  "action": "view",
  "timestamp": 1700000103,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
```

Out

```
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.

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",
    "session": "s3ss10n"
  },
  "nested": [],
  "consent": {
    "functional": true
  },
  "id": "2b846434003a9c2a",
  "trigger": "load",
  "entity": "product",
  "action": "view",
  "timestamp": 1700000100,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
```

Out

```
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.

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",
    "session": "s3ss10n",
    "email": "user@example.com"
  },
  "nested": [
    {
      "entity": "child",
      "data": {
        "is": "subordinated"
      }
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "7e3506b60bc51e25",
  "trigger": "load",
  "entity": "page",
  "action": "view",
  "timestamp": 1700000104,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
```

Out

```
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.

Event

```
{
  "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": "bd20cab5bb994bf2",
  "trigger": "test",
  "entity": "user",
  "action": "delete",
  "timestamp": 1700000107,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
```

Mapping

```
{
  "silent": true,
  "settings": {
    "destroy": true
  }
}
```

Out

```
trackClient.destroy("us3r")
```

### Mapped properties

A data mapping transforms the event payload into Customer.io track properties for an order.

Event

```
{
  "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": "f3609a924b9a0aae",
  "trigger": "load",
  "entity": "order",
  "action": "complete",
  "timestamp": 1700000102,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
```

Mapping

```
{
  "name": "purchase",
  "data": {
    "map": {
      "order_id": "data.id",
      "value": "data.total",
      "currency": "data.currency"
    }
  }
}
```

Out

```
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.

Event

```
{
  "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": "1a2ac2b0a581d2ea",
  "trigger": "load",
  "entity": "order",
  "action": "complete",
  "timestamp": 1700000101,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
```

Mapping

```
{
  "name": "purchase"
}
```

Out

```
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.

Event

```
{
  "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": "888aa71e259ad220",
  "trigger": "load",
  "entity": "page",
  "action": "view",
  "timestamp": 1700000106,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
```

Mapping

```
{
  "silent": true,
  "settings": {
    "page": {
      "map": {
        "url": "data.url",
        "referrer": "data.referrer"
      }
    }
  }
}
```

Out

```
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.

Event

```
{
  "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": "760585c2359e1354",
  "trigger": "test",
  "entity": "user",
  "action": "suppress",
  "timestamp": 1700000108,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
```

Mapping

```
{
  "silent": true,
  "settings": {
    "suppress": true
  }
}
```

Out

```
trackClient.suppress("us3r")
```

### Unsuppress person

A user unsuppress event resumes messaging for a previously suppressed Customer.io profile.

Event

```
{
  "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": "e7a84a6532e6ac95",
  "trigger": "test",
  "entity": "user",
  "action": "unsuppress",
  "timestamp": 1700000109,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
```

Mapping

```
{
  "silent": true,
  "settings": {
    "unsuppress": true
  }
}
```

Out

```
trackClient.unsuppress("us3r")
```

### User login identify

A user login triggers only a Customer.io identify call with profile attributes, skipping the track.

Event

```
{
  "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": "ce29dd48efc56dfa",
  "trigger": "test",
  "entity": "user",
  "action": "login",
  "timestamp": 1700000105,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
```

Mapping

```
{
  "silent": true,
  "settings": {
    "identify": {
      "map": {
        "email": "data.email",
        "first_name": "data.first_name",
        "plan": "data.plan"
      }
    }
  }
}
```

Out

```
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[​](#customer-lifecycle "Direct link to 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[​](#transactional-messaging "Direct link to 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.
