Guzzling HaveIBeenPwned with PHP

System and account breaches are happening all the time but fortunately there are services such as HaveIBeenPwned that scoop up the data that is released and provide a mechanism for people to see if their email address has been compromised.

Another really nice touch is that Troy Hunt (the guy behind it) has implemented an API which you can use to see whether your email address appears in his database, and it’s that which I am going to show you how to use PHP and Guzzle to consume.

Let’s begin by taking a look at his API. You can see what kinds of results are returned by browsing to this URL (I have replaced my real email address with ‘name’):

There’s lots of good information there, but depressingly, you can see that my details were exposed in 3 attacks:

  • Dropbox in 2012
  • LinkedIn in 2012 and,
  • Onliner Spambot in 2017

Ho hum.

So what is this blog entry about, anyway? Let’s write a quick throw-away app which will allow us to lookup whether any other email addresses have been compromised.

Create a Directory for Your Project

Retrieving the Guzzle Depedency

The next step is to go get Guzzle, the package. That is going to handle all of our HTTP communication and make talking to the API a whole lot easier. Make sure you have composer installed before you do this though – you have, right?!

Referencing Guzzle and the Autoloader

Now we need to let PHP know about Guzzle and in particular, which of the classes we want to use.

Now add this to the top:

The Client is going to be used to do the heavy (light?) lifting with the API whilst the ClientException will handle any problems — we’ll come back to that later, though.

Iterating Over the Command Line Options

The plan is that we are going to run this small application on the command line in this fashion:

For that, we are going to use two important facets of the language: $argv and $argc. $argv is a string array that contains all of the parameters on the command line. So in the example I used above, it would look like this:

Notice how the PHP command isn’t there and that in the first element, the name of the PHP script is?

Conveniently, $argc contains the total number of arguments which makes it easy for us to iterate over the array. We can zip through that now, so add this underneath the require statement:

If you like, you can echo out the items inside the loop with something akin to:

You can see that I am starting at 1, too — no need to look at the name of the script – I only want email addresses.

Instantiating the Guzzle Client

Add the following line above the “for” loop:

This creates our client and let’s it know what the base address is.

Notably, it also sets a delay. Whilst this service is free, it does cost Troy, and to prevent abuse, he doesn’t allow more than one request per 1.5 seconds. But what would happen if we did exceed the state rate? Well, we’d get a response like this:

Now we have our client, we’re good to go and can add on any specific endpoints depending on what we are doing.

Ready? Now type this in at the bottom of the file before I walk you through it:

You can think of this in terms of three sections – one which does the work and two others which will handle anything going awry. Starting on line 1, we’re going to pass in the Guzzle client but also the email address of the account we want to check.

Line 6 actually makes the call using the get method of the client. As a parameter, you can see that we are using the breachedaccount endpoint with the email address appended, just like we did right at the beginning with the browser.

Line 7 makes sure we get a 200 response code signalling all is well. Lines 16-18 are if something else is returned; that shouldn’t happen but we cover it just in case.

Lines 8-14 do the actual work – covering the response to JSON and then building a nice comma delimited list of which services were breached.

Let’s talk about the first catch block starting on line 20. What you need to realise is that
haveibeenpwned returns a 404 (Not Found) should there be no account whatsoever with that email address. That manifests itself as a ClientException in the Guzzle client, so we manage that here. In this case, we either return no services or warn that something went quite wrong — there are no other responses that we are interested in as valid.

The last catch block starting on line 30 covers anything else – someone ripping out your
internet cable, your computer catching fire or the world ending.

Wiring in the Function

We’re almost done. We now just need to hook in a call to the function and we’re finished. Go back to the for loop and replace the comment with this:

Running the app

As already covered, you can now run the application with a command line such as this:


Taking things further

There, There are a few things you could do to make this better.

  • How about adding a web interface?
  • Why not add the command to a cron (scheduled) job and have it check your email addresses every day?
  • You could keep an eye on all email addresses of your family for them – no need for them to sign up to the service then.
  • Explore some of the other features of the API.

Whatever you choose to do, make sure you act on anything you find – change those account passwords to different phrases and now!


Written by Stephen Moon
email: stephen at

Hey! Did you enjoy reading this? If you did and would like an email when I add new content, just subscribe to my list. You can unsubscribe at any time.

Leave a Reply

Your email address will not be published. Required fields are marked *