Using Monzo Webhooks with Azure Logic Apps

I’m a brand-new customer of Monzo’s banking service and I’m a big fan of the fact that they’ve published an API for their services. Whilst the API isn’t really their focus right now as they roll out to more customers, it’s still a great direction and is useful-enough as is to do some interesting things.

This is a brief introduction to how to use Monzo’s API, in particular its webhooks, in conjunction with Azure Logic Apps in order to create an automated workflow triggered by transactions against your Monzo account.

The Monzo API is not yet suitable for building public applications.

As such, this how-to could become obsolete quite quickly. Nevertheless, lets start.


You’ll have an Azure account and an account with Monzo. That’s about it.


What are we going to setup? Here’s a quick sketch:

Architecture — courtesy of Microsoft Paint

Simple enough…

Setup the basic Logic App

First, create the HTTP request trigger and a matching 200 response. We’re going to blithely assume that everything downstream just works (we’ll discuss more on this a bit later).

In the Azure Portal, click on the green + and type “Logic App” in the search box. Click on that and you’ll get something like this:

Hit ‘Create’ (obviously). On the next screen you’ll need to give it a name, allocate it to a resource group and put it in a region. Pick your favourites. This doesn’t have much effect.

Once the deployment has completed, click in to the newly created Logic App and you’ll likely be greeted with the default Logic Apps Designer page. Handily, the one we’re after is right on the front page.

You’ll be greeted with a screen containing this in the middle.

Your next task is to hit the ‘Edit’ link.

Under ‘Advanced Options’ set:

  • Method: POST
  • Relative path: /webhook

Note: this last part (relative path) isn’t strictly necessary. I’ve just set it for neatness and the possibility that other webhooks might be added in future that we might want to send to the same Logic App.

We’ll come back to the Request Body JSON Schema in a while.

Last part is to hit ‘New step’ and type ‘Response’ in the search box.

You are looking for ‘Request / Response — Response’. This is how you set the HTTP response code that is returned to the webhook. Make sure you set it to ‘200’. Don’t worry about Headers or Body. Just hit save.

Side note: this was determined through a bit of trial and error with the webhook. If you don’t have a Response Step, the Logic App will return a 202 Accepted status code — which means that the task was accepted but might not necessarily have completed successfully. Monzo’s webhook interprets this as not successful and will retry with an exponential backoff (see their documentation). This is not ideal to the Logic App as it’ll see them as separate invocations.

After you’ve saved it, open up the Request step again and you’ll see that the URL has been populated. Copy that out. Save it somewhere for later. It’ll look something like:

Notice that it’s put the ‘/webhook’ bit in there for you. Handy.

You’ll never guess what’s next… Yes. We’re going to make a webhook.

Create the Monzo webhook

Next we’re off to a handy part of Monzo’s website that lets you submit requests to its API through an API playground.

Login to the Monzo Developer page. It’ll e-mail you a magic link that logs you in to their API playground. From here we can issue requests to the API without having to faff about with writing code, or implementing OAuth2.

In particular you want “Register webhook” from the menu on the left. Click that. You’ll get a page like this (excuse the blocking out):

There aren’t usually black boxes with red outlines. Those were just my session credentials.

It’s pre-created a request body for you. All you need to do is substitute the URL you copied at the end of the previous step ( into the spot where it has “”.

Once done — click Send. Now you have yourself a webhook. You should get a response body at the bottom of the page with the ID of the new webhook, your Account ID and confirmation of the URL that has been setup.

If you need to confirm it’s there, click on ‘List webhooks’ in the menu on the left and Send that request to see it listed out.

Back to the Logic App

Now, I’m not going to go through every possible permutation of thing you could build, but here’s some ideas:

  • E-mail (, Gmail) yourself a summary of the transactions coming through your account. If your expenses system at work can receive e-mails to create new expense items, point it at that.
  • Add a row to a spreadsheet in Google Sheets for each new transaction.
  • Tweet about how rich you are every time you top up your card (don’t do that, please).

Azure Logic Apps let you do things like Conditionals (e.g. if the Merchant Name = “Starbucks” then do this, else do something different) as well, so there’s a certain amount of power there.


Sorry, side-tracked. Back to Azure.

Back in the Logic Apps Designer, you’ll remember we completely ignored the ‘Request Body JSON Schema’ box. This is actually fairly important because it tells Azure the JSON elements it expects to see in the Request Body and it then allows you to reference those elements in subsequent Steps in the Logic App.

If you look in the Monzo Developers API at the Retrieve Transaction section, there’s an example response. What you really get through the webhook is a little different, so here’s a slightly sanitised real one from my own account.

"type": "transaction.created",
"data": {
"id": "tx_test123",
"created": "2017-07-10T15:26:41.423Z",
"description": "NYX*ApogeeInternation Aberystwyth GBR",
"amount": -60,
"currency": "GBP",
"merchant": {
"id": "merch_00009KIcvJV8CvKF6f3zyD",
"group_id": "grp_00009KIcvJP6ZJifTJ0FGL",
"created": "2017-05-11T15:35:28.536Z",
"name": "Apogeeinternation",
"logo": "",
"emoji": "",
"category": "groceries",
"online": false,
"atm": false,
"address": {
"short_formatted": "3 Gressenham, Aberystwyth HA7 4LZ",
"formatted": "3 Gressenham, Aberystwyth HA7 4LZ, United Kingdom",
"address": "3 Gressenham",
"city": "Aberystwyth",
"region": "",
"country": "GBR",
"postcode": "HA7 4LZ",
"latitude": 51.6195494,
"longitude": -0.3081456,
"zoom_level": 4,
"approximate": false
"updated": "2017-05-12T10:22:03.142Z",
"metadata": {
"created_for_transaction": "tx_test234",
"enriched_from_settlement": "tx_test234",
"google_places_icon": "",
"google_places_id": "ChIJ5aksjlIbdkgR19pjmgejasY",
"google_places_name": "Apogee Photo Magazine",
"suggested_name": "Apogee Photo Magazine"
"disable_feedback": false
"notes": "",
"metadata": {},
"account_balance": 7722,
"attachments": null,
"category": "groceries",
"is_load": false,
"settled": "",
"local_amount": -60,
"local_currency": "GBP",
"updated": "2017-07-10T15:26:41.635Z",
"account_id": "acc_test123",
"counterparty": {},
"scheme": "gps_mastercard",
"dedupe_id": "1234",
"originator": false,
"include_in_spending": true

Click on ‘Use sample payload to generate schema’ and paste in the JSON above. Azure will then infer the schema for you and give you access to the elements as data.

Sidenote: the untidy thing that you’ll notice is that if you have multiple elements with the same name, but at different levels of nesting, you’ll see duplicates in the whizzy UI designer. You may need to just pick one and then duck into the ‘Code view’ in order to work out which one you’ve actually referenced. See the Google Sheets example below and the ‘Using the Code view’ section.

Send an email

Now we have Azure receiving calls from the webhook and parsing the JSON content out so we can use it. Let’s send ourselves an e-mail as it’s the easiest and quickest thing to do.

Underneath the ‘Response’ element, add another Step. I’m going to add Gmail, but you could use any other supported e-mail service such as I selected “Gmail — Send an email”.

The first time you use an external service, you’ll need to ‘Connect’ the Azure Logic App to it. This is the same as activating a ‘Service’ in IFTTT. You authorise (yes, I’m from the UK) the Logic App to use your Gmail/ account.

You should get a screen a bit like this. Notice the pop-out on the right side of the screen:

The JSON schema we created earlier has indeed become a set of elements we can choose from. You should be able to knock together something very quickly to send yourself an e-mail containing various bits of data from the transaction.

Insert rows into a Google Sheets sheet

Now we’re going to do something similar with Google Sheets. But first, a little preparation work.

To use a workbook from Google Sheets, you need to create the workbook you’re going to insert into and add a headers rows with titles.

Save that, then go back to the Azure Portal.

Add a new step and search for Google Sheets. There will be an option like “Insert New Row”. Select that.

Use the dialog boxes to locate the File in Google Drive and select the appropriate worksheet within the File, that contains the headers you set up. If all goes well, after a short delay, you’ll see the headers populated into the form as below.

Using the dynamic content picker, you can populate this with values from the JSON as follows.

The difficulty you might notice is that there are multiple ‘id’ and ‘name’ options in the dynamic content picker. We’ll address this in the Code view section.

Using the Code view

The Code view is an alternative view of your Logic App as a JSON-based structure. In it you can see the detail of how data from the JSON request data is mapped into things like the Google Sheets action.

In particular look at the excerpt below and see how the JSON elements are referenced and mapped into columns in the spreadsheet. If any of those mappings are incorrect, based upon your choices in the Designer UI, correct them in the JSON and click Save.

"body": {
"AccountID": "@triggerBody()?['data']?['account_id']",
"Address": "@triggerBody()?['data']?['merchant']?['address']?['short_formatted']",
"Amount": "@{triggerBody()?['data']?['amount']}",
"Balance": "@{triggerBody()?['data']?['account_balance']}",
"Description": "@triggerBody()?['data']?['description']",
"Merchant": "@triggerBody()?['data']?['merchant']?['name']",
"Notes": "@triggerBody()?['data']?['notes']",
"Timestamp": "@triggerBody()?['data']?['merchant']?['created']"

Quite a lot of problems using Logic Apps can be fixed by having a dig around in the Code view. The editor itself does JSON syntax highlighting, so it’s fairly nice to use.

That’s it. You should now have a working system whereby Monzo sends a HTTP POST request to an Azure Logic App every time to perform a transaction. The Azure Logic App will then perform whatever actions that you’ve configured within it.

Often things don’t go right first time, so continue reading to see some pointers to help you debug issues that might come up.

Debugging and finding problems

Logic Apps has a neat enough interface to try and work out what’s going on. With a little bit of “here’s one I made earlier”, here’s the Overview screen for another similar Logic App I’ve been using for myself:

Everything looks great!

You can click on any of the runs in the ‘Runs history’ section and look for yourself at the details of the data at each stage in the execution and the result of any of the conditional evaluations. If there are errors, you can see those too.

This is what happens if you screw up one of the dynamic function calculations
Showing the evaluation of a conditional step
Show the raw contents of the original request received by the Logic App

The user interface is pretty useful in terms of helping you work out the detail of what happened at each step in your workflow.

Final notes

Hopefully this has been a useful canter through creating something useful from a combination of Monzo’s webhooks and Microsoft Azure’s Logic Apps. From this hopefully you’ve learned:

  • how to setup a basic Logic App to receive webhook calls
  • how to register a webhook with Monzo’s API
  • how to configure the Logic App with the webhook JSON schema
  • some ideas as to how you might then do something useful with the transaction data you’ll receive from the webhook.
  • a rough introduction to more advanced topics such as Code view and the usefulness of the Run History in the Logic Apps Overview.

Perhaps the last thing to note is that we haven’t created a test-harness as part of this to enable you, dear reader, to make a few test calls to your Azure Logic App in order to check it’s all working. I may do that in an update to this post.

Advanced usage

Number formatting

Number formatting isn’t really a thing in Monzo’s API. All amounts and balances come through in pence, so £10 is 1000.

To format that as something human-readable I ended up using this horrible mess of substring functions in Code view:

@{substring(string(triggerBody()?[‘data’]?[‘amount’]),0,sub(length(string(triggerBody()?[‘data’]?[‘amount’])),2))}.@{substring(string(triggerBody()?[‘data’]?[‘amount’]),sub(length(string(triggerBody()?[‘data’]?[‘amount’])),2),2)} @{triggerBody()?[‘data’]?[‘currency’]}

Believe it or not, that turns 1000 into 10.00 GBP. If anyone has a more elegant solution, I’d be glad to hear it.


As above, I may update this post to provide a test-harness for easier Logic App debugging.

All I can suggest in the meantime is to double-check your working and either use small transactions or top-ups to cause the webhook to fire to test your Logic App works as you’d hope.

If scripting is your thing, I created a variant of the Logic App to just write the request into a new file on Google Drive. I copy-pasted its contents into a Python script I’d written that just POSTs that into the Logic App.

Why are we blindly returning a 200 Success code?

As I was working through this, it occurred to me that basically whatever failures that happen (Google goes down…), it’s unlikely to be resolvable within the 10 minutes or so that the retry continues for, so there’s not much point in trying. If we were in control of a system into which we’d built some failure detection and automated failover, then sure. Perhaps it’d help. For our purposes though, we’re relying on Azure and the Google services we integrate with to be highly available. As such, we just immediately carry on with the processing flow and consume the event.

If we did have some more unreliable integrations (perhaps back to some infrastructure we owned) then perhaps it’d make sense to move the ‘Response’ element to after that Step. We could then add a second Step after that element to return a different Response code (e.g. 500) in the case of an error, to cause the Monzo webhook to backoff and retry after a few seconds/minutes.

Alternatively, if we do want to handle those failure cases, then deal with that in the Logic App. Add a subsequent step to provide your error-handling flow and edit the relationship so that only errors are passed into that Step.

Supporting resources

I spent ages trying to work out how to get the Google Sheets example to work, then I Googled and found this useful screencast: