---
title: "Customer.io"
url: "https://help.flows.world/en/article/customerio"
language: "en"
last_updated: "2026-10-28T14:42:34.761Z"
---

# Customer.io

## Customer.io

## Introduction

The **Customer.io** App for Flows enables you to connect your workflows to Customer.io, a messaging and customer engagement platform. This integration allows you to create and update people, track behavioural events, trigger journeys, send transactional email and push, and read your campaigns, broadcasts and segments, directly from your Flows without writing complex code. With this App, you can build automated workflows that keep Customer.io in sync with player and customer activity in real time, perfect for registration onboarding, behavioural campaign triggers, and transactional messaging.

### What is Customer.io?

Customer.io is a customer engagement platform that stores a profile for every person, records the events they generate, and uses both to drive automated messaging journeys across email, push, SMS and in-app. Key features include:

-   People profiles with unlimited standard and custom attributes
-   Event tracking that triggers and branches campaigns
-   Campaigns, API-triggered broadcasts and segments
-   Transactional email and push with reusable templates
-   Suppression and device management for compliance and mobile messaging

## Getting Started

### Prerequisites

Before using the Customer.io App, you will need:

-   A Customer.io account
-   Your Track API Site ID and Track API Key
-   An App API Key

### Finding Your Credentials

1.  Log in to Customer.io and go to **Settings > Account Settings > API Credentials**
2.  Open the **Track API Keys** tab and copy the Site ID and the API Key. These are your `siteId` and `trackApiKey`
3.  Open the **App API Keys** tab, click **Create App API Key**, name it, and copy the generated token. This is your `appApiKey`

**Recommendation:** The Site ID and Track API Key travel as HTTP Basic authentication, and the App API Key as a Bearer token. Keep all three secure, because anyone holding them can read, message and delete every person in your workspace. The App API Key is shown only once when created, so store it before closing the dialog.

**Workspace credentials:** Use the credentials from the Customer.io workspace you want to connect. Credentials from a different workspace will return 401 on every stage.

### Available Stages

| Stage | Method | Description |
| --- | --- | --- |
| Create or Update Person | PUT | Create a person or update one, sending any attributes as a JSON object |
| Identify Person | PUT | Legacy: create or update a person with an email address only |
| Track Event | POST | Record a named event against a known person |
| Track Anonymous Event | POST | Record an event with no person attached |
| Delete Person | DELETE | Permanently delete a person and their history |
| Suppress Person | POST | Stop all messaging to a person without deleting them |
| Unsuppress Person | POST | Resume messaging to a suppressed person |
| Add or Update Device | PUT | Register a mobile push token against a person |
| Delete Device | DELETE | Remove a push token from a person |
| Add to Segment | POST | Add people to a manual segment |
| Remove from Segment | POST | Remove people from a manual segment |
| Trigger Broadcast | POST | Start an API-triggered broadcast |
| Send Transactional Email | POST | Send a one-off email from a transactional template |
| Send Transactional Push | POST | Send a one-off push from a transactional template |
| Look Up Person by Email | GET | Find people by email address |
| Get Person Attributes | GET | Read every attribute stored on a person |
| List Campaigns | GET | Return every campaign |
| List Broadcasts | GET | Return every broadcast |
| List Segments | GET | Return every segment |
| List Transactional Messages | GET | Return every transactional template |

## Authentication

Customer.io splits its functionality across two APIs with two different credentials. The App handles this for you: the connection collects all three values once, and each stage uses the right pair. There is no OAuth, no token refresh and no expiry handling.

**Track API:** Used by every stage that pushes data in: Create or Update Person, Identify Person, Track Event, Track Anonymous Event, Delete Person, Suppress and Unsuppress Person, Add or Update Device, Delete Device, Add to and Remove from Segment.

```plaintext
Authorization: Basic base64(siteId:trackApiKey)
```

**App API:** Used by every stage that sends a message or reads data back: Trigger Broadcast, Send Transactional Email, Send Transactional Push, Look Up Person by Email, Get Person Attributes, and all four List stages.

```plaintext
Authorization: Bearer <appApiKey>
```

## Create or Update Person

The Create or Update Person stage creates a person in Customer.io or updates an existing one, sending any attributes you want as a single JSON object. This is the stage to use on a registration event.

### Input Parameters

| Parameter | Variable | Required | Description |
| --- | --- | --- | --- |
| ID | `{{id}}` | Yes | The unique identifier of the person in Customer.io, normally your own user ID from the registration event. If no person has this ID one is created, if one exists it is updated. Use one identifier type consistently across your workspace or you will create duplicate people. |
| Payload | `{{payload}}` | Yes | JSON object with the attributes to set on the person. Keys are the Customer.io attribute names as they appear in your workspace; values can be fixed text or placeholders from earlier stages in your flow. Custom attributes work like standard ones, because Customer.io creates them on first use. Attributes you do not send keep their current value. Always include email, or the person exists but cannot be messaged. |

### Output

| Field | Description |
| --- | --- |
| success | true when Customer.io accepted the upsert |
| id | The person ID you sent. Pass it to Track Event, Suppress Person or Add or Update Device |

### Usage Example

| Parameter | Value |
| --- | --- |
| id | `{{event.userId}}` |
| payload | `{"email":"{{event.email}}","first_name":"{{event.firstName}}","country":"{{event.country}}","signup_date":"{{event.createdAt}}"}` |

### Important Notes

-   The Payload field is your mapping layer. The left side of each pair is the attribute name in Customer.io, the right side is the data coming from your flow. When a different client names the same attribute differently, you edit this one field in their flow and nothing else changes
-   Attributes are merged, not replaced. Sending `{"vip_level":"gold"}` later updates only that attribute and leaves the rest intact, so the same stage covers registration and every later profile update
-   The Payload must be a valid JSON object. A trailing comma or an unquoted key returns a 400
-   This stage supersedes Identify Person, which can only send an email address
-   If your workspace identifies people by both id and email, sending a new id with an email that already belongs to someone does not create a duplicate. Customer.io updates the existing person and assigns them the new id instead. This is a common source of confusion when testing: reusing the same email with different generated IDs updates one person repeatedly, it does not create multiple people
-   You can also set `unsubscribed:true` directly inside the Payload to suppress a person in the same call that updates their other attributes. This has the same effect as running Suppress Person separately, and can save a stage if you are already sending a Payload

## Identify Person

The Identify Person stage creates or updates a person with an email address only. It is kept for flows already using it, and new flows should use Create or Update Person.

### Input Parameters

| Parameter | Variable | Required | Description |
| --- | --- | --- | --- |
| ID | `{{id}}` | Yes | The unique identifier of the person in Customer.io |
| Email | `{{email}}` | Yes | The person's email address. Without it Customer.io cannot message them |

### Output

Returns `success`, `id` and `email`.

### Important Notes

-   This stage calls exactly the same Customer.io endpoint as Create or Update Person. The only difference is that it can send nothing but the email address

## Track Event / Track Anonymous Event

These stages record an event in Customer.io. Track Event attaches it to a known person; Track Anonymous Event records it with no person attached, for activity from visitors you have not identified yet.

### Input Parameters

| Parameter | Variable | Required | Description |
| --- | --- | --- | --- |
| ID | `{{id}}` | Yes (Track Event only) | The person the event belongs to. The person does not have to exist first, because Customer.io creates a profile for an unknown ID |
| Event Name | `{{eventName}}` | Yes | Name of the event, e.g. `purchase`, `signup`, `deposit`. This is the string you select when building a campaign trigger in Customer.io, so keep it stable, because renaming it breaks existing campaigns. Customer.io creates the event type on first use |

### Output

| Field | Description |
| --- | --- |
| success | true when the event was accepted |
| id / eventName | The values you sent, echoed back |

### Usage Example

| Parameter | Value |
| --- | --- |
| id | `{{event.userId}}` |
| eventName | `first_deposit` |

### Important Notes

-   Events are accepted asynchronously. A success response means Customer.io queued the event, not that a campaign has fired, so allow a few seconds before checking the person's Activity log
-   Anonymous events can only trigger campaigns that do not target a person, so use Track Event whenever you have an ID

## Delete Person / Suppress Person / Unsuppress Person

These three stages act on an existing person using their ID. Suppress stops all messaging while keeping the record; Delete removes the person entirely.

### Input Parameters

| Parameter | Variable | Required | Description |
| --- | --- | --- | --- |
| ID | `{{id}}` | Yes | The person's identifier in Customer.io |

### Output

All three return `success` and the `id` you sent.

### Important Notes

-   Delete Person is permanent and takes the person's event history with it. There is no undo
-   Suppress Person is the right choice for an unsubscribe or a GDPR objection: the person stops receiving messages but their history survives for reporting
-   A suppressed person is silently skipped by every campaign. If messages stop arriving for one user and nothing looks wrong, check whether they are suppressed
-   Deleting a person who does not exist still returns success, because these calls are idempotent

## Add or Update Device / Delete Device

These stages register or remove a mobile push token on a person, which is what makes push messaging possible for that user.

### Input Parameters

| Parameter | Variable | Required | Description |
| --- | --- | --- | --- |
| ID | `{{id}}` | Yes | The person who owns the device |
| Device Token | `{{deviceToken}}` | Yes | The push token issued by APNs or FCM. It comes from your own mobile app and is not visible anywhere in the Customer.io UI |
| Platform | `{{platform}}` | Yes (Add or Update Device only) | Must be exactly `ios` or `android`, lowercase. Any other value is rejected |

### Output

Add or Update Device returns `success`, `id` and `deviceToken`. Delete Device returns `success` and `id`.

### Important Notes

-   Push tokens rotate. Call Add or Update Device on every app launch rather than only at registration, or your push delivery will silently decay over time
-   Call Delete Device on logout, otherwise the next person to use that handset receives the previous user's notifications

## Add to Segment / Remove from Segment

These stages add or remove people from a manual segment in Customer.io.

### Input Parameters

| Parameter | Variable | Required | Description |
| --- | --- | --- | --- |
| Segment ID | `{{segmentId}}` | Yes | Numeric ID of a manual segment. Run List Segments to get it, or read it from the segment's URL in Customer.io |
| Customer IDs | `{{customerIds}}` | Yes | Comma-separated list of person IDs, e.g. `user_1,user_2,user_3`. Use the same identifier type as the ID parameter elsewhere |

### Output

Both return `success` and the `segmentId` you sent.

### Important Notes

-   Only manual segments can be edited through the API. A data-driven segment computes its own membership and the call will fail
-   Person IDs that do not exist are ignored silently rather than returning an error, so a success response does not prove every ID landed

## Trigger Broadcast

The Trigger Broadcast stage starts an API-triggered broadcast in Customer.io, which is the way to launch a Customer.io journey from a Flow.

### Input Parameters

| Parameter | Variable | Required | Description |
| --- | --- | --- | --- |
| Broadcast ID | `{{broadcastId}}` | Yes | Numeric ID of the broadcast to start. Run List Broadcasts to find it |

### Output

| Field | Description |
| --- | --- |
| id | Identifier of the triggered run, for correlating with Customer.io's delivery reporting |
| broadcastId | The broadcast ID you sent, echoed back |

### Important Notes

-   The broadcast must be created in Customer.io as an API-triggered broadcast. Triggering any other type returns 404, which reads like a wrong ID but is not
-   The broadcast must also be live in Customer.io. A draft returns 404 as well

## Send Transactional Email / Send Transactional Push

These stages send a one-off message rendered from a transactional template, such as receipts, password resets and shipping confirmations. They bypass campaigns entirely.

### Input Parameters

| Parameter | Variable | Required | Description |
| --- | --- | --- | --- |
| Transactional Message ID | `{{transactionalMessageId}}` | Yes | ID of a transactional template configured in Customer.io under Messages > Transactional. Run List Transactional Messages to get it |
| To | `{{to}}` | Yes (email only) | Recipient email address |
| Identifier ID | `{{identifierId}}` | Yes | The person ID the message is attributed to in Customer.io, normally the same value as the ID used elsewhere. Required even when To is set, because Customer.io logs the delivery against the person |

### Output

| Field | Description |
| --- | --- |
| delivery\_id | Customer.io's identifier for this send. Use it to find the message in Deliveries |
| queued\_at | Timestamp the message entered the sending queue |

A `delivery_id` means queued, not delivered. Sending happens asynchronously, so check Deliveries in Customer.io, or use a Customer.io webhook, if you need delivery confirmation.

### Important Notes

-   The template must be live. A draft template returns 400
-   Send Transactional Push reaches only people who have a device registered through Add or Update Device. With no device on the profile the call succeeds and nothing arrives
-   A suppressed person receives nothing, transactional messages included

## Look Up Person by Email / Get Person Attributes

These read stages find a person or read back everything stored on one, which is useful for checking a sync worked, or for branching a flow on an attribute Customer.io holds.

### Input Parameters

| Parameter | Variable | Required | Description |
| --- | --- | --- | --- |
| Email | `{{email}}` | Yes (Look Up Person by Email) | Exact email address to search for. This is not a partial match |
| ID | `{{id}}` | Yes (Get Person Attributes) | The person's identifier in Customer.io |

### Output

| Field | Description |
| --- | --- |
| results | Look Up Person by Email: the matching people |
| customer | Get Person Attributes: the person with every attribute stored on them |

`results` is an array. Flows cannot resolve array indexes, so add a Loop stage over the response if you need to read individual people out of it.

### Important Notes

-   The same email address can belong to more than one person if your workspace has been fed inconsistent identifiers, which is why this returns an array rather than a single record
-   Get Person Attributes returns custom attributes alongside standard ones, so it is the quickest way to confirm a Create or Update Person payload landed as intended
-   Get Person Attributes also returns `cio_id`, a permanent identifier Customer.io generates automatically and never changes. If you ever need to update a person's email or other identifier after it has been set, reference them using `cio_<the cio_id value>` in the ID field of another stage. This is the only way to change an identifier once it exists

## List Campaigns / List Broadcasts / List Segments / List Transactional Messages

These four stages take no parameters and return everything of their type in the workspace. Their purpose is to find the IDs the other stages need.

### Output

| Stage | Field | Feeds |
| --- | --- | --- |
| List Campaigns | campaigns | Reference only |
| List Broadcasts | broadcasts | Trigger Broadcast |
| List Segments | segments | Add to Segment / Remove from Segment |
| List Transactional Messages | messages | Send Transactional Email / Push |

Each of these fields is an array. Flows cannot resolve array indexes, so add a Loop stage over the response, or run the stage once by hand and copy the ID you need into the stage that consumes it.

### Important Notes

-   In practice these are run once during setup to collect IDs, not on every flow execution

## Use Cases

1.  **Registration sync:** A registration event fires Create or Update Person with the player's ID and a Payload mapping their registration fields onto your Customer.io attributes. Customer.io journeys take over from there.
2.  **Behavioural campaign triggers:** Call Track Event with `first_deposit` or `kyc_verified` and let Customer.io campaigns react, with no further logic in the Flow.
3.  **Profile enrichment over time:** Call Create or Update Person again whenever a player's tier, balance band or preferences change. Attributes merge, so each call only needs the fields that changed.
4.  **Transactional receipts:** After a payment, Send Transactional Email with your receipt template and the player's ID as Identifier ID.
5.  **Launch a journey from a Flow:** Run List Broadcasts once to find the ID, then call Trigger Broadcast whenever your Flow's conditions are met.
6.  **Unsubscribe and GDPR handling:** Suppress Person on an opt-out, Delete Person on an erasure request.
7.  **Mobile push lifecycle:** Add or Update Device on every app launch, Delete Device on logout, then Send Transactional Push for time-critical alerts.

## How to Test

1.  Install Customer.io from the marketplace and create a connection.
2.  Paste your Site ID, Track API Key and App API Key from **Settings > Account Settings > API Credentials** into the three connection parameters. They must all come from the same Customer.io workspace.
3.  Start with **List Segments**. It takes no parameters and needs nothing pre-existing, so it proves the App API Key works on its own.
4.  Run **Create or Update Person** with `id` = `flows-test-1` and `payload` = `{"email":"jane@example.com","first_name":"Jane","plan":"pro"}`. Open People in Customer.io and confirm the person exists with all three attributes. This proves the Track API credentials work.
5.  Run it again with `payload` = `{"plan":"enterprise"}` and confirm only `plan` changed while `email` and `first_name` survived. This is the merge behaviour.
6.  Run **Track Event** with the same id and `eventName` = `flows_test_event`, then check the person's Activity log.
7.  Run **Get Person Attributes** with that id to confirm the read path and the App API Key.
8.  Clean up with **Delete Person**.

Each endpoint also carries a pink note in the Designer canvas with its own test recipe and example values.

## Troubleshooting

| Symptom | Cause | Fix |
| --- | --- | --- |
| 401 on Create or Update Person, Track Event or any other data-push stage | Wrong Site ID or Track API Key, or credentials from a different workspace | Re-copy both from the Track API Keys tab of the workspace you are connecting |
| 401 on sends, triggers or List stages | Wrong or revoked App API Key | Create a new App API Key in your workspace and paste it into the connection |
| Person appears but never receives anything | The profile has no email address, or the person is suppressed | Include email in the Payload, and check the person is not suppressed |
| 400 on Create or Update Person | The Payload is not a valid JSON object | Check for trailing commas, unquoted keys and unescaped quotes inside values. A malformed object is rejected wholesale rather than partially applied |
| 404 on Trigger Broadcast | The broadcast is not API-triggered, or is still a draft | Confirm the ID with List Broadcasts and check the broadcast type in Customer.io |
| 400 on a transactional send | The template does not exist, is a draft, or requires variables that were not supplied | Confirm the ID with List Transactional Messages and check the template's required variables |
| Transactional push returns a delivery\_id but nothing arrives | The person has no device registered | Call Add or Update Device with the handset's push token first |
| Add to Segment succeeds but membership does not change | The segment is data-driven, or the person IDs do not exist | Use a manual segment, and confirm the IDs with Look Up Person by Email |
| Duplicate people in Customer.io | The same person was sent under two different identifier types | Pick one identifier, either your own user ID or email, and use it consistently in every stage |
