Importing past events using

  • 18 March 2021
  • 1 reply

So I was busy importing old “payment success” events into mixpanel.

I noticed about 30-40% of the events being rejected, for reasons unknown.

According to the documentation in I should be able to check the error string. Unfortunately the response I'm receiving does not seem to contain key "error" with a error message…

I later on discovered I needed to set verbose to 1! I had forgotten to do that.

Then finally I was getting error messages explaining what was wrong with the event data I was sending. 

Apparently all the rejected events were due to a lack of urlencoding of the “properties” data sent to mixpanel. My data contained characters like & and ó etc and this caused Mixpanel to reject.

Below is the code that enabled me to successfully import over 10K events with a shitload of properties. Just wanted to share with you, hopefully this helps somebody :-)

And if somebody has a smarter way of getting / converting the API secret , I'm all ears! (I suspect a MD5 transformation, but not sure).

    $mixpanelProject = 'replace with Mixpanel Project Token';
$mixpanelAuthorization = 'replace with Mixpanel API Secret';

$properties['token'] = ;
$properties['time'] = strtotime($orderCreated);
$properties['Order ID'] = $orderID;
$properties['Order Amount'] = $orderAmount;
$properties['Product Names'] => ['Banana', 'Cucumber', 'Grapes', 'Avocado'];
// Add all properties you need

$mixPanelEventData = [
'event' => $eventName,
'properties' => $properties

$mixpanelJSON = json_encode($mixPanelEventData, JSON_PRETTY_PRINT);

$curlHandle = curl_init();

curl_setopt_array($curlHandle, [
CURLOPT_URL => '', // this url depends on where you store your project data EU or US
CURLOPT_RETURNTRANSFER => true, // Important to set this to true, because you want to receive the response object from Mixpanel
CURLOPT_POSTFIELDS => 'data=' . urlencode($mixpanelJSON) . '&verbose=1', // Yeah this looks weird. I tried to set this up properly as an array. Didn't work :-(. Hugely important is the urlencode, not using it will not import anything.
'Accept: application/json',
'Content-Type: application/x-www-form-urlencoded; charset=utf-8', // Important if any of the data you're importing contains UTF8 characters
'Authorization: Basic ' . $mixpanelAuthorization
/* Ok maybe I'm an idiot but getting hold of this authorization key was not exactly easy. Documentation points toward the API secret. Using that secret key does NOT work.
However go to click on the "Send" button, paste the API secret and now copy the code appearing in the screen
on the right hand side after --header 'Authorization: Basic <XXXXXXX KEY XXXXXXX>
That's the key that makes everything work. */


$response = curl_exec($curlHandle);
$result = json_decode($response, true);

if (curl_errno($curlHandle)) throw new \Exception('cURL error: ' . curl_error($curlHandle));

if ($result['status'] == 0) throw new \Exception('Mixpanel error: ' . $result['error'] . ' Order ID: ' . $properties['Order ID']);

$httpcode = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);

if ($httpcode <> 200) throw new \Exception('Mixpanel httpcode: ' . $httpcode);

curl_close ($curlHandle);



1 reply

Hi Mick -

Thanks for writing this out for anyone else interested in importing events to Mixpanel. I’m sorry to hear the documentation wasn’t so clear and we definitely appreciate the feedback. I’m on the support team and I figured I’d shed a little more insight on how to use the API Secret in HTTP requests.

Our API import tool will base64 encode the API secret, which is a requirement when manually setting the Authorization header. Depending on the HTTP client you use, it’s not a requirement to base64 encode the API secret (the HTTP client will take care of this for you when it sets the Authorization header). The most important thing when authenticating your requests is that the API secret should be set as the user and the password should be left blank. Depending on the HTTP client you use, this may look different. Using curl as an example you could authenticate an import request like the following

#format of the -u flag
$ curl -u user:password

#authenticating the curl request
$ curl -u API_SECRET:

notice the colon after API_SECRET. This specifically tells curl that the API_SECRET is the user, and to the right of the colon, the password is left blank. When curl sends the HTTP request, it will automatically set the Authorization header and base64 encode the credentials. To bring this back to our API import tool and the base64 encoded API secret, if you base64 decode the string, you will notice that it decodes to your API_SECRET with a trailing colon since the Authorization header uses the same format as curl, <user:password>.

I hope you find this useful!