Keeping your integration in sync: Implementing Xero webhooks using Node, Express, and ngrok
By: Rett Behrens
Tags: Node, API, Webhooks, Xero, Developers

Hello Xero developers and accounting API enthusiasts!

A recent Xero Community post inquired about our Invoice webhooks and how to use them. In this post, I’ll discuss implementing a webhook using Node, Express, ngrok, and our super helpful xero-node-oauth2-app sample app repo.

Understanding Webhooks

Need your app to keep in sync with invoices in Xero? If so, it’s likely you’ve made an initial GET request upon connecting your app, to pull all existing invoices, but then what? How do you keep your app up-to-date with Xero, without scheduling regular API calls to see if there are any new invoices? Or without making an API call per invoice in your database, just to see if something has changed? This scenario could quickly eat up your available API calls and trigger the rate limit.

Wouldn’t it be great if Xero could let you know when something was created or changed and specifically which record to request? That’s where Xero webhooks come in.

A webhook is a subscription to certain specified events within Xero — currently limited to the creation or updating of either Contacts or Invoices. In response to an event in Xero, our API will send a POST request to the URL associated with the webhook — configured in the Apps dashboard of the Xero developer portal.

We can see from the example payload below that each payload contains:

an events array

a last event sequence

a first event sequence

and an entropy

Each event includes:

a resource URL

a resource ID

an event date in UTC

an event type

an event category

a tenant ID

and a tenant type

The Events array: The details of the events that you’ve subscribed to

Last Event Sequence: The sequence number of the last event in this payload

First Event Sequence: The sequence number of the first event in this payload

Entropy: A random string to make the payload more cryptographically secure

Resource URL: The URL to retrieve the resource that has changed

Resource ID: The ID of the resource that has changed

Event Date UTC: The date and time that event occurred (UTC time)

Event Type: The type of event of that occurred

Event Category: The category of event that occurred

Tenant ID: The ID of the tenant that the event happened in relation to

Tenant Type: The type of tenant

A webhook event, as you can see from the sample payload above, does not provide the record details but rather notifies your app that an event has occurred and provides the necessary information to find out more by requesting that record from Xero.

To oversimplify it:

your app receives a webhook event

your app makes a GET request for the resource by resource ID

request for the resource by resource ID if the event was a “Create” you would add the new record to your database

if the event was an “Update” you would compare the resource returned by Xero against the record already in your database and update the relevant fields

trigger any follow up actions your app might perform

Production apps can and will receive webhook payloads containing multiple events so your code should be written to iterate through the array for such cases. Webhooks are batched at the app level, meaning a single payload could contain events for multiple tenants, multiple event categories, and multiple event types.

Steps to implement webhooks

Now we’ve got through the explanation, let’s get into how to implement a webhook in your application. If you haven’t already created an app in the Xero developer portal, go ahead and do so now following the steps in our Getting Started guide. If you’re planning to follow along using the xero-node-oauth2-app sample app, now would also be a good time to clone the repo to your machine and follow the readme to update the .env file with your app credentials.

Ok, now that we’ve got an app created in the Xero developer portal and we’ve added our client ID and client secret to the .env file of xero-node-oauth2-app, we’re going to navigate to the webhooks tab for our specific app.

Next, check “Invoices” to indicate we want to subscribe to Invoice events in Xero and specify the URL we want Xero to send webhook notifications to… but wait, what’s this? The URL must be https? But how are we supposed to test locally?

Introducing ngrok! With ngrok, developers can expose their localhost environment and set up a forwarding public URL that meets https requirements via the command line in their terminal. In addition to setting up forwarding public URL ngrok provides a web interface for developers to inspect http traffic in real time and replay any request by clicking on it — nifty.

We’ll now install ngrok by inputting npm install ngrok -g in the terminal. Next it’s time to start a session and specify the port our app server is running on by inputting ngrok http 5000 in the terminal. We’re specifying port 5000 because that’s what our sample app runs on by default. If it worked, you should see something like this in your terminal.

Copy the https ngrok.io address and paste it into the notifications URL field and specify the endpoint. Now we can test webhooks locally. Xero will send notifications to this public URL and ngrok will tunnel those requests and responses to and from our localhost server.

IMPORTANT — notice that the session will expire after 8 hours, this means you’ll need to run the command again to generate a new session and a new ngrok URL which you’ll need to update your webhook URL in Xero. You can get a stable ngrok URL if you choose to pay for an account with ngrok.io if you would prefer to not have to modulate this URL every time you’re testing your webhook.

Click “Save” and Xero will generate your webhook key. Copy and paste your webhook key into your .env file.

Now it is time to implement our webhook endpoint in our project!

Per the Xero webhook documentation, our endpoint must meet the following criteria:

It uses HTTPS on the standard 443 port It responds within 5 seconds with a 200 OK status code There is no body in the response There are no cookies in the response headers If the signature is invalid a 401 Unauthorised status code is returned

“Hold up, signature, what signature?”, you might be asking your screen right now… For added security, Xero signs its webhook requests with an x-xero-signature hash value in the request headers. In order to determine whether the signature is valid or not, the payload is hashed using HMACSHA256 with your webhook signing key and base64 encoded and then compared against the x-xero-signature header value. If the signature computed from the payload matches the signature from the headers, you have yourself a correctly signed payload and can respond with a status of 200. Otherwise, you have an incorrectly signed payload and need to respond with a status of 401.

With consideration to the requirements outlined above, let’s implement our webhooks endpoint and signature verification method.

IMPORTANT — you need to configure your body-parser middleware to accept the raw request body from the webhook event without modification. If this body is modified at all, your code will fail to verify the signature and will fail Xero’s “Intent to receive” validation.

Send it

That’s it! You’re all set to receive webhook events in just a few lines of code! Now we can test out our code by requesting Xero send “Intent to receive” validation requests. Before we request ITR from Xero, we’ll need to have at least one organisation connected to our application. If you’re following along with our sample app, the necessary code and ui are already set up — just click the button that says “Connect to Xero”.

If you’re building your own project and haven’t gotten that far yet, check out our guides on connecting via Postman or Insomnia. Once you’ve confirmed you’ve connected at least one organisation to your application, click the button that says “Send ‘Intent to receive’” on the webhooks tab of your app’s details view in the Xero developer portal and watch the ITR requests coming in from Xero. You should see something similar out in your terminal showing the console logged events and signature verification, the POST requests and our 401 or 200 responses to Xero, and finally you’ll see the Status update from “‘Intent to receive’ required” to “OK” with a timestamp in the developer portal.

Looking good

Solid

Noice

Now that Xero has validated our signature verification, we can start receiving events for when an invoice is created or updated. Let’s test it using the Xero interface to create and then update an invoice, watching the webhook events roll in, in real time. First we’ll create an invoice. Then we’ll edit the invoice we just created. You’ll receive two webhook events from Xero.

Awesome, we’ve done it! We’ve successfully implemented webhook functionality into our integration.

To bring this full circle back to the scenario I described at the start of this post, our potential next steps would be to write code so our app makes a GET request for the resource by resource ID. If the event was a “Create” you would add the new record to your db, or if the event was an “Update” you would compare the resource returned by Xero against the record already in your db and update the relevant fields, then trigger any follow up actions your app might perform, based on the changed status.

If you prefer your tech content in video form, you’re in luck!

If you’re building an integration to Xero and leveraging webhooks to deliver a #beautiful experience for your users, tell us about it in the comments below and partner with us to get your integration certified and listed in our app marketplace.