Skip to main content

How to 🤖 automate the Albert Heijn app

(or almost any other app)

In a recent tweet I showed the output of a script that I run every week to automatically activate offers that the Dutch supermarket Albert Hein puts in their app every week. You are supposed to open up to app, go to ‘bonus box’ and activate the offers you want. Normally, you can activate 5 / 10 discounts. But the thing is, if you have ‘Albert Heijn Premium’ (12,99 / year) you can activate 10 discounts. Sometimes there are more then 10, but we’ll ignore that for now.

Whenever I tap ‘Activate’ on an offer, it signals to Albert Heijn’s systems that I want to activate that offer. If we could listen in on how the app does that, we may be able to replicate that action with something other then the app. For example, a script.

You can listen in on that traffic with a tool called Burp Suite. It’s written in Java and works on the 3 major platforms. Charles is also a good option which does the same thing.

For this blog, I’m going to choose Burp Suite. Go ahead and install it now if you want to follow along. I’ll wait.

Open Burp Suite and start a project. Go to Proxy > Options and click on the single item in the Proxy Listeners section, then click edit.

burpsuite proxy listeners

Make sure the proxy listens on an IP address on the same local network as your phone.

burpsuite proxy options

Burp sets up a HTTP proxy that we can use to route traffic through. On iOS, go to Settings > WiFi > Tap the Info ‘(i)’ button next to the network you’re connected to > Scroll down to ‘Configure Proxy’ > Tap Manual > Enter the proxy and port from the Burp Suite settings.

Android will be different, depending on the manufacturer and ROM that you’re running. But I’m sure it’s not too hard to figure out (try WiFi settings)

screenshot of proxy settings on ios

You may notice that you can’t get on the internet anymore. Nothing is wrong, that’s just Burp Suite’s Intercept feature that is on by default. This feature holds any HTTP messages for you to edit them, before they leave or enter your phone. You can turn this off in the ‘Proxy > Intercept’ tab.

Now we need to install a Root CA on our phone, to be able to intercept HTTPS messages. Luckily, most apps (including Albert Heijn) use encryption. In your phone’s browser, you can visit http://burp to download the CA. Visit that page and download the CA.

screenshot of http://burp page
downloading the ca step 1

Just a reminder, these are iOS instructions: After downloading, activate it in Settings > General > VPN & Device Management. Tap it and install it. After you’ve done that, you also need to go to Settings > About > Certificate Trust Settings and enable it there. Allowing iOS to show you a couple of more warnings.

vpn & device managemant
installing the ca cert

After all that hard work, you deserve a beverage of your liking. Go ahead and grab it now. I’ll wait.

Alright, now the real fun can begin. Go to the HTTP history tab in Burp Suite. You will most likely see a whole bunch of requests that your phone is making to all sorts of apps and services.

We’re interested in the Albert Heijn app traffic, so go ahead and open it and check out the requests it makes when you tap certain items in the app. For example, the Bonus page, or your Bestekzegels page (if you need Bestekzegels, I got a few left, hit me up).

ah app traffic

Authentication
#

Ok, now a little bit on reverse engineering the Albert Heijn app and it’s authentication. When you login to your Albert Heijn account with your username/password. You are subject to hCaptcha. This is unfortunate because that means it cannot be automated ( unless you pay clickfarms to do them for you).

However, all is not lost. Once you log in, you see a POST request to api.ah.nl/mobile-auth/v1/auth/token/refresh. What you will get back is a access_token and a refresh_token. Take note of that request token, because you will need it to request new access_tokens. You use the access_token to authenticate your requests. However, that same access_token has a very limited lifespan. You can request a new access_token by sending a post message to that endpoint I noted earier with the following body

{
	"refreshToken":"<uuid>",
	"clientId":"appie"
}

And you will get back:

{
	"access_token":"<uuid>",
	"refresh_token":"<uuid>"
}

Note that using the Albert Heijn app on your phone may interfere with the token that you saved. Every once in a while you need to sniff traffic again to get a new refresh token. However, in practice. This does not happen very often.

The BonusBox
#

Now, how do you activate bonusbox offers? Well, just open up the bonus box page and watch the requests. You will see a request to https://api.ah.nl/mobile-services/bonuspage/v1/choose-and-activate?. The JSON body you get back looks something like this (truncated, because it’s long!)

{
	"sectionType":"PO",
	"sectionDescription":"Kies en Activeer Bonus",
	"bonusGroupOrProducts":[
		{
			"bonusGroup":{
				"id":"326670",
				"offerId":861069,
				"offerStartDate":"2022-01-17",
				"segmentId":326670,
				"segmentDescription":"Alle AH Kookgroente in zak 200-400 gram",
				"bonusStartDate":"2022-01-17",
				"bonusEndDate":"2022-01-23",
				"bonusType":"PO",
				"promotionType":"PERSONAL",
				"segmentType":"BBOX",
				"discountDescription":"2e HALVE PRIJS",
				"images":[
					{
						"width":48,
						"height":48,
						"url":"https://static.ah.nl/static/product/AHI_434d50303233343333_4_48x48_GIF.GIF"
					},
					{
						"width":80,
						"height":80,
						"url":"https://static.ah.nl/static/product/AHI_434d50303233343333_4_80x80_JPG.JPG"
					},
					{
						"width":200,
						"height":200,
						"url":"https://static.ah.nl/static/product/AHI_434d50303233343333_4_200x200_JPG.JPG"
					},
					{
						"width":708,
						"height":708,
						"url":"https://static.ah.nl/static/product/AHI_434d50303233343333_4_LowRes_JPG.JPG"
					}
				],
				"category":"Extra online aanbiedingen",
				"future":false,
				"shopType":"AH",
				"salesUnitSize":"2 zakken naar keuze",
				"exampleFromPrice":5.78,
				"exampleForPrice":4.34,
				"exampleHasListPrice":false,
				"isStapelBonus":false,
				"extraDescriptions":[],
				"activationStatus":"ACTIVATED"
			}
		},
		[next item here]

Now, to activate an item, you just send a PATCH request to https://api.ah.nl/mobile-services/bonuspage/v1/activate/$offerId with the offerId of the offer you want to activate.

My PHP code to do this every week looks something like this. All that is missing is the $token variable, which I’m going to keep secret for obvious reasons.

$monday = strtotime('next monday');
$date = date('Y-m-d', $monday);

$headers = array(
  'authorization' => "Bearer $token",
  'Host' => 'api.ah.nl',
  'content-type' => 'application/x-www-form-urlencoded; charset=utf-8'
);

$endpoint = "https://api.ah.nl/mobile-services/bonuspage/v1/choose-and-activate?bonusStartDate=$date";

// Get Bonusbox items
$response = $client->request('GET', $endpoint, [
  'headers' => $headers
]);

$bonusbox = json_decode($response->getBody());

foreach ($bonusbox->bonusGroupOrProducts as $item) {
	
	if(!empty($item->bonusGroup)) {
		$type = "bonusGroup";
		$description = 'segmentDescription';
		$discount = 'discountDescription';
	} else {
		$type = "product";
		$description = 'bonusSegmentDescription';
		$discount = 'bonusMechanism';
	}

	echo "Activating offer: {$item->$type->$description}\r\n";
	echo "  {$item->$type->$discount}\r\n\r\n";

	$offerId = $item->$type->offerId;
	$segmentId = $item->$type->segmentId;

	// Activate offer
	$endpoint = "https://api.ah.nl/mobile-services/bonuspage/v1/activate/$offerId";

	$response = $client->request('PATCH', $endpoint, [
	  'headers' => $headers,
	  'http_errors' => false,
	  'body' => "segmentId=$segmentId&startDate=$date"
	]);
}

You can use the same way of watching traffic to automate almost any app. Which is great fun if you ask me.

A colleague recently talked about listening to the Picnic app traffic to automatically order groceries and get them delivered. Picnic also supports direct debit for payments so the entire thing can, in theory, be automated.

That is all. Hope you learned something!