Up bank

Up bank are an Australia based neobank trying to take on the Big 4 in Australia by providing a modern experience to banking. Their app, made in partnership with Ferocia, is something that has made my life a lot easier. It works fantastically.

Recently they decided to release the first beta version of their API. This is somewhat of a first for a bank, while Open Banking has been talked about for some time many banks don’t provide any way to access your own data through an API. Most API’s provided by banks are usually to do with obtaining product information and the like.

Hopefully by releasing an API it will prompt other banks to follow suite.

The API

The documentation for the API is available on Up’s developer site they detail the necessary requirements such as obtaining an access token and the endpoints.

A specific feature of interest is the webhook functionality. Essentially when an event occurs with your funds such as a new transaction is created, processed etc Up will send a HTTP request to an endpoint of your choosing where you can do whatever you like with this event.

Interestingly you can also create, test, and inspect logs for a webhook endpoint via the Up API as well.

A quick Symfony example

The code for this project is available here

When using this project you will need to create an .env.local file with the following variables

UP_BASE_URI=https://api.up.com.au/api/v1/
UP_PERSONAL_ACCESS_TOKEN=       # your personal access token retrieved from Up developer website
UP_WEBHOOK_SECRET_KEY=          # your webhook secret key (read below)

I wanted to be able to quickly create webhooks via the API so I started a new Symfony project with symfony/console and implemented a number of commands.

λ php bin/console
Symfony 5.2.0-RC2 (env: dev, debug: true)

Usage:
  command [options] [arguments]

  ...

  up
    up:ping                     Make a basic ping request to the API
    up:webhooks:create          Create a new webhook with a given URL
    up:webhooks:delete          Delete a specific webhook by providing its unique identifier
    up:webhooks:list            Retrieve a list of configured webhooks
    up:webhooks:ping            Send a PING event to a webhook by providing its unique identifier

Once a webhook is created it will also return a Secret Key, this secret key is then used to authenticate that the webhook messages are truly received from Up. When you have the secret key you will need to set the UP_WEBHOOK_SECRET_KEY environment variable.

Let’s create a webhook pointing to our local machine running the symfony server and ngrok.

Creating a webhook
Creating a webhook

Once the webhook has been created and you’ve set your UP_WEBHOOK_SECRET_KEY we can ping the webhook to confirm it’s working.

Pinging a webhook
Pinging a webhook

As mentioned previously the webhook endpoint we’ve specified also needs to ensure that the webhook is authenticated. This is implemented by created a marker interface called SecuredWebhookController which is then implemented on the Controller that receives the webhook.

Then implementing an EventSubscriber which can intercept the request after it’s been resolved to the Controller but prior to the Controller executing, we can then check to ensure the signature matches the one provided by Up.

<?php

declare(strict_types=1);

namespace App\EventSubscriber;

use App\Controller\SecuredWebhookController;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class WebhookAuthenticationEventSubscriber implements EventSubscriberInterface
{
    private string $secretKey;

    public function __construct(string $secretKey)
    {
        $this->secretKey = $secretKey;
    }

    public function onKernelController(ControllerEvent $event)
    {
        $controller = $event->getController();

        if ($controller instanceof SecuredWebhookController) {
            $receivedSignature = $event->getRequest()->headers->get('X-Up-Authenticity-Signature');

            $body = $event->getRequest()->getContent();
            $signature = hash_hmac('sha256', $body, $this->secretKey);

            if(!hash_equals($receivedSignature, $signature)) {
                throw new \Exception("Unable to authenticate webhook event");
            }
        }
    }

    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::CONTROLLER => 'onKernelController'
        ];
    }
}

By default the webhook endpoint just logs to STDOUT

PING event received
PING event received

Future work

This is all slapped together, I would like to improve this by generating a client using their provided OpenAPI specification as well as integrating Symfony Messenger so that events can be handled asynchronously.