Handling Xero Webhooks With a Laravel Application

Rod Staines
Dec 20, 2020 · 6 min read
Image for post
Image for post

I’ve built a Laravel 7 client management application to manage complex billing for another much larger product. The application creates an invoice and dynamic line items (based on active features, licence overages, crazy pricing tiers, custom discounts and ad-hoc unique items) locally and then exports it to Xero via their API.

Using a daily scheduled job, the payment for these invoices are processed days later using Stripe (credit card or BECS) based on the respective client’s payment terms. A successful payment will then be applied to the invoice in Xero.

But what if a client pays their invoice before the payment due date, or if our in-house finance team make a change to the invoice once it’s in Xero? My application needs to know to ensure the client is not paying twice or paying the wrong amount when it’s picked up by the scheduled job.

Xero Webhooks to the rescue!

Image for post
Image for post

There are a few things you need to configure in your application to satisfy Xero and safely accept payloads. This process is part of the Intent to Receive, which is essentially Xero checking you’re ready to accept payloads and that you’re verifying that the payloads are cryptographically correct.

Xero doesn’t want to be firing webhooks to something that’s not listening, so they will also rate limit based on how your application responds. Xero is expecting timely and accurate response codes (200 or 401).

“If we don’t receive an acceptable response when sending events we will continue retrying the request, with decreasing frequency for up to 24 hours. After 24 hours the webhook will be disabled and needs to be re-enabled.”

There are a few parts to this process, so follow this article and the instructions in order and you should have a relatively pain-free time configuring your application.

Add the webhook key to your .env file

Image for post
Image for post

You will likely already have your Xero Client ID and Client Secret in your .env file, so create a new environment variable for XERO_WEBHOOK_KEY.

Add a new key-value pair to your config file for Xero.

‘signing_key’ => env(‘XERO_WEBHOOK_KEY’),
Image for post
Image for post

Use middleware to verify the payload

In Terminal, use Artisan to create a new middleware:

php artisan make:middleware VerifyXeroToken

Add a new key-value pair to your route middleware array in Kernal.php.

‘verify_xero’ => \App\Http\Middleware\VerifyXeroToken::class,
Image for post
Image for post

In hindsight, it would have been more accurate if I named this middleware ‘VerifyXeroSignature’.

In Terminal, use Artisan to create a new Controller to handle the webhook events:

php artisan make:controller XeroWebhookController

Create a new route in api.php for the incoming webhooks. Assign your new middleware to this route.

Route::post(‘webhook/xero’, ‘XeroWebhookController@handle’)->middleware(‘verify_xero’);
Image for post
Image for post

Create a public URL using Ngrok for testing configurations

In Terminal, run the following command (Mac users):

valet share

You’ll be presented with this beauty. Don’t be concerned if you see some other connection success/failure information here. Those will clear out once you start receiving HTTP requests.

Image for post
Image for post

The generated ‘Forwarding’ URL is your new public URL. Copy the https version of the URL and add it to the ‘Send notifications to’ section of your Xero App’s Webhook settings.

Image for post
Image for post

Press ‘Save’, but hold off on sending the ‘Intent to receive’ until your middleware and Controller are both configured.

Configure your middleware

Image for post
Image for post

After successfully passing through the middleware, the request will be routed to your controller. For testing purposes, simply return a 200 response to Xero without evaluating the payload any further.

Image for post
Image for post

Once you have successfully passed the ‘Intent to receive’ process, you can build out this Controller to handle the payload.

Send the Intent to Receive

Image for post
Image for post

Xero will send multiple webhook events to your endpoint to verify your configuration.

Image for post
Image for post

In my Terminal screenshot above, you can that I received 4 events and only 1 of them was a correctly signed payload.

Image for post
Image for post

Xero is now satisfied, so you’re ready to build your Controller.

Process the request

In the Controller handle method, I’m iterating over the payload events and actioning only the invoice update events. For each event, I save the payload to the database for future reference.

Because Xero expects a response within 5 seconds and the payload contains very little information, I need to make a call back to Xero to get the current invoice version. These calls could take some time and I can’t keep Xero waiting, so I dispatch the processing to a queue using a job I’ve called SyncXeroInvoice.

Image for post
Image for post

After iterating through the events, I return a successful 200 response with an empty body as per Xero’s requirements.

By now the SyncXeroInvoice job is processing in the background. My local invoice will be updated with the latest line items and payments information from Xero.

When it comes time to process a payment for this invoice, my application will now have the correct information.

Hopefully, this article helps you implement webhooks in your application. Let me know if you have any comments or feedback. Happy coding!

Tip: Use Postman during testing

While you’re building out your Controller, I recommend replicating a successful request payload into a tool like ‘Postman’.

In the Terminal screenshot above, you will notice there’s a web interface for Ngrok (mine is http://127.0.0.1:4040).

From the Ngrok menu, select ‘Inspect’. Here you will find each request, related JSON payload and the x-xero-signature header.

Image for post
Image for post

Grab the payload, the header and build the request in Postman. Send the same payload to your application as many times as you need!

The Startup

Medium's largest active publication, followed by +755K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store