bunq API: Configure Callbacks for your Banking Accounts

December 9th, 2023 1505 Words

The digital banking service bunq has an API for their banking accounts. With support for webhooks, you can easily track any activity within your account! bunq calls them Callbacks. This guide explains how to use the bunq mobile application and some cURL requests to get a JSON request for every activity in your bunq banking accounts.

Create a bunq Callback

Other services call them webhooks, bunq calls them Callbacks! A callback consists of an endpoint URL and a type of event. The available types are listed in the API documentation about Callbacks. For tracking the basic account activity, this example will create two Callbacks: one for PAYMENT and one for MUTATION events.

This guide requires a paid bunq account and involves these acitivities:

  • Use the bunq mobile application
  • Create a private / public key with OpenSSL
  • Send simple HTTPS requests with cURL

You can do this!

Create Private Key

To use the bunq API, you first need to generate a typical encryption key pair. The private key is used to create a signature of every API request body; the public key will be uploaded to bunq, so every request’s signature can be verified.

# Create a private key

$ > openssl genrsa -des3 -out bunq.pem 2048

Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:

# Create a public key

$ > openssl rsa -in bunq.pem -outform PEM -pubout -out bunq_public.pem

Enter pass phrase for bunq.pem:
writing RSA key

Register API Client Installation

First, you need to send the public key to the bunq API. This will register your key and allow further API communication.

# Create single line key string
$ > export KEY_DATA=`cat bunq_public.pem`
$ > export KEY=${KEY_DATA//$'\n'/\\n}

$ > curl -X POST https://api.bunq.com/v1/installation \
    -H "Content-Type: application/json" \
    -H "Cache-Control: no-cache" \
    -H "User-Agent: sbstjn.com/0.0.1" \
    -H "X-Bunq-Language: en_US" \
    -H "X-Bunq-Region: de_DE" \
    --data "{\"client_public_key\":\"$KEY\"}"

{
  "Response": [
    { "Id": { "id": 234567891 } },
    {
      "Token": {
        "id": 123456789,
        "created": "2023-12-09 10:00:00.000000",
        "updated": "2023-12-09 10:00:00.000000",
        "token": "yXjmuT5sJP51GNN7CBqKebRDOymq6BUYKJefqitK"
      }
    },
    {
      "ServerPublicKey": {
        "server_public_key": -----BEGIN PUBLIC KEY-----\nMII2323NzaC1yc2EAAAADAQABAAAAQQCo9+BpMRYQ\n6JPTW86CcXrhQ4YATF+5OyTebIx5lBYm8S+/Z\nkCuSXW1sgWxSyKPJo8ejNLlhxjcGaIaKHOysH8ooj4Z\n3MvnAYPujkoELPyNGfkq9NiKKnyt0IWu3FqvcSKoLZa\/dXz\nbVm3xp22Ft0tluXnyPdzZBi9CdKBA7uMVx0iTu\/1XLa4rt\nB7YZ3qRPH0Zz7gJxmGI31j75+L29juLaZPt6fd7rHX+IBGh1UC97nOdvFmiGVwnf\nlQIDAQAB\n-----END PUBLIC KEY-----\n"
      }
    }
  ]
}

The response includes your provided public key as well as the needed token for further API requests to the bunq banking service.

Create bunq API Key

After you sent your public key to bunq, you can head over to the mobile application and create a new API key within the Developers section of your Account Settings.

Create new bunq API key

Next, use the generated API key from the bunq mobile banking application together with the token response from the initial API request to register your local computer as an allowed Device Server to access bunq.

# Set environment variable for API key from mobile application
$ > export API_KEY=uuFCDtBKKsRWy11VJQF8kQvmTGRmyqrJ03cK7jgG

# Set environment variable for Request token
$ > export TOKEN=yXjmuT5sJP51GNN7CBqKebRDOymq6BUYKJefqitK

# Store payload for request in local file
$ > export PAYLOAD="{\"description\": \"sbstjn.com\", \"secret\": \"$API_KEY\", \"permitted_ips\": [\"*\"]}"
$ > echo -n $PAYLOAD > payload_device_server.json

# Generate signature for request payload
$ > openssl dgst -sha256 -sign bunq.pem -out payload_device_server.sha256 payload_device_server.json
$ > openssl enc -base64 -in payload_device_server.sha256 -out payload_device_server.sha256.base64

# Load signature as single line string to variable
$ > export SIGNATURE=`tr -d '\n' < payload_device_server.sha256.base64`

$ > curl -X POST https://api.bunq.com/v1/device-server \
    -H "Content-Type: application/json" \
    -H "Cache-Control: no-cache" \
    -H "User-Agent: sbstjn.com/0.0.1" \
    -H "X-Bunq-Language: en_US" \
    -H "X-Bunq-Region: de_DE" \
    -H "X-Bunq-Client-Authentication: $TOKEN" \
    -H "X-Bunq-Client-Signature: $SIGNATURE" \
    --data $PAYLOAD

{
  "Response": [
    { "Id": { "id": 123456789 } }
  ]
}

When switching back to the bunq mobile application, you see the API key is assigned to your application name now; it can no longer accept other incoming requests.

Create bunq Session

Based on the previous steps, you can now use the bunq mobile banking API to create a new user session for your bunq account. Same as for the /device-server request, use the initially created token of the /installation response together with your API key from the mobile application to create a new session.

# Set environment variable for API key from mobile application
$ > export API_KEY=uuFCDtBKKsRWy11VJQF8kQvmTGRmyqrJ03cK7jgG

# Set environment variable for Request token
$ > export TOKEN=yXjmuT5sJP51GNN7CBqKebRDOymq6BUYKJefqitK

$ > export PAYLOAD="{\"secret\": \"$API_KEY\"}"
$ > echo -n $PAYLOAD > payload_session_server.json

# Generate signature for request payload
$ > openssl dgst -sha256 -sign bunq.pem -out payload_session_server.sha256 payload_session_server.json
$ > openssl enc -base64 -in payload_session_server.sha256 -out payload_session_server.sha256.base64

$ > export SIGNATURE=`tr -d '\n' < payload_session_server.sha256.base64`

# Load signature as single line string to variable
$ > curl -X POST https://api.bunq.com/v1/session-server \
    -H "Content-Type: application/json" \
    -H "Cache-Control: no-cache" \
    -H "User-Agent: sbstjn.com/0.0.1" \
    -H "X-Bunq-Language: en_US" \
    -H "X-Bunq-Region: de_DE" \
    -H "X-Bunq-Client-Authentication: $TOKEN" \
    -H "X-Bunq-Client-Signature: $SIGNATURE" \
    --data $PAYLOAD

{
  "Response": [
    { "Id": { "id": 123456789 } },
    {
      "Token": {
        "id": 234567891,
        "created": "2023-12-09 10:00:00.000123",
        "updated": "2023-12-09 10:00:00.000123",
        "token": "NLaPy51QN7LwVr4KXtOAfY2pjIf5GGPTYspmrnP6"
      }
    },
    {
      "UserPerson": {
        "id": 1122334,
        "created": "2021-10-15 10:00:00.000123",
        "updated": "2023-11-25 10:00:00.000123",
        "status": "ACTIVE",
        "sub_status": "NONE",
        "public_uuid": "c10ee090-0ad3-4ca6-a14b-a2663a08f67f",
        "display_name": "Sebastian Müller",
        "public_nick_name": "Sebastian",
        "language": "en_US",
        "region": "de_DE",
        "session_timeout": 3600,
        "daily_limit_without_confirmation_login": {},
        "alias": [],
        "avatar": {},
        "relations": [],
        "tax_resident": null,
        "notification_filters": [],
        "address_main": {},
        "address_postal": {},
        "first_name": "Sebastian",
        "middle_name": "",
        "last_name": "Müller",
        "legal_name": "Sebastian Müller",
        "date_of_birth": "XXXX-YY-ZZ",
        "place_of_birth": "Example",
        "country_of_birth": "DE",
        "nationality": "DE",
        "gender": "MALE",
        "version_terms_of_service": "1",
        "deny_reason": null,
        "document_issuing_authority": null,
        "document_expiry_date": null,
        "document_status": "ACTIVE",
        "is_primary_document": true,
        "customer": {},
        "customer_limit": {},
        "billing_contract": [],
        "pack_membership": null,
        "premium_trial": null
      }
    }
  ]
}

Take note of that values for Token > token and Response > UserPerson > id . This is your internal User ID and is required for accessing the monetary accounts for which you want to configure the bunq Callback URL. The token is needed for further API requests.

Available bunq Accounts

Next, you can retrieve the available monetary accounts from your bunq account. Use the token value from the previous API response and send a basic GET request:

# Set environment variable for Request token
$ > export TOKEN=NLaPy51QN7LwVr4KXtOAfY2pjIf5GGPTYspmrnP6

# Load signature as single line string to variable
$ > curl -X GET https://api.bunq.com/v1/user/1122334/monetary-account \
    -H "Content-Type: application/json" \
    -H "Cache-Control: no-cache" \
    -H "User-Agent: sbstjn.com/0.0.1" \
    -H "X-Bunq-Language: en_US" \
    -H "X-Bunq-Region: de_DE" \
    -H "X-Bunq-Client-Authentication: $TOKEN"

{
  "Response": [
    {
      "MonetaryAccountBank": {
        "id": 6677889,
        "created": "2023-11-22 10:00:00.000123",
        "updated": "2023-11-22 10:00:00.000123",
        "alias": [],
        "avatar": {
          "uuid": "f836f673-be3a-4bdb-b42b-5bbb461bfbf1",
          "image": [],
          "anchor_uuid": "c908786b-b796-48e3-82d5-f7eca88f0162",
          "style": "NONE"
        },
        "balance": {
          "currency": "EUR",
          "value": "9.99"
        },
        "country": "NL",
        "currency": "EUR",
        "display_name": "Sebastian M\u00fcller",
        "daily_limit": {
          "currency": "EUR",
          "value": "100.00"
        },
        "description": "Default",
        "public_uuid": "56b2e6ca-4ca3-4504-bb32-3c9dd50d89b0",
        "status": "ACTIVE",
        "sub_status": "NONE",
        "timezone": "europe\/amsterdam",
        "user_id": 1122334,
        "monetary_account_profile": {
          "profile_fill": {},
          "profile_drain": null,
          "profile_action_required": "FILL_ACCOUNT_NEEDED",
          "profile_amount_required": {
            "currency": "EUR",
            "value": "50.00"
          }
        },
        "setting": {},
        "connected_cards": [],
        "budget": null,
        "overdraft_limit": {
          "currency": "EUR",
          "value": "0.00"
        },
        "all_auto_save_id": [],
        "total_request_pending": {
          "currency": "EUR",
          "value": "0.00"
        }
      }
    },
    {
      "MonetaryAccountBank": {
        "id": 3344556,
        "created": "2023-12-01 10:00:00.000123",
        "updated": "2023-12-01 10:00:00.000123",
        "alias": [
          {
            "type": "IBAN",
            "value": "NL57ABNA6995898621",
            "name": "Sebastian M\u00fcller"
          }
        ],
        "avatar": {
          "uuid": "28b9fefc-c9e2-4b4a-a3db-b84a96957c78",
          "image": [],
          "anchor_uuid": "06e7d07a-edfb-4f43-9414-33793497eea7",
          "style": "NONE"
        },
        "balance": {
          "currency": "EUR",
          "value": "20.00"
        },
        "country": "NL",
        "currency": "EUR",
        "display_name": "Sebastian M\u00fcller",
        "daily_limit": {
          "currency": "EUR",
          "value": "50.00"
        },
        "description": "Development",
        "public_uuid": "e556323b-9577-4aca-839b-d498ef54d08d",
        "status": "ACTIVE",
        "sub_status": "NONE",
        "timezone": "europe\/amsterdam",
        "user_id": 1122334,
        "monetary_account_profile": {},
        "setting": {},
        "connected_cards": [],
        "budget": {},
        "overdraft_limit": {
          "currency": "EUR",
          "value": "0.00"
        },
        "all_auto_save_id": [],
        "total_request_pending": {
          "currency": "EUR",
          "value": "0.00"
        }
      }
    }
  ],
  "Pagination": {
    "future_url": null,
    "newer_url": null,
    "older_url": null
  }
}

The bunq API response includes an object for every one of your monetary accounts. You can configure a custom webhook (also named Callback in bunq) for each of your accounts individually.

Create a file notifications.json with two Callback endpoint; one for PAYMENT and one for MUTATION events:

{
  "notification_filters": [
    {
      "category": "PAYMENT",
      "notification_target": "https://example.com/incoming/bunq"
    },
    {
      "category": "MUTATION",
      "notification_target": "https://example.com/incoming/bunq"
    }
  ]
}

The listed response of the available bunq monetary accounts included two accounts: 3344556 and 6677889 . To configure the intended Callbacks, send the notifications.json payload to both API endpoints:

# Generate signature for request payload
$ > openssl dgst -sha256 -sign bunq.pem -out notifications.sha256 notifications.json
$ > openssl enc -base64 -in notifications.sha256 -out notifications.sha256.base64

$ > export SIGNATURE=`tr -d '\n' < notifications.sha256.base64`

# Load signature as single line string to variable
$ > curl -X POST https://api.bunq.com/v1/user/1122334/monetary-account/3344556/notification-filter-url \
    -H "Content-Type: application/json" \
    -H "Cache-Control: no-cache" \
    -H "User-Agent: sbstjn.com/0.0.1" \
    -H "X-Bunq-Language: en_US" \
    -H "X-Bunq-Region: de_DE" \
    -H "X-Bunq-Client-Authentication: $TOKEN" \
    -H "X-Bunq-Client-Signature: $SIGNATURE" \
    --data @notifications.json

$ > curl -X POST https://api.bunq.com/v1/user/1122334/monetary-account/6677889/notification-filter-url \
    -H "Content-Type: application/json" \
    -H "Cache-Control: no-cache" \
    -H "User-Agent: sbstjn.com/0.0.1" \
    -H "X-Bunq-Language: en_US" \
    -H "X-Bunq-Region: de_DE" \
    -H "X-Bunq-Client-Authentication: $TOKEN" \
    -H "X-Bunq-Client-Signature: $SIGNATURE" \
    --data @notifications.json

Wow, that’s it! 🎉 Now you will get a nice JSON object whenever something change the balance on your bunq accounts, and for incoming or outgoing payments.

Next, we’re sending this to AWS EventBridge for processing 😍