Triggering Atomic from Customer.io
This guide covers both directions of a round-trip between Customer.io and Atomic.
- Customer.io to Atomic: start an Action Flow when a customer reaches a step in a journey.
- Atomic to Customer.io: record what the customer did with the card, so the journey can react to it, or it can be stored for later use.
How it works
Customer.io's Send Data action is a general purpose webhook. Atomic's Webhook trigger is a general purpose inbound endpoint with a payload mapper. The two can be connected to trigger Atomic Action Flows.
Customer.io signs every webhook with an HMAC-SHA256 signature, and Atomic's signed request verification can be configured to check that signature. This means you get an authenticated connection.
To follow the steps in this guide, you will need:
In Atomic:
- Edit-level permissions in your Atomic environment, so you can add and configure a Webhook trigger on an Action Flow.
- For the return leg, a Customer.io credential holding a Customer.io App API key.
In Customer.io:
- Permission to edit automations or broadcasts in the workspace. Note that automations were previously called campaigns in Customer.io.
- Access to Settings > Workspace Settings > API & Webhook Credentials, to read your webhook signing key.
Before you start: match your customer identifiers
Atomic's Webhook trigger targets customers using targetUserIds, and that value must be the ID that Atomic knows the customer by. Customer.io needs to send an ID that Atomic recognizes.
There are two options:
- Your identifiers already match. If the
idyou use to identify people in Customer.io is the same ID you use for customers in Atomic, you can send{{customer.id}}. This is common when both systems are fed from the same internal customer record. - Store the Atomic ID as a Customer.io attribute. If the identifiers differ, add the Atomic customer ID to each Customer.io profile as a custom attribute, for example
atomic_id, and send{{customer.atomic_id}}instead.
If you are not sure which applies to you, check a single customer in both systems and compare. Getting this wrong produces requests that are accepted but match no customer.
Click your avatar in the bottom left of the Atomic Workbench and select Copy my test customer ID. Use this value while testing so the cards come to you.
Part 1: Triggering Action Flows from Customer.io
Step 1: Add a Webhook trigger in Atomic
In the Atomic Workbench:
- Open the Action Flow you want Customer.io to trigger, or create a new one.
- Add a Webhook trigger to the canvas. Atomic generates a unique inbound URL. Copy this, you will need it in Customer.io.
- Leave the trigger in Test mode. It stays in Test mode until you have finished testing and are ready to generate live cards.

The endpoint follows this shape, where the final segment is a secret:
https://<my-org>.customer-api.atomic.io/<my-env>/connector/<flow-id>/<secret>
Anyone holding the full URL can trigger your Action Flow if you don't configure request verification. Store it somewhere safe, keep it out of tickets and screenshots, and recreate the trigger if you think it has been exposed.
See Webhook triggers for full details on configuration and Test versus Live mode.
Step 2: Add a Send Data action in Customer.io
In Customer.io, open the Automation or broadcast you want to trigger from, and add a Send Data action at the point in the workflow where the card should be sent.
Configure it as follows:
- Set the method to POST and paste your Atomic Webhook trigger URL.
- Leave the Content-Type header as
application/json. - In the body, build a payload in the shape Atomic expects.
Atomic accepts the following shape directly, so if you build it in Customer.io you do not need any mapping on the Atomic side:
{
"targetUserIds": ["{{customer.atomic_id | strip}}"],
"flowInvocationId": "renewal-{{customer.atomic_id | strip}}-{{customer.policy_id | strip}}",
"variables": {
"firstName": "{{customer.first_name | escape | strip}}",
"renewalDate": "{{customer.renewal_date | escape | strip}}",
"premiumAmount": "{{customer.premium_amount | escape | strip}}"
}
}
Only targetUserIds is required. The variables object maps onto the context variables in your Action Flow, and flowInvocationId protects you against duplicate sends, which is covered in Stop duplicate sends below.

Use the Preview panel to check your work before saving. A successful call returns 200 OK with a data object describing the Action Flow that started, as shown above.
Each customer who reaches this step generates one request. Customer.io does not batch journey members into a single call, so if you are adding this to a broadcast that targets a large segment, expect a request per customer rather than one bulk request.
Step 3: Map the payload, if you need to
If you built the body in Atomic's shape as shown above, skip this step.
You may prefer to keep the Customer.io body simple and let Atomic do the transforming, particularly if several journeys post slightly different payloads to the same trigger. In that case, send whatever shape suits you:
{
"atomic_id": "{{customer.atomic_id | strip}}",
"policy": "{{customer.policy_id | strip}}",
"renews_on": "{{customer.renewal_date | strip}}"
}
Then map it in Atomic:
- Select the Webhook trigger on the canvas and click Edit payload mapping.
- Click Get new sample, then send a test from Customer.io. The first attempt fails because no mapping exists yet, but it populates the Sample payload body field.
- With Basic selected, set the target user Type to
pathand the Path tobody.atomic_id. Atomic namespaces the incoming request body underbody, so your fields are addressed asbody.<field>. Map the remaining fields to your Action Flow variables the same way. For anything more involved, switch to the JavaScript option.
The Output panel at the bottom shows the result of your mapping, so you can confirm it produces the targetUserIds and variables Atomic expects before saving.

Step 4: Secure the connection
The trigger URL contains a hard-to-guess token, so on its own it acts as a shared secret. That is a reasonable baseline, but it offers no protection against a replayed or tampered request. Add one or both of the following.
Verify signed requests
Customer.io signs every webhook with an HMAC-SHA256 signature over the string v0:<timestamp>:<body>, sending the result in an X-CIO-Signature header alongside an X-CIO-Timestamp header. Atomic can verify this directly.
- In Customer.io, go to Settings > Workspace Settings > API & Webhook Credentials and open the Webhook signing keys tab. Copy the signing key.
- In Atomic, select the Webhook trigger, and under Verify incoming request paste the signing key into Authentication secret.
- In the Authentication field, enter the following configuration:
{
"algorithm": "sha256",
"digest": "hex",
"data": {
"signature": {
"header": "x-cio-signature"
},
"timestamp": {
"header": "x-cio-timestamp"
}
},
"template": "v0:[timestamp]:[payload]"
}
Atomic replaces [timestamp] with the value from the X-CIO-Timestamp header and [payload] with the raw request body, then hashes the result with your signing key and compares it to the X-CIO-Signature header. Because Customer.io puts the signature and the timestamp in separate headers, no parsePrefix is needed.
