Community Tip: Everything about Server-side Updates - Mixpanel
Blog Post

Community Tip: Everything about Server-side Updates

Mixpanel’s Engagement and People APIs automatically add geolocation data, and for People updates, updates the “Last Seen” Date. This Community Tip goes into depth on the best strategies for making updates from your server for a variety of use cases. These include: asynchronous tracking, batch/scripted updates, and importing in addition to typical synchronous server side tracking. This tip is most useful for developers and contains important code samples for many use cases.

The Problem

Tracking client-side comes with its conveniences. As nice as it is to have the library handle distinct_id for you (which is a standard feature of our client-side libraries), the benefit is in the “default” properties that can be automatically grabbed from the local machine. You can find a list of most of these properties here.

Most of these (like $browser, $device, $os, etc.) are pulled from the local machine when the event or People call is constructed by our library; geolocation data ($city, $region, mp_country_code), however, is actually set from the IP address on the incoming request when the data hits our servers. While our server-side libraries don’t automatically grab any device-level properties, our servers will still try to resolve city/region/country when the request is received. As all server-side calls will likely originate from the same IP (that is, the IP of your server) this can have the unintended effect of setting the location of all of your users to the location of your datacenter. Luckily there’s any easy fix: the “ip” parameter.

Here’s an example event call to our REST API:

{
    "event": "Level Complete",
    "properties": {
        "Level Number": 9,
        "distinct_id": "13793",
        "token": "e3bc4100330c35722740fb8c6f5abddc",
        "time": 1409259110
    }
}

This will send an event called “Level Complete” with a single property called “Level Number” – as this call is being sent over HTTP none of the default properties will come through. The REST API won’t do any geolocation by default (as it assumes you’re sending these calls server-side), though you can force it to by sending in a property called “ip”:

<

{
    "event": "Level Complete",
    "properties": {
        "Level Number": 9,
        "distinct_id": "13793",
        "token": "e3bc4100330c35722740fb8c6f5abddc",
        "time": 1409259110,
        "ip": "72.229.28.185"
    }
}

This request, however, will use the value sent with the “ip” parameter for geolocation – this event will appear to have come from New York, NY.

Geolocation with Engage updates

It turns out that this is often a far bigger issue for People profile updates, which are often batch-updated server side or over the REST API. Here the syntax and behavior are a bit different:

{
    "$token": "e3bc4100330c35722740fb8c6f5abddc",
    "$distinct_id": "13793",
    "$set": {
        "Address": "1313 Mockingbird Lane"
    }
}

The request ends up looking like this:

>http://api.mixpanel.com/engage/?data=ew0KICAgICIkdG9rZW4iOiAiZTNiYzQxMDAzMzBjMzU3MjI3NDBmYjhjNmY1YWJkZGMiLA0KICAgICIkZGlzdGluY3RfaWQiOiAiMTM3OTMiLA0KICAgICIkc2V0Ijogew0KICAgICAgICAiQWRkcmVzcyI6ICIxMzEzIE1vY2tpbmdiaXJkIExhbmUiDQogICAgfQ0KfQ==

This will set a property called “Address” to the profile where distinct_id = “13793” – calls to /engage, however, assume that you do want to use the IP address of the request if no IP property is given. As a result, this call will appear to have come from wherever the machine that generated the request is located – not a good situation if you were doing a batch update of all of your profiles. In that case, each user would appear to be located wherever the server is located. You can override this behavior by including “ip=0” as a URL parameter:

http://api.mixpanel.com/engage/?data=ew0KICAgICIkdG9rZW4iOiAiZTNiYzQxMDAzMzBjMzU3MjI3NDBmYjhjNmY1YWJkZGMiLA0KICAgICIkZGlzdGluY3RfaWQiOiAiMTM3OTMiLA0KICAgICIkc2V0Ijogew0KICAgICAgICAiQWRkcmVzcyI6ICIxMzEzIE1vY2tpbmdiaXJkIExhbmUiDQogICAgfQ0KfQ==&ip=0

This will force Mixpanel to ignore the IP on the request, leaving the geolocation information untouched (so it will reflect whatever was previously set with another library instead of overwriting it). If you want to pass in your own IP address similar to the way you can with track just leave out the ‘ip=’ URL parameter and add a property called “$ip” to the message payload:

{
    "$token": "e3bc4100330c35722740fb8c6f5abddc",
    "$distinct_id": "13793",
    "$ip": "72.229.28.185",
    "$set": {
        "Address": "1313 Mockingbird Lane"
    }
}

Notice that you need to set “$ip” outside of the “$set” dictionary. This will overwrite the geographic data on the profile with distinct_id = 13793 with New York, NY.

Ignoring Last Seen

There’s another issue with profile updates, and that’s “$last_seen”. This property reflects the timestamp of the last update to any People property, so server-side calls will also overwrite this if you’re not careful (this can lead to all of your users showing the same timestamp for Last Seen). To avoid this, just add in the “$ignore_time” property to the People call”

{
    "$token": "e3bc4100330c35722740fb8c6f5abddc",
    "$distinct_id": "13793",
    "$ip": "72.229.28.185",
    "$ignore_time": "true",
    "$set": {
        "Address": "1313 Mockingbird Lane"
    }
}

This request will update the profile without updating “$last_seen” – all server-side updates to People records should probably ignore time just to be safe.

Ruby Example

All of our server-side libraries have parameters that allow you to force the IP and “last seen” behavior you want. In Ruby, for example:

def set(distinct_id, properties, ip=nil, optional_params={})

Uses the IP in the property for Geo-location

tracker.people.set('Drew', {'$first_name' => 'Drew'}, '72.229.28.185')

Does not Geo-locate at all

tracker.people.set('Drew', {'$first_name' => 'Drew'}, '0')
tracker.people.set('Drew', {'$first_name' => 'Drew'}, 0)

Geo-locates from the IP on the request

tracker.people.set('Drew', {'$first_name' => 'Drew'}, nil)
tracker.people.set('Drew', {'$first_name' => 'Drew'})

You can ignore time by passing “$ignore_time” => “true” into the optional_params dict:

Does not Geo-locate or update “$last_seen”

tracker.people.set('Drew', {'$first_name' => 'Drew'}, 0, {'$ignore_time' => 'true'})
Get the latest from Mixpanel
This field is required.