Reverse engineering the PostNL consumer API

While working on automating my home using HomeAssistant (tip!), I thought it would be awesome to show incoming deliveries on the dashboard. PostNL, the main Dutch postal service, offers a portal that enables you to track the delivery of your letters and packages. There are no prebuilt components available for this scenario yet, so let's start coding! 🤓

However, PostNL doesn't offer a public API... Now what? Let's start digging.

API?

With the PostNL app you can track your incoming and past deliveries, after you log in. This means that they probably have some sort of webservice (API), which can be reused for this component.

The starting point of this journey will be the PostNL iOS app, since my main device is an iOS device. However, it is possible to use any mobile device which is supported by PostNL. (sorry Windows Phone users...)

Sniff iOS app requests

In order to figure out the API endpoints and body, we need to capture all requests the iOS app makes. To sniff all the traffic I will be using Burp Suite (free edition). In their documentation you can read how to configure an iOS device to work with Burp.

Since PostNL is using SSL (https), we need to take some extra steps to capture the traffic. For the sake of proxying SSL requests, we have to man-in-the-middle attack ourselves. This can be achieved by installing a CA certificate from BurpSuite. This will only work if the app isn't using certificate pinning.

Make sure you remove the certificate when you are done, otherwise you will be vulnerable for MITM attacks!

When everything is configured, the sniffing can begin! Open the PostNL app, login and retrieve the status of your deliveries.

Burp Suite

In the screenshot above you can see that the PostNL app is making multiple requests to https://jouw.postnl.nl and to an Adobe service for analytics & tracking purposes. The /mobile/token endpoint sounds interesting and if we check the request payload, we are able to see which parameters have to be sent to the API (and yes, my password is 64 characters..).

While further analysing the request, I noticed that the response is in JSON and contains a bearer token. This probably means that the API is secured by Bearer token usage according to the OAuth v2 specification.

{"access_token":"[redacted]","token_type":"bearer","expires_in":1799,"refresh_token":"[redacted]"}

If we analyse a request to another endpoint, we can see that the Bearer token is included in the header. So, in order to authenticate to this webservice we will need to obtain the Bearer tokens and pass them in an 'Authorization' header.

Call the PostNL webservice

After we know all the endpoints and the authorisation method, we can start 'calling' the webservice. I started using Postman to build the requests and to test them. This was a trial-and-error process where the only issue I faced was that the 'Api-Version' header is required and should be 4.6. Otherwise you will the very useful error message, An error has occurred ...

In order to call the PostNL webservice more easily I decided to write an API wrapper, which allows you to use all the functionality without manually creating all requests. If you want to read more about this Design Pattern, have a a look at the Adapter pattern and the Facade pattern. Since HomeAssistant is written in Python 3, the API wrapper should also be written in this language. However, this is something that can be done in any language of your choice since they are just simple HTTP requests.

python-postnl-api

The Python3 library and the HomeAssistant plugin are still a work in progress, because my time is quite limited at the moment and my experience with Python is zero.

It would be great if companies just open up their APIs and publish it, so developers can build awesome stuff. If they don't? There will always be workarounds like the one I described. Just make sure you use it in a good way, don't ruin the fun for everyone. 😉

Github: python-postnl-api (work in progress, contributions welcome)

Screenshot of the discovered endpoints, described using the OpenAPI 3.0 specification.

Disclaimer: This is a personal project and it is not affiliated with PostNL or my employer. It is published with permission of PostNL.

Always make sure that it is allowed to use the webservice, which would be stated in the EULA (end-user license agreement). This article only shows in which way you can reverse engineer a not publicly exposed webservice, it is not about whether it is legal to use the webservice for commercial or personal purposes.

Mick Vleeshouwer

Partner Technology Strategist @ Microsoft | Software Engineer

Amsterdam