User Attributes

There are two types of attributes in Customer.io: Customer and Event attributes.

With Customer attributes, you’re sending us information about your users: things like first_name, is_account_manager or num_projects—any information that is important for your business. Event attributes are information that’s included in your event data, such as the information specific to a particular purchase.

Attributes can be a string, boolean or a number, and can be used to create data-driven segments or to include information in your emails. You will also be able to use these attributes in emails that you send by typing {{customer.first_name}} in your email. Any email can access a customer attribute, but event attributes are available only in event triggered emails.

Every customer attribute is stored under a customer object, so you can access any attribute with {{customer.ATTRIBUTE_NAME}}.

Every event attribute is stored under an event object, so to access those attributes in your event triggered emails you’ll use {{event.ATTRIBUTE_NAME}} (the actual word event, not your event name).

How do I send you user attribute data?

Pass customer attributes through the _cio.identify() function in the Javascript snippet. Alternatively, you can send user attributes using the REST API.

Here’s an example of how to send custom user attributes:

<script type="text/javascript">
    _cio.identify({
      id:         'userid_34', // must be unique per customer
      email:      'customer@example.com',
      created_at: 1333688268, // seconds since the epoch (January 1, 1970)

      // Custom user attributes
      first_name:  'Joe',
      plan_name:  'free',
      is_account_manager: true,
      num_projects:  15
    });
</script>

Internal attributes

We reserve the use of all user attributes beginning with an underscore _ for internal use only. As a result, their behavior is undefined and likely not what you expect: internal user attributes aren’t eligible for segmentation, searching, or triggering of campaigns.

For example: if you have a segment for “Americans” built on the attribute country being equal to “America”, that will work. If you instead use _country, you can still send us that attribute, but any customers with it will not be eligible for segmentation, no matter what its value.

How do I create segments based on user attributes?

Once you are sending us custom attributes, you’ll have a lot of flexibility in how you can use them to create data-driven segments. Here’s a simple example for the attribute is_account_manager:

attributes sample 1

You can also set the numeric value of an attribute:

attributes sample 2

In fact, there’s a great deal of flexibility in how you can define your segments:

attributes sample 3

You can also check whether an attribute is between two values. For example, if you want to isolate customers with an NPS score of 7-8 (inclusive), you can do so with the “is between” option in the attribute dropdown:

"attribute is between"

How do I create segments based on event attributes?

If you’re including event data in your events, you’ll be able to segment based on that information. For example, if you’d like to know who bought a t-shirt:

event attributes sample 1

You’ll be able to segment by one piece of event data in each condition.

Also, if you want to know who bought a t-shirt recently (within the last month, maybe), you can do that too. Click Refine and add your timeframe:

event attributes sample 2

Important things to know:

Using attributes in messages:

Customer attributes come from the user’s profile and are available in any message type. They are pulled into your message content by using Liquid code like {{ customer.attribute_name}} in your message templates.

Event attributes come from the data you include in the events you send and are only available in event-triggered campaigns. They are pulled into your message content by using Liquid code like {{ event.attribute_name}} in your message templates.

Attribute data types:

Currently, attribute values in Customer.io are only simple strings, booleans or numbers.

We do, however, make your strings available as arrays or JSON objects inside your email templates for convenience (if they are correctly formatted as such), but we do not currently distinguish them as being different data types such as arrays and dictionaries, etc. You can use Liquid templating in your email templates to output values from your JSON values like: {{ customer.city.slug }} but you cannot access these nested values in the same manner when segmenting or filtering.

Formatting your attributes:

Let booleans be booleans, not strings. For example:

Good: is_account_manager: true
Bad: is_account_manager: 'true'

The boolean (true/false) shouldn’t have quotation marks around it.

Also, let numbers be numbers, not strings:

Good: num_projects: 15
Bad: num_projects: '15'

Chronology of attribute updates:

Attribute updates (identify() API calls) are processed in the order we receive them. However, due to various network conditions, we can’t guarantee that we will receive them in the order you send them. To ensure your attributes end up with the values you intend, you’ll want to add an additional attribute to each API call named _timestamp. The _timestamp value should be a UNIX timestamp that represents the date and time the update was made.

When we receive multiple attribute updates for the same user:

  • If the updates do not include a _timestamp value, the attribute values in the last update we process will prevail.
  • If the updates include a _timestamp value, the attribute values in the update having the newest _timestamp will prevail.
  • If the updates include the same _timestamp value, the attribute values in the first update will prevail. Values for existing attributes sent in subsequent updates will be ignored. Values for attributes sent in subsequent updates - that were not sent in the first update - will not be ignored. They will be added if they do not already exist. If they do already exist, they will be updated so long as the _timestamp value for this call is newer than the timestamp of the last update those attributes were a part of.

Please note that this is accurate down to the second. If requests are sent within microseconds of each other, there is still the potential that they could be out of order.

Here is an example set of API calls containing a _timestamp value:

SENT FIRST

curl -i https://track.customer.io/api/v1/customers/test-user-id \
-X PUT \
-u site-id:api-key \
-d _timestamp=1522081942 \
-d Attr1='attr1_val3_sent1st_timestamp=1522081942(newer)'

SENT SECOND

curl -i https://track.customer.io/api/v1/customers/test-user-id \
-X PUT \
-u site-id:api-key \
-d _timestamp=1522081941 \
-d Attr1='attr1_val1_sent2nd_timestamp=1522081941(older)' \
-d Attr2='attr2_val2_sent2nd_timestamp=1522081941(older)'

And here is the final outcome: screenshot

Notice we did not drop the second request with the older _timestamp value. Instead, we still processed the second request but only updated the attribute (Attr2) that did not already exist from a call that had a newer _timestamp value.