Set up universal links
We wrap tracked links in a URL that points to a domain we control and can use to log your clicks. After we log a click, we redirect people to the URL you originally used for the link. Universal links don’t work this way, so we introduced a special liquid tag—{% cio_link_id %}
—to help you self-manage universal links.
These instructions only apply to links in emails
You can use Universal links in all message channels, but the manual tracking steps we describe here are only viable in emails.
- For in-app messages, links are automatically tracked by the web and mobile SDKs.
- For SMS, the generated
cio_link_id
is too long for an SMS and breaks the URL. This is a known issue and an open feature request to add a link shortener. - For push, use the
Opened
metric, which is the push equivalent toClicked
.
Tracking universal links in Customer.io
Currently, tracked links in Customer.io are always wrapped in a URL that points to a domain we control and can use to log your clicks. Once logged, we then redirect people to the URL you originally used for the link.
When using universal links, however, this type of link tracking presents a problem because your tracked links are no longer pointing to a path you have configured in your app site configuration files. As mentioned above, tracked links in Customer.io will point to a domain we control instead and then we redirect people to the final destination. For various technical reasons, these redirected links do not always cause the person’s device to route universal links to the intended app no matter what you’ve set in your app site configuration files. These are things that are out of your control (and ours), such as the devices themselves and the apps that are installed on them.
To help combat this issue, we’ve made it possible for you to self-manage the tracking of your universal links by using a special liquid tag, {% cio_link_id %}
.
To log the clicks of your universal links within Customer.io you will have to
- configure any universal links to be untracked in our system
- and then pass the clicked links through to us programmatically for tracking purposes
Here’s how to do that:
First, configure any universal links to be untracked in our system
Per your app site configuration files for Android and iOS, any links that you expect to be deep linked need to look like the link shown in the image below.


Two components must be in the link’s code for this to work as expected:
link_id={% cio_link_id %}
This gives us a way to send you the link id as a query parameter value so that you can pass the link to us for tracking. We’ve used link_id in this example but the parameter name can be anything your system will recognize as the holder of the link id token you need to send back to us.class="untracked"
This ensures that we don’t attempt to wrap this link like we do for our conventional link tracking. You may add other classes as well if needed, but one of them must be “untracked” to ensure that we don’t alter the link.
When your message is sent, links configured like the one pictured above will point to a URL that looks something like:
http://yourwebsite.com/confirm/?link_id=eyJlbWFpbF...928bf
(Note, we’ve truncated the link id token in that example because it is normally quite long.)
Then, pass the links through to us for tracking purposes
Next, from within your app, you will need to retrieve the link_id
token from the request URL and pass it along to us by sending a POST request from within your app to a URL that will look something like this one:
https://<your-tracking-domain>/click/<link_id>
The <link_id>
part of the POST URL above is where the value pulled from the link_id
parameter will go.
The <your-tracking-domain>
part of the POST URL above needs to be replaced with whatever domain is being used in your workspace’s deliverability settings for the domain being used to send your message.
You can locate your deliverability settings using the left-hand menu in your account and going to Workspace Settings > Email > Manage Domain > Link Tracking. Then you will click the “Configure” button on the line for the message’s sending domain. (I.e., the domain being used in the FROM address selected for your message.)
If you have configured a CNAME record in your domain’s DNS records for use in Customer.io as your custom link tracking domain then you will replace <your-tracking-domain>
above with that domain.
For example, if the custom link tracking domain in your settings says email.yourdomain.com
AND we have verified your CNAME record for that domain then you would perform an HTTP POST to URLs that are formatted like: http://email.yourdomain.com/click/eyJlbWFpbF...928bf
so that we can log the click.
If we have NOT verified your CNAME record for the sending domain then you will replace <your-tracking-domain>
above with e.customeriomail.com
instead so that we can log the click.
Note that the tracking domain you send this to must match the domain you configured in the deliverability settings discussed above. Each of our link ids are cryptographically signed by Customer.io using your tracking domain (for security purposes) so the request must arrive on the same domain for validation.
Assuming the token we receive is valid, we will respond with a 200 OK
response, and register a click for the associated message. You will see these in your account as “Clicked Email” events in your activity logs. (There, the href
value will contain our special replacement (CIO--LINKID
) instead of the long value, which is your indication that it was a universal link.


You can also look at Top Clicked Links section found on the relevant campaign’s Overview tab.


A few things to note
Circular dependencies
We don’t support the use of {% cio_link_id %}
inside tracked links because it causes a circular dependency, where in order to generate the link_id
we need to know the full value of the link, but in order to know the full value of the link we need to generate the link_id
.
Deep links vs. universal links
Even though they are different things, the terms “deep links” and “universal links” are often used interchangeably. Here, you’ll find a brief description of each.
Deep links
A deep link is any link that is meant to take a user to content that is located deeper into a website or app than the homepage or home screen. Your app is meant to define a URI scheme for deep links that can be used to route people appropriately when they click on them. For example, the URL fb://profile/33138223345
can open Wikipedia’s profile in the Facebook app, while fb://
may just open the Facebook app to its default screen.
The downside to URI schemes like this is that there’s no backup plan if the person clicking the link doesn’t have the intended app installed on the device being used to click. The lack of a fallback makes it very difficult to ensure that your URI scheme will always open deep links in the app and manner you expect them too.
Universal links
As an elegant solution to the no-fallback issue mentioned above, Apple introduced universal links in iOS 9. Universal links are formatted as regular web links (http://yourdomain.com
) and you create a configuration file that defines which paths are meant to be handled by your app. When a link points to one of your configured paths, iOS will lookup which app is registered for the link’s domain. If that app is installed on the device being used, it will open as a result of the click and it is up to the device to display the appropriate content. Otherwise, if the app is not installed on the device, the user’s web browser will attempt to open the link.