How to Sync Customer.io Data into PostHog

October 31, 2025

If you’re using Customer.io for running campaigns and PostHog for analytics, you’ve probably wondered how to connect the two. In other words - how do you see in one place, what happens after someone gets an email, push notification, or Slack message?

This guide walks you through exactly how to get that data flowing from Customer.io into PostHog - without overcomplicating things.

Why You’d Want to Do This

Customer.io is where engagement starts. It sends the right message at the right time.


PostHog is where insight happens. It helps you understand what users actually do next.

By connecting them, you get a full picture - for example:

  • Did that product launch email get opened - and did the user convert afterwards?
  • Which notification type (email, push, Slack) drives the most re-engagement?
  • Are your triggered campaigns actually moving the retention needle?

Once you have all your message events inside PostHog, you can answer those questions without exporting CSVs or manually joining data.

Step 1: Pull Data from Customer.io

Customer.io exposes a simple REST API for pulling campaign and message data.

1.1. List all campaigns
GET https://api.customer.io/v1/campaigns
Authorization: Bearer <CIO_APP_API_TOKEN>
Accept: application/json

Example response:
{
  "campaigns": [
    {
      "id": 3,
      "name": "Onboarding - Week 2",
      "type": "triggered",
      "created": 1697011200
    }
  ]
}

Each campaign has its own ID, which you’ll use in the next call.

1.2. Get messages for a campaign
GET https://api.customer.io/v1/campaigns/{campaign_id}/messages
Authorization: Bearer <CIO_APP_API_TOKEN>
Accept: application/json

Example response:
{
  "messages": [
    {
      "id": "dgSW-woAAJq2nQGZtp0BAZaInGWsBG7c9cfSz6UuCw==",
      "type": "email",
      "customer_id": "2ded2d0e-1a8b-44f2-b1bc-65f89ebdc321",
      "metrics": {
        "sent": 1746049000,
        "delivered": 1746049001,
        "human_opened": 1746063121,
        "clicked": 1746721399
      }
    }
  ]
}

Those UNIX timestamps (metrics.sent, metrics.clicked, etc.) tell you when each event happened.

Depending on the channel, you might also see metrics like:

  • pushed, opened, clicked for push notifications
  • slack_sent, slack_clicked for Slack campaigns
  • triggered, completed for webhooks

Step 2: Map Customer.io Metrics to PostHog Events

Every metric you see in Customer.io can be represented as a PostHog event.Here’s a simple mapping you can start with:

Channel Customer.io Metric PostHog Event Name
Email delivered cio:email_delivered
Email human_opened cio:email_opened
Email clicked cio:email_clicked
Push pushed cio:push_sent
Push opened cio:push_opened
Push clicked cio:push_clicked
Slack slack_sent cio:slack_sent
Slack slack_clicked cio:slack_clicked

How would you attribute events to the right person in PostHog? Use the Customer.io person ID as the PostHog distinct_id - that’s your unique user reference. 

This is based on the assumption that you have a “Destination” data pipeline set up for Customer.io in PostHog to give Customer.io persons a PostHog identifier.

With the records pulled from Customer.io, you can transform them in PostHog event format and send it to PostHog using its API. 

PostHog’s /batch API is designed for high-volume ingestion. You can send up to 500 events at a time, and gzip-compress the payload for efficiency. Here’s a python snippet:

import gzip, json, requests
payload = {
    "api_key": "<PH_API_KEY>",
    "batch": [
        {
            "event": "cio:email_delivered",
            "distinct_id": "2ded2d0e-1a8b-44f2-b1bc-65f89ebdc321",
            "timestamp": "2025-10-14T17:13:34Z",
            "properties": {
                "source": "customer_io",
                "channel": "email",
                "campaign_id": 3,
                "campaign_name": "Onboarding - Week 2",
                "message_id": "dgSW-woAAJq2nQGZtp0BAZaInGWsBG7c9cfSz6UuCw=="
            }
        }
    ]
}
gz = gzip.compress(json.dumps(payload).encode("utf-8"))
requests.post(
    "https://app.posthog.com/batch/",
    headers={"Content-Type": "application/json", "Content-Encoding": "gzip"},
    data=gz
)

Once it’s in PostHog, you’ll see these events side-by-side with your product analytics.

Step 4: Make It Incremental (and Safe to Re-run)

If you’re running this regularly (say, daily), you’ll want to avoid sending duplicates.

The easiest way is to keep a small tracking table in your data warehouse - like Redshift, BigQuery, SQLite, etc.

CREATE TABLE IF NOT EXISTS public.cio_event_log (
    message_id   VARCHAR(255),
    metric_type  VARCHAR(50),
    campaign_id  INT,
    created_at   TIMESTAMP DEFAULT GETDATE(),
    PRIMARY KEY (message_id, metric_type)
);

Before sending, check if the (message_id, metric_type) already exists.

If it does, skip it.

If not, send it to PostHog and log it in the table.

This makes your job run over and over again without ever resending old data.

Step 5: Ensure Privacy, Security, and Best Practices

A few critical points to keep in mind while setting up your pipeline:

  • Never send emails or phone numbers. Use customer_id or a hashed identifier.
  • Throttle your API calls. Customer.io’s API has rate limits (~10 requests/sec).
  • Use gzip and batching for efficient delivery to PostHog.
  • Log every batch you send - it makes debugging painless.

Step 6: Automate it

Great! You have the ETL logic made. Now we need to set up a running schedule to get new data in PostHog as it comes - preferably run every 24 hours.

If you have AWS set up, we suggest running this pipeline as a Glue job. Glue can:

  • Fetch Customer.io data daily
  • Compare it with your Redshift table
  • Push only new events to PostHog
  • Log completion metrics and notify Slack on failures

It’s reliable, serverless, and can handle hundreds of thousands of events per run.

Otherwise, you can set this up easily on any server that supports Python execution, making requests and Cron jobs.

Step 7: How to Use this Data in PostHog?

Once you have these events in, you can make all kinds of insights to get the full picture. The most useful insight you can make is a “Funnel”. 

With a Funnel, you can add steps - for example:

Email Delivered → Email Opened → Feature Landing page → Feature Adoption

You’ll be able to track how many persons entered the funnel, conversion and drop-off numbers at every step, conversion time, and a whole lot more!

The Takeaway

Customer.io is how you talk to users.

PostHog is how you understand them.

By syncing these two, you connect your messaging layer to your product insights - so you’re not guessing what impact campaigns have. You’re measuring it.

Recent Blogs

calendar icon

November 06, 2025

calendar icon

October 31, 2025

calendar icon

October 18, 2025

calendar icon
hubspot logo

How to Setup PostHog with Clay and Cloud Functions

See how you can create a full loop with PostHog, Clay and Cloud functions for your Product.

calendar icon
hubspot logo

How to Bring Your Database Data into PostHog

See how you can use rich data sitting in your product or marketing database in PostHog for Analytics.

calendar icon
hubspot logo

Google Ads to BigQuery Setup for PostHog Integration

Check out how you can set up Google Ads data to be synced with BigQuery for PostHog analytics.