Python source
Updated August 17, 2026How it works
Our python library helps you record source events from your python server code. Requests from your python app go to our servers, and we route your data to your destinations.
This library uses an internal queue so that your identify and track calls are non-blocking and fast. It also batches requests and flushes asynchronously to Customer.io’s servers.
Like our other libraries, you can log anonymous activity—track and page events—with an anonymousId. When you identify a profile, you can pass the anonymousId and we’ll associate the anonymous activity with the identified profile.
Getting Started
-
Go to Integrations. In the Directory tab, pick the Python Data In integration.
-
Give the source a Name and click Complete Setup. The name is simply a friendly name to help you find and recognize your source in Customer.io.
-
Install the python library. If you use a system to manage dependencies, you should pin the library version to
1.0.Xto avoid breaking changes when we make updates.pip install customerio-cdp-analytics -
Import the library in your app and set your
write_keybefore making anyanalyticscalls. If you’re in our EU data center, you can also set thehostparameter tohttps://cdp-eu.customer.io.from customerio import analytics analytics.write_key = 'YOUR_WRITE_KEY' # If you're in our EU data center # analytics.host = 'https://cdp-eu.customer.io'
Now you’re ready to make calls to Customer.io!
The default initialization settings are production-ready and will queue individual analytics calls. A separate background thread is responsible for making the requests to Customer.io, so calls to the library won’t block your program’s execution.
If you’re in our EU data center
You’ll need to set the host parameter to our EU URL (https://cdp-eu.customer.io). Note that our EU regional endpoints account for the location of your data in Customer.io; they don’t account for the locations of your sources and destinations.
from customerio import analytics
analytics.write_key = 'YOUR_WRITE_KEY'
analytics.host = 'https://cdp-eu.customer.io'
Enable automatic geolocation support
You can automatically geolocate profiles when you identify them and pass their IP addresses in the context.ip field in your identify requests. This helps you gather information about your audience’s location and time zone so you can schedule messages at the right times or send messages relevant to their communities.
If you’ve already set up your integration to capture IP addresses, and you’ve enabled the workspace-level Automatic Geolocation Data Collection setting, you can enable geolocation for your integration.
After you set up your integration, go to your integration’s Settings tab and turn on the Enable Geolocation setting.
Development settings
By default, the python library is set to queue and send requests directly to Customer.io. But, while you’re integrating this library, you should enable some settings to help you troubleshoot problems.
- Use
analytics.debugto log debugging information to the python logger. - Set an
on_errorhandler to print the response you receive from our API.
import logging
def on_error(error, items):
print("An error occurred:", error)
# You need to configure a logger, in order to see the debugging information.
logging.basicConfig(level=logging.WARNING)
analytics.debug = True
analytics.on_error = on_error
You can also prevent the library from sending data to Customer.io during testing. This can save you the trouble of cleaning out bogus data later.
analytics.send = False
Identify
You should call A key-value pair that you associate with a person or an object—like a person's name, the date they were created in your workspace, or a company's billing date etc. Use attributes to target people and personalize messages.identify when your customers create an account, log in, or otherwise identify themselves. The identify method tells you who the current website or app visitor is and lets you set or update unique traits
You can send an identify call with an anonymous ID and/or user ID. To associate anonymous activity with an identified profile, send both IDs in a single call.
- Anonymous ID only: This assigns traits to a profile before you know who they are.
- User ID only: Identifies a user and sets traits.
- Both user ID and anonymous ID: Associates the data sent in previous anonymous
page,track, andidentifycalls with the person you identify by user ID.
analytics.identify('f4ca124298', {
'email': 'cool.person@example.com',
'first_name': 'cool',
'last_name': 'person'
})
- userIdstringThe unique identifier for a person. This value should be unique across systems, so you recognize the same person in your sources _and_ destinations.
- anonymousIdstringA unique substitute for a User ID in cases when you don’t have an absolutely unique identifier. Our libraries generate this value automatically to help you track people before they sign up, log in, provide their email, etc.
- Additional properties that you know about a person. We've listed some common/reserved traits below, but you can add any traits that you might use in another system.
- A dictionary of context about a source call/event, like the user’s IP address or locale. Context is automatically collected by our source libraries.
- Contains a list of booleans indicating the integrations that are enabled (true) or disabled (false). By default, all integrations are enabled (returning an empty object). Set
"All": falseto reverse this behavior. - timestampstring(date-time)The ISO-8601 timestamp when the event originally took place. This is mostly useful when you backfill data past events. If you're not backfilling data, you can leave this field empty and we'll use the current time or server time.
Merge anonymous activity into a profile
Unlike our client-side JavaScript library, server-side libraries and the API don’t assign an anonymous ID automatically. You assign an anonymous ID to track a person’s anonymous track and page calls. Then when you identify the user, you pass the anonymous ID with their user ID and Customer.io will merge the last 30 days of anonymous activity with the identified profile.
You can’t reuse an anonymous ID after it’s merged into a profile. Once an identify call links an anonymous ID to a user ID, Customer.io merges that ID’s anonymous activity into the profile and marks the anonymous ID as used—any events sent under the same anonymous ID afterward stay anonymous and won’t link to the profile.
If a person generates new anonymous activity after you’ve identified them—for example, they log out and keep browsing before logging back in—assign a new anonymous ID for that activity, then send another identify call with their user ID and the new anonymous ID to merge it into the same profile.
However, there is a 5 minute window after you merge anonymous activity into a profile where you can still merge anonymous activity using the same anonymous ID. This allows you to merge anonymous activity in a batch or in rapid succession without worrying about the order of your calls.
analytics.identify(
'019mr8mf4r',
{
'email': 'cool.person@example.com',
'first_name': 'Cool',
'last_name': 'Person',
},
anonymous_id='anon-abc-123',
)
Track
The track method tells us about actions profiles take—the events profiles perform—on your site. Every track call represents an event.
You should track your audience’s activities with events both as performance indicators and so you can respond to your audience’s activities with automations An automated process people enter when they meet your criteria. An automation has a trigger (who enters, and when), a workflow of messages and actions, and exit criteria (when they leave). A person's path through the workflow is their journey.
You can send events with an anonymousId or a userId. Calls that you make with an anonymousId are associated with a userId when you identify someone by their userId.
Track calls require an event name describing what a person did. And they generally include a series of properties, providing additional information about the event. Beyond that, we’ve provided a complete schema for writable event fields below, and you can find more information in our API documentation.
analytics.track('f4ca124298', 'added_to_cart', {
'product': "shoes",
'revenue': 39.95,
'qty': 1,
'size': 9
})
- userIdstringrequiredThe unique identifier for a person. This value should be unique across systems, so you recognize the same person in your sources _and_ destinations.
- eventstringrequiredThe name of the event
- Additional properties for your event.
- Event Properties *any typeAdditional properties that you want to capture in the event. These can take any JSON shape.
- A dictionary of context about a source call/event, like the user’s IP address or locale. Context is automatically collected by our source libraries.
- activebooleanWhether a user is active.
This is usually used when you send an .identify() call to update the traits independently of when you've “last seen” a user.
- ipstringThe user's IP address. This isn't captured by our libraries, but by our servers when we receive client-side events (like from our JavaScript source).
- localestringThe locale string for the current user, e.g.
en-US. - userAgentstringThe user agent of the device making the request
- channelstringThe channel the event originated from.Accepted values:
browser,server,mobile - Contains information about the campaign that resulted in the API call, gathered from, or mapping to, UTM parameters (e.g.
utm_source). - Contains information about the current page in the browser. This is automatically collected by our JavaScript source.
- Contains a list of booleans indicating the integrations that are enabled (true) or disabled (false). By default, all integrations are enabled (returning an empty object). Set
"All": falseto reverse this behavior.- Enabled/Disabled integrations *boolean
- timestampstring(date-time)The ISO-8601 timestamp when the event originally took place. This is mostly useful when you backfill data past events. If you're not backfilling data, you can leave this field empty and we'll use the current time or server time.
Deduplicate events
Generally, we’ll generate a message_id for each event you send to Customer.io. But, you can set your own message_id, which might be helpful if you need to deduplicate events.
We’ll accept the first instance of any operation with a given message_id and ignore any operations with the same message_id for the next 12 hours. The message_id is can be any string value, but we recommend a hash of the event data or a UUID/ULID to ensure that you don’t inadvertently deduplicate events.
If you backdate events, you’ll need to deduplicate them before you send them to Customer.io. We deduplicate the message_id within 12 hours from when we receive the event—not the timestamp on the event itself.
analytics.track(
user_id = 'f4ca124298',
event = 'added_to_cart',
properties = {
'product': "shoes",
'revenue': 39.95,
'qty': 1,
'size': 9,
},
message_id = 'message_id_here',
)
Page
The Page method records page views on your website, along with optional extra information about the page a person visited.
If you’re using Customer.io’s client-side JavaScript library in combination with our python library, then the client side JavaScript library already captures page calls for you by default.
But, if you have a single page app or you don’t use our JavaScript client library on your website, you’ll need to send your own page calls.
Structure
analytics.page('<user_id>', 'category', 'name', {
'properties': 'any'
}, {
#options
'integrations': {
#Enable/disable integrations
#By default, all destinations are enabled
}
})Example
analytics.page('<user_id>', 'Retail Page', 'shoes', {
'url': 'https://example.com/products/showes'
})- userIdstringrequiredThe unique identifier for a person. This value should be unique across systems, so you recognize the same person in your sources _and_ destinations.
- namestringrequiredThe name of the page.
- Additional properties for your event.
- categorystringThe category of the page. This might be useful if you have a single page routes or have a flattened URL structure.
- Page Properties *any typeAdditional properties that you want to send with the page event. By default, we capture
url,title, and stuff.
- A dictionary of context about a source call/event, like the user’s IP address or locale. Context is automatically collected by our source libraries.
- activebooleanWhether a user is active.
This is usually used when you send an .identify() call to update the traits independently of when you've “last seen” a user.
- ipstringThe user's IP address. This isn't captured by our libraries, but by our servers when we receive client-side events (like from our JavaScript source).
- localestringThe locale string for the current user, e.g.
en-US. - userAgentstringThe user agent of the device making the request
- channelstringThe channel the event originated from.Accepted values:
browser,server,mobile - Contains information about the campaign that resulted in the API call, gathered from, or mapping to, UTM parameters (e.g.
utm_source). - Contains information about the current page in the browser. This is automatically collected by our JavaScript source.
- Contains a list of booleans indicating the integrations that are enabled (true) or disabled (false). By default, all integrations are enabled (returning an empty object). Set
"All": falseto reverse this behavior.- Enabled/Disabled integrations *boolean
- timestampstring(date-time)The ISO-8601 timestamp when the event originally took place. This is mostly useful when you backfill data past events. If you're not backfilling data, you can leave this field empty and we'll use the current time or server time.
Group
The Group method associates an identified profile with a group—like a company, organization, project, online class or any other collective noun you come up with for the same concept. In Customer.io Journeys, we call groups objects An object is a non-person entity that you can associate with one or more people—like a company, account, or online course.
Group calls are useful for integrations where you maintain relationships between profiles and larger organizations, like in Customer.io! In Customer.io Journeys, you can store groups as objects An object is a non-person entity that you can associate with one or more people—like a company, account, or online course.
Find more details about group, including the group payload, in our API spec.
analytics.group('user_id', 'group_id', {
'name': 'Initech',
'domain': 'Accounting Software'
})
- userIdstringThe unique identifier for a person. This value should be unique across systems, so you recognize the same person in your sources _and_ destinations.
- groupIdstringrequiredID of the group
- objectTypeIdstringIf you use Customer.io Journeys as a destination, this value is the type of group/object your group belongs to; object type IDs are stringified integers. If you don't include this value, we assume the object type ID is
1. See objects in Customer.io Journeys for more information. - Additional information about the group.
- object_type_idstringIf you use Customer.io Journeys as a destination, this value is the type of group/object your group belongs to; object type IDs are stringified integers. If you don't include this value, we assume the object type ID is
1. See objects in Customer.io Journeys for more information. - Group Traits *any typeAdditional traits you want to associate with this group.
- A dictionary of context about a source call/event, like the user’s IP address or locale. Context is automatically collected by our source libraries.
- activebooleanWhether a user is active.
This is usually used when you send an .identify() call to update the traits independently of when you've “last seen” a user.
- ipstringThe user's IP address. This isn't captured by our libraries, but by our servers when we receive client-side events (like from our JavaScript source).
- localestringThe locale string for the current user, e.g.
en-US. - userAgentstringThe user agent of the device making the request
- channelstringThe channel the event originated from.Accepted values:
browser,server,mobile - Contains information about the campaign that resulted in the API call, gathered from, or mapping to, UTM parameters (e.g.
utm_source). - Contains information about the current page in the browser. This is automatically collected by our JavaScript source.
- Contains a list of booleans indicating the integrations that are enabled (true) or disabled (false). By default, all integrations are enabled (returning an empty object). Set
"All": falseto reverse this behavior.- Enabled/Disabled integrations *boolean
- timestampstring(date-time)The ISO-8601 timestamp when the event originally took place. This is mostly useful when you backfill data past events. If you're not backfilling data, you can leave this field empty and we'll use the current time or server time.
Alias
The Alias method combines two previously unassociated user identities. Some integrations automatically reconcile profiles with different identifiers based on whether you send anonymousId, userId, or another trait that the integration expects to be unique. But for integrations that don’t, you may need to send alias requests to do this.
In general, you won’t need to use the alias call; we try to handle user identification gracefully so you don’t need to merge profiles. But you may need to send alias calls to manage user identities in some data-out integrations.
For example, in Mixpanel it’s used to associate an anonymous user with an identified user once they sign up.
analytics.alias(previous_id, user_id)
Here’s how you might use the alias call. In this case, we start with an anonymous_user and switch to an email address when a person provides their userId.
# the anonymous user does actions under an anonymous ID
analytics.track('92734232-2342423423-973945', 'Anonymous Event')
# the anonymous user signs up and is aliased to their new user ID
analytics.alias('92734232-2342423423-973945', '1234')
# the user is identified
analytics.identify('1234', { 'plan': 'Free' })
# the identified user does actions
analytics.track('1234', 'Identified Action')
- previousIdstringrequiredThe anonymousId or userId value that you want to merge into the canonical profile.
- userIdstringrequiredThe userId that you want to keep. This is required if you haven't already identified someone with one of our web or server-side libraries.
Configuration and Library Options
If you want to change the library’s default settings want to send data to multiple sources, you can create your own client(s). Remember that each client runs a separate background thread, so you won’t want to create new clients on every request.
from customerio.analytics import Client
Client('YOUR_WRITE_KEY', debug=True, on_error=on_error, send=True,
max_queue_size=100000, upload_interval=5, upload_size=500, gzip=True)
| Field | Description |
|---|---|
debug bool | Set True to enable verbose logging, False by default. |
send bool | Set False to avoid sending data to Customer.io, True by default. |
on_error function | Set an error handler to be called whenever errors occur. |
sync_mode bool | Set True to send requests synchronously (blocking). Otherwise requests are queued and sent in the background. False by default. |
max_queue_size int | The maximum number of elements allowed in the queue. Hitting the max queue size means you’re identifying / tracking faster than you can flush. If this happens, let us know! |
upload_interval float | The frequency, in seconds, of sends to Customer.io. Default value is 0.5. |
upload_size int | The number of items per batch upload. Default value is 100. |
timeout int | Request timeout in seconds. Default value is 15 seconds. |
max_retries int | Maximum number of times to retry requests that fail due to e.g.: timeout. Defaults to 10. |
proxies dict | Proxies to use for requests, in the requests module format for proxies. |
gzip bool | Set True to compress data with gzip before sending, False by default. |
Selecting Destinations
You can pass an integrations object to alias, group, identify, page and track calls that lets you turn certain destinations on or off. By default all destinations are enabled. Passing false for an integration disables the call to that destination.
You might want to do this for things like alias calls, which aren’t supported by all destinations.
In this case, Customer.io specifies the track to only go to Vero. All: false disables all destinations except the ones you explicitly specify.
analytics.track('user_id', 'Membership Upgraded', integrations={
'All': False,
'Mixpanel': True,
'Google Analytics': False
})
Destination flags are case sensitive. You’ll find each integration’s name at the top of each integration’s page in our documentation.
Backfilling historical data
You can backfill data by adding a timestamp to your calls. This can be helpful if you’ve just switched to Customer.io.
You can only do this for destinations that accept timestamped data—most analytics tools like Mixpanel and Amplitude do. The notable destination that doesn’t support timestamped data is Google Analytics.
import datetime
from dateutil.tz import tzutc
timestamp = datetime.datetime(2538, 10, 17, 0, 0, 0, 0, tzinfo=tzutc())
analytics.track('019mr8mf4r', 'started_class', {
'class': 'How to Use CDP'
}, timestamp=timestamp)
Time zones in Python
Python’s datetime module supports two types of date and time objects: naive objects without time zone information, and aware objects that include time zones. By default, newly created datetime objects are naive. Make sure that you use time zone aware objects when you import data so that you send time zone information correctly.
We created an aware datetime object in the previous section using the tzinfo argument to the datetime constructor. If you omitted this argument, we would not pass time zone info:
>>> naive = datetime.datetime(2015, 1, 5, 0, 0, 0, 0)
>>> aware = datetime.datetime(2015, 1, 5, 0, 0, 0, 0, tzinfo=tzutc())
>>> naive.isoformat()
'2015-01-05T00:00:00'
>>> aware.isoformat()
'2015-01-05T00:00:00+00:00'
If you have an ISO format timestamp string that contains time zone information, dateutil.parser can create aware datetime objects.
>>> import dateutil.parser
>>> dateutil.parser.parse('2012-10-17T18:58:57.911Z')
datetime.datetime(2012, 10, 17, 18, 58, 57, 911000, tzinfo=tzutc())
>>> dateutil.parser.parse('2016-06-06T01:46:33.939388+00:00')
datetime.datetime(2016, 6, 6, 1, 46, 33, 939388, tzinfo=tzutc())
>>> dateutil.parser.parse('2016-06-06T01:46:33.939388+07:00')
datetime.datetime(2016, 6, 6, 1, 46, 33, 939388, tzinfo=tzoffset(None, 25200))
>>> dateutil.parser.parse('2016-06-06T01:46:33.939388-07:00')
datetime.datetime(2016, 6, 6, 1, 46, 33, 939388, tzinfo=tzoffset(None, -25200))
If you find yourself with a naive object, and know what time zone it should be in, you can also use pytz to create an aware datetime object from the naive one.
>>> import datetime
>>> import pytz
>>> naive = datetime.datetime.now()
>>> aware = pytz.timezone('US/Pacific').localize(naive)
>>> naive.isoformat()
'2016-06-05T21:52:14.499635'
>>> aware.isoformat()
'2016-06-05T21:52:14.499635-07:00'
The pytz documentation contains additional information on time zone usage, and can help you handle edge cases.
Batching
Our libraries are built to support high performance environments. It’s safe to use this library on a web server that serves hundreds of requests per second.
But every method you invoke does not result in an HTTP request. Instead, we queue requests in memory and then flush them in batches, which allows for more efficient operation.
By default, our Python source library flushes:
- every 100 messages (control with
upload_size) - if 0.5 seconds has passed since the last flush (control with
upload_interval)
There is a maximum of 500KB per batch request and 32KB per call.
What happens if there are too many messages?
If our python module can’t flush calls faster than it’s receiving them, it’ll simply stop accepting requests. This means your program will never crash because of a backed up analytics queue. The default max_queue_size is 10000.
Flush events on demand
You can flush your queue on demand. For example, at the end of your program, you’ll want to flush to make sure there’s nothing left in the queue. Just call the flush method.
analytics.flush()
This method blocks the calling thread until there the message queue is empty. You’ll want to use it as part of your cleanup scripts and avoid using it as part of the request lifecycle.
How do I gzip requests?
You can compress batched requests before you send them to Customer.io by setting the gzip argument when constructing your Client.
from customerio.analytics import Client
Client('YOUR_WRITE_KEY', gzip=True)
Detecting errors
You can listen to events on failed flush attempts.
def on_error(error, items):
print('Failure', error)
analytics.on_error = on_error
Logging
Our library uses the standard python logging module. By default, logging is enabled and set at the WARNING level. If you want more verbose logs, you can set a different log_level:
import logging
logging.getLogger('customerio').setLevel('DEBUG')