Developer documentation
The Sandbox offers a safe and simple way to try out Nordea’s APIs. Developers can familiarise themselves with existing and upcoming functionality. Moreover, it allows the developers to experiment and build applications that use the Nordea API before release. See Sandbox for more information regarding the Sandbox and what it is.
The purpose of this documentation is to give an overview of all the APIs which are included in Nordea API Sandbox - both existing production release and upcoming versions. There is separate documentation for each API which includes the full API reference of the particular API.
This API implementation is Representational State Transfer Service (RESTful service in short), and the responses produced by the Nordea API are in JavaScript Object Notation, JSON format. The API also consumes JSON so requests sent to it should have the request body in JSON format.
The links to download the Swagger files can be found here
Note that this API might change in the future based on feedback. Backward compatibility is not guaranteed. This document discusses the latest version of the APIs.
API Overview
The API set consists of Account Information Services (AIS), Payment Initiation Services (PIS), Confirmation of Funds (CoF) and Access Authorisation APIs.
The latest versions of the APIs are as follows:
API | Version in URL |
---|---|
AIS | v5 |
Cards | v5 |
Commercial Cards | v1 |
COF | v1 |
PIS | v4 |
Redirect Access Authorisation | v5 |
Decoupled Access Authorisation | v5 |
Commercial Cards Access Authorisation (redirect) | v1 |
Confirmation of Funds Access Authorisation (redirect) | v1 |
Account Information Services API
This service provides the ability for PSD2-regulated TPPs with the ‘AISP’ role to request information on one or more Nordea customer payment accounts which are accessible online via the Nordea banking channel. The information can be categorised as follows:
- Account list
- Account details
- Transaction history
In the Sandbox, developers can create accounts and delete accounts that were created. Also, developers can create transactions for the created accounts. It supports the following request methods: GET, POST, DELETE
Note, that the endpoints related to posting and deleting of the accounts and transactions are provided for the Sandbox only. Please do not put real accounts’ data into the Sandbox. Kindly note that any data uploaded to the system/service may be viewed by Nordea. We do not allow any actual customer data to be stored in our service.
Cards API
This service provides the ability for PSD2-regulated TPPs with the ‘AISP’ role to request information on one or more Nordea customer Credit Cards which are accessible online via the Nordea banking channel. The information can be categorised as follows:
- Card list
- Card details
- Transaction history
Commercial Cards API
This service provides the ability for PSD2-regulated TPPs with the ‘AISP’ role to request information on one or more Nordea customer Commercial Cards (First Cards) which are accessible online via the Nordea banking channel. The information can be categorised as follows:
- Card list
- Card details
- Transaction history
This API utilise Commercial Cards Access Authorisation API.
Confirmation of Funds API
This service enables TPPs with the CBPII role to get a Y/N response whether customer (resource owner) has enough funds on their account for a particular payment amount.
Payment initiation services
This service provides the ability for PSD2-regulated TPPs with the ‘PISP’ role to initiate payments on behalf of a Nordea customer from a payment account that is accessible online - and with permission to make payments online - via the Nordea banking channel.
Access Authorisation
Access Authorisation API is responsible for authenticating the customer (resource owner) and acquiring the customer’s authorisation to share account information with the TPP (client) - and/or authorisation for the TPP to initiate payments on the customer’s behalf. The resulting access token is used by the TPP to consume AIS and PIS APIs.
Commercial Cards Access Authorisation
Dedicated for Commercial Cards Access Authorisation API to authenticate the cardholder and acquire the cardholder’s authorisation to share commercial cards information with the TPP (client). The resulting access token is used by the TPP to consume Commercial Cards API.
Confirmation of Funds Access Authorisation
Dedicated for Confirmation of Funds Access Authorisation API to authenticate the customer (resource owner) and acquire the customer’s authorisation to confirm whether has enough funds on it’s account.
Terminology
In this section, to avoid confusion, we go through some terminology used in the documentation.
Term | Explanation |
---|---|
Sandbox | Sandbox in the context of this API means a mocked service whereby the data returned by the API consists of example data. Its purpose is to mimic the current production and upcoming versions of the API. The Sandbox API will always have the latest version of the API, meaning that all new versions appear in the Sandbox before they are introduced into the production. Developers can create their account and transaction data for use in the Sandbox environment. |
Resource owner | The resource owner refers to the bank customer who uses the client (TPP application) and authorises client access to their accounts |
Third Party Provider (TPP) | Third-Party Provider (TPP) is the provider of one or more applications which the resource owner (customer) uses. TPP is the client/consumer of the API. |
Client | The client refers to the consumer of the API, which is commonly a TPP application. |
API Call | API call is a request towards the API which receives a response. The API is by design stateless, and therefore it does not “remember” anything about previous requests, i.e., there is no session. Therefore every request made towards the API must contain certain headers so that the API can authenticate and authorise the client. |
API Console | API Console is a tool on the API portal which lets users try out API calls in their web browser quickly. |
Authentication | Resource owner (customer) authentication: the process by which the resource owner identifies themselves to Nordea. The client (TPP application) authentication: the process which provides the correct identity of the client; a key component in enforcing that clients can access only the resources that they are allowed to. |
Access authorisation | Access authorisation is the process through which the client obtains permission to access the resource owner’s data and services at the bank. |
Access Scopes | Access scopes map to a subset of the consent negotiated between the resource owner (customer) and the client (TPP). Scopes dictate what data the client can access and what services the client can consume. |
Consent | Consent is agreed between the resource owner (customer) and the client (TPP). It includes what data is to be shared, what services are to be performed on the resource owner’s behalf, the duration, and for what purpose. Once agreed, some of this information is used to start the access authorisation flow through the Access Authorisation API. |
Production Version of the API | The production version of the API refers to the actual release version of the API which this Sandbox mimics. It will allow clients to access the real banking data, subject to the resource owner’s consent. |
Access Token | A token which is retrieved by the client after successful access authorisation flow. The access token is passed by the client in all AIS, PIS and COF API calls. |
Refresh Token | A token which is retrieved by the client after successful access authorisation flow. The refresh token is passed by the client in token exchange call to obtain a new access token after access token duration has expired. |
Authorisation Code | The authorisation code (or ‘auth code’ for short) is a code provided to the client during the access authorisation flow; this code short-lived and exchanged for an access token. |
Availability level | Availability tells in which part of the delivery lifecycle the given operation, parameter, model or property is. |
API HTTP Methods
RESTful APIs like this one use HTTP methods to perform actions to fetch, modify, add, or delete resources. Here we list methods used by this API.
- GET - This method reads a resource and returns it. It returns 200 on success.
- POST - This method creates a new resource. It returns 201 on success.
- PUT - This method requests that the entity provided is stored in the URI provided. It returns 200 on success.
- DELETE - This method deletes the resource identified by URI. It returns 200 on success.
Following table shows which methods are supported by the APIs.
API | GET | POST | PUT | DELETE |
---|---|---|---|---|
AIS | X | X | - | X |
Cards, Commercial Cards | X | - | - | - |
COF | - | X | - | - |
Redirect Access Authorisation | - | X | - | - |
Decoupled Access Authorisation | X | X | - | - |
Commercial Cards Access Authorisation | - | X | - | - |
Confirmation of Funds Access Authorisation | - | X | - | - |
PIS | X | X | X | - |
Note that for COF (all methods) and for AIS, POST, and DELETE are used in the Sandbox only
API responses and response codes
This API consumes JSON objects and returns responses as JSON objects. Consult the API reference or the examples sections of each API page to see how the response objects look like. The responses are returned encoded in UTF-8 format.
The objects in which the API returns as a response are specified in the API reference and they all contain some attributes that are common to all objects. For instance, every object has a return code embedded in them which makes it easy for application developers to check whether the request was successful or not. See the examples to learn how to make requests.
Response format
The AIS API response format is the following (response data is omitted).
{
"group_header": {
"creation_date_time": "2017-06-01T10:12:42Z",
"http_code": 200,
"message_identification": "7376382343"
},
"response": {
}
}
Where the group_header
contains the http_code
, creation time of the response, and message identifier. The backend generates the message identifier for each message.
The response contains the actual payload of the response, and the endpoint in question defines its structure.
API response codes
The API response HTTP codes are returned for the user application within the JSON object. These codes can be divided into four categories.
- 2xx Success
- 3xx Redirection
- 4xx Client errors
- 5xx Server error
The return codes used in this API are covered here or in the API reference.
Connecting to API
To be able to use and connect to the API there are few requirements.
- Application has to be created.
- Client ID is required.
- Client secret is required.
- Signature is required.
- Access token is required.
- Refresh token is required.
The client ID and client secret are generated after the application is created on the My apps page. The client ID and client secret can be found when clicking the app icon after the app is created. The access token and the refresh token must be retrieved by using the Access Authorisation API, see the access authorisation documentation on how to do this. The token generation process is also briefly discussed in the client authentication section.
When making requests, the client ID, client secret, and access token are sent in a header of each request. Note that both, client ID and secret are always sent, in every single request that is made towards the API.
Here is an example of how the parameters look in the request header (some headers are omitted):
Authorization: Bearer YOUR-SECRET-ACCESS-TOKEN-HERE
X-IBM-Client-Id: ababababab-fefe-fafa-b3b3-111112a11111
X-IBM-Client-Secret: saerw4RAWEfwq4ir240jriw40jar2304jra4
If the client secret is lost or leaked, it can be regenerated by clicking the reset button from the apps page. See the examples section of any API to learn how these headers are sent.
Note that the
X-IBM-Client-Id
andX-IBM-Client-Secret
parameters are only used by the client. Also, the access token is made available for the client; the access token should be stored by the client and it should not be given to the end-user.
Client authentication
In the previous section, we described the requirements to connect to the API. The client ID and client secret are parameters that are configured to the client, and they are never exposed to the actual application user.
Initiating the access authorisation flow
See the access authorisation API specific documentation for example how the access authorisation flow works.
Retrieving an access token and a refresh token
You will receive an access token from Nordea for a period defined by you up to 180 days. You are only entitled to access the payment transactions executed in the last 90 days when using this access token, unless strong customer authentication is performed.
See the access authorisation API specific documentation for example how the access authorisation flow works.
Applications
The client (TPP) application might refer to a website, mobile application, and so on. The resource owner (customer) authorises access for the client application (consumer of the API) to use API resources. This authorisation is acquired via the access authorisation flow.
When developing an application, you should also check the security section below.
Migrating an application to the production environment
To be able to use the application in the production environment with real data, the application owner needs to get access to the production environment. Please raise a support ticket to get more information.
API Swagger definition
The Nordea API specification is also available in the Swagger format. Swagger is the world’s largest framework of API developer tools for the OpenAPI specification (OAS). More information about it can be found here.
The Swagger files for this API can be downloaded from these links:
- For v1: COF, Confirmation of Funds Access Authorisation, Commercial Cards Access Authorisation
- For v4: PIS, PIS-SE.
- For v5: AIS, Access Authorisation
In Nordea API there are some extensions in use, as listed below:
x-availabilityStatus
x-availabilityInfo
x-example
x-deprecationInfo
x-removalInfo
and for monetary amounts, which are decimal strings
x-minimum
x-exclusiveMinimum
x-maximum
x-exclusiveMaximum
The x-example
is used to specify response examples.
The x-availabilityStatus
and x-availabilityInfo
are used to specify availability status and additional information about the availability status respectively. The availability status information can be, for example, an explanation of why the given feature has certain availability status. The availability status can be one of the availability statuses listed below (see availability statuses in APIs).
The x-deprecationInfo
gives textual information when the given feature is deprecated, for example, month, date, quarter, or version.X-removalInfo
is estimated removal time of the given feature, for example, month, date, quarter, or version.
The minimum and maximum vendor extensions are used to specify minimum or maximum amounts for the monetary amounts. The difference between exclusive and non-exclusive is that in the exclusive the minimum or maximum is not included in the value, for example, if x-exclusiveMinimum
is set to “0” then, “0” cannot be specified as a value and it must be larger than that whereas x-minimum
of “0” allows “0” as a minimum value.
Note that to avoid parsing monetary amounts as inaccurate floating-point numbers, they are serialized as strings which include the decimal format of the value. These decimal value strings should be parsed by using an appropriate library, for example, BigDecimal in Java or bignumber.js in JavaScript to avoid inaccuracies in calculations. Using floating-point numbers can lead to rounding errors and inaccurate results, so please do not use them.
The Swagger specification uses polymorphism to simplify the API definition; it is used in models which share common properties and reduces repetition in models of Nordea API, we use the allOf
keyword to inherit base models. More information about the polymorphism can be found here.
As an example, polymorphism is used in the DebitTransaction
model which inherits the Transaction
model.
Differences between the upcoming production API and Sandbox API
The production version of the API provides access to real customer data. For example, you will be able to get a real account and transaction information and initiate real payments. The Sandbox is the environment where the application developers can develop their applications before they are promoted to production. The Sandbox API is a superset of the production API, and there will be ‘Sandbox-only’ features in the Sandbox which are never going to be in the production version of the API. The Sandbox and production APIs might have beta features that are subject to change in the future because they are developed based on the feedback. These beta features are included so the developers can already test them and give feedback regarding them. The beta features are marked as a beta in the API reference which can be found at the bottom of this page.
Availability statuses in APIs
The availability statuses are meant to inform developers about the feature lifecycle. The availability status is defined for API operations, parameters, models, and properties. These are defined in the API Swagger definition and can also be seen in the API reference.
There are five availability levels, which are:
Level | Description |
---|---|
Beta | The Beta availability level is for the features that are already implemented but are subject to change in the future based on any feedback. These features can be used by developers in Sandbox and production, but it cannot be guaranteed that breaking changes are not introduced in the future. The beta features might be even completely removed in future releases if deemed necessary. Note that also the beta feature changes first appear in the Sandbox environment and then later in the production environment. This way the developers have time to adapt to the upcoming change in the production API. |
Deprecated | The deprecated availability level is for features that will be removed in the future. The actual time for how long the deprecated features will remain supported has yet to be decided but developers will be informed well in advance before features are removed. |
Draft | The draft availability level is for features that are still in the design phase, and they lack implementation. These are added to give a sneak peek for the developers what is coming to be added to the APIs in the future. |
Stable | The stable availability level signals that this feature is in production use and should be used in production applications. No breaking changes will be made to the stable features without deprecating them. |
Sandbox only | ”Sandbox only” is an availability level which indicates that the feature is only available in the Sandbox environment and it will never be available in the production environment. For instance, some application testing features have such availability level. |
The stable features are found in the Sandbox and production environments. The same is true for beta and deprecated features.
Draft availability level features are not yet implemented, so they are not found from any environment, they can be only reviewed only from the upcoming API features section.
The beta and draft availability levels are introduced because it will help the API development at the beginning, i.e., it is faster to push new features into production. The beta and draft will become less and less important when the API matures.
Feature deprecation and API lifecycle
When a feature is flagged as deprecated, clients will be notified about this. The features are first deprecated in the Sandbox. This acts as an alert that the applications using the API should be updated to adapt to the newer version of the API.
When deprecation is set for a feature, and there will be replacing features, e.g., if something gets renamed, it will be made available immediately when deprecation is set. The deprecated feature and the replacing feature will co-exist in the API for some period, and after the period, the deprecated feature is removed.
If the feature is removed completely, then it will just disappear after the deprecation period.
Note, that features here can refer to models, operations, parameters, and properties.
The production version of the API is updated after the Sandbox, that is, the deprecation flags and replacing features first appear in the Sandbox and later to the production API.
API versioning
The API is versioned by the version number in the URL, for example, if the URL is:
https://open.nordea.com/personal/v5/some/api/endpoint
Then the version of the API and endpoint in question is version 5 (v5). When new versions are released, the version numbering will be incremented, that means, next version will have the following URL:
https://open.nordea.com/personal/v6/some/api/endpoint
When APIs reach deprecated availability status, the application should be updated to use the newer version of the APIs as soon as possible.
The API version number in the URL indicates the major version of the API. There can be minor updates to the APIs which do not change the major version number. The major version changes only when large changes are made which break backward compatibility.
Security considerations
Security is an important aspect, and this API is developed by using well-tested and widely adopted security schemes.
The client ID, client secret, access token, and signature are sent in the header of the request. All the communication between the API and the client is secured by TLS, which encrypts the traffic. When developing an application, it is important to keep the credentials and tokens safe and handle them carefully to avoid the leakage of the secrets in the wrong hands.
There are many good resources online regarding security, for example Twitters Best Practices: Security guide. Also, OWASPs developer guide is a valuable resource when designing secure web applications.
Error codes and responses
Every response returned by this API has a response code. Response codes can be used to check the result of the requests, e.g., was the request successful or did it fail.
The following table shows the return codes used by AIS API:
HTTP Status Code | Text | Description |
---|---|---|
200 | OK | Request was fulfilled. |
201 | Created | The request has been fulfilled, resulting in the creation of a new resource. |
302 | Found | Redirect. |
401 | Unauthorized | Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided. |
403 | Forbidden | The request was valid, but the server is refusing action. The user might not have the necessary permissions for a resource. |
404 | Not Found | The requested resource could not be found but may be available in the future. |
Please note that the underlying data model for account information is the same in all AIS responses. Some endpoints however return data for more than one account, that is the list accounts endpoint returns data for multiple accounts whereas the account details endpoint returns data for just one account.
HTTP header fields
Each request requires a set of mandatory HTTP fields, as tabulated below:
Field | Description | Remarks |
---|---|---|
X-Nordea-Originating-Date | HTTP header element for date and time represented as RFC 7231 Full Dates. example date: Wed, 24 Apr 2019 14:00:37 EEST, It allows to set it under JavaScript in the browser while standard ‘Date’ is forbidden | |
X-Nordea-Originating-Host | HTTP header element for the specification of the domain name of the server. It is similar to standard HTTP ‘Host’ header but it won’t be overridden by proxies. It also allows to set it under JavaScript in the browser while standard ‘Host’ is forbidden | |
Digest | Digest header as defined in [RFC3230] contains a Hash of the message body | PSD2 APIs (Access Authorisation, AIS, PIS) from v4 onwards and COF; |
Signature | Application-level signature of the request by the TPP, using QSealC | PSD2 APIs (Access Authorisation, AIS, PIS) from v4 onwards and COF; |
X-IBM-Client-Id | Client ID provided by Nordea, identifying the TPP application | |
X-IBM-Client-Secret | Client secret provided by Nordea’s authentication credentials of the TPP application |
As well as the mandatory fields, the following optional fields must be provided when the PSU is present (i.e. in session with the TPP):
Field | Description | Remarks |
---|---|---|
X-Nordea-Originating-User-Ip | PSU IP address | |
X-Nordea-Originating-User-Agent | PSU user agent |
Please note that more information on these mandatory and optional headers is available in the downloadable Swagger definition files and API Reference sections.
Common issues
In this section, we will collect the common issues that the users of the API face. This section will be updated over time.
Access token and code, and refresh token - what are these?
An access token is sent to the API within the request header, it is used by the API to authenticate and authorise the client acting on behalf of the resource owner. Clients cannot use the API without an access token.
Auth code is short-lived, one-time usage, and exchanged for an access token when during access authorisation.
The refresh token is long-lived, one-time usage, and exchanged for an access token after the access token duration has expired.
Problems with access authorisation
Please see the access authorisation flow section in Access Authorisation API documentation.
Client secret is lost
If you lose the client secret, you have to generate a new one. To do this, just go to your apps, find the correct app, and click its icon and click reset client secret.
Application ID is not registered
If you receive this error when making a request, please double-check from the Sandbox that you are subscribed to the APIs you want to use.
API returns 500 and not the meaningful error message
In this case, most commonly the request or request payload is invalid. Please check the request you are making carefully. The error messages are not descriptive at the moment, and this issue is noted. The error messages will be improved over time.
Postman collection does not work properly
First, please check that the client secret and client ID are set properly.
Next, ensure that Automatically follow redirects is disabled in Postman, for this to work, you need to have the interceptor extension installed and activated for Postman.
Getting started with the API using Postman
In this section, we take a look at how to get started with the API by using Postman. If you are new to Postman, see the documentation and tutorials.
First, download the collection for Postman:
v5:
- Including all countries (FI, SE, DK, NO): here
and import it to the Postman. Enter your application ID and application secret to the existing environment. You can find the correct values for those from the app console in the API Market Sandbox.
For a full step-by-step tutorial on getting started with Postman, see Step by Step Guide to Getting Started With Postman
On how to manage the environments, please see:
Finally, make sure that you disable Automatically follow redirects from the Postman settings. For instructions on how to do this, see this.
Code examples and an example application
There are multiple examples on the Github page of Nordea Open Banking. These examples include example application which uses this API, check it out to see how the API can be used programmatically. The readme of the application has instructions on how to run it. Note however that the example application SHOULD NOT BE USED IN PRODUCTION. There is, for instance, no TLS configured by default and there are no unit tests, its purpose is just to be an example of how the API can be used, so be warned. If you find a bug, have any comments or ideas, please open an issue with the GitHub or make a pull request.
Authentication with eIDAS certificate
Definition of payment service directive and regulatory technical standards
PSD2 and the EBA regulatory technical standards on strong customer authentication and common and secure communication (hereafter the ‘RTS’) regulate the access to payment service user’s accounts. RTS (article 34) specifies that for identification payment service providers shall rely on QSealC or QWAC certificates.
Nordea is using QSealC -type of eIDAS certificate for TPP authentication as defined by RTS. In practice, this means a digitally signed request where the TPP will use their eIDAS certificate, issued by one of the QTSPs operating in EU/EEA.
A National Competent Authority in each country grants a PSD2 license to the TPP, including a unique ID of the TPP. By acquiring an eIDAS certificate from a QTSP, the certificate holds this unique ID which is then used to identify the TPP in question when establishing a connection to one of the ASPSPs.
For details, see:
- Payment Services Directive 2 - PSD2
- Regulatory Technical Standards - RTS
- Qualified Certificate Profiles by ETSI
Issuers of the eIDAS certificates
There are currently several QTSPs operating in EU/EEA who can issue QSealC certificates. EU maintains a website which lists all issuers. The website is called Trusted List Browser. From the page, a QTSP can be located by selecting a type of service, which should, in this case, be “Qualified certificate for electronic seal” under “Qualified trust services”.
For details, see:
How does it work?
An eIDAS QSealC certificate is used by the TPP to digitally sign all API requests. The signing process is defined by ietf.org in HTTP Message Signatures. The AIS and PIS APIs have a mandatory Signature
header. All TPPs are required to use eIDAS certificates for authentication and authorisation after September 14, 2019.
The signature is sent in the ‘Signature’ HTTP header as described in the RFC. The keyId is the client ID of your application, created during the registration to the API Market.
The request-target is a combination of the HTTP action verb and the request URI path. A sample GET and POST request with signature are presented in the following examples.
For example (request-target): get /personal/v5/accounts/<accountId>
The following Signature
headers are required for all GET requests: “(request-target) x-nordea-originating-host x-nordea-originating-date”:
GET: https://open.nordea.com/personal/v5/accounts/<accountId>
X-Nordea-Originating-Host: open.nordea.com
X-Nordea-Originating-Date: Thu, 05 Jun 2019 21:31:40 GMT
Signature:
keyId="<clientId>",algorithm="rsa-sha256",
headers="<signature headers>", signature="<signature code>"
The following Signature
headers are required for all POST or PUT requests: “(request-target) x-nordea-originating-host x-nordea-originating-date content-type digest”:
POST: https://open.nordea.com/personal/v4/payments/domestic
X-Nordea-Originating-Host: open.nordea.com
X-Nordea-Originating-Date: Thu, 05 Jun 2019 21:31:40 GMT
content-type: application/json
digest:
SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=
{
"amount":
"2.00",
"currency":
"SEK",
"debtor": {
"account": {
"_type": "BBAN_SE",
"currency":"SEK",
"value": "5345433453"
},
"message": "Test"
},
"creditor": {
"account": {
"_type": "BBAN_SE",
"currency":"SEK",
"value": "434500"
},
"message": "Fifth msg",
"name": "Company A"
},
"externalId":"string"
}
Signature: keyId="<clientId>",algorithm="rsa-sha256",
headers="<signature headers>", signature="<signature code>"
Note: Request parameter
content-type
value depends on the endpoint you are consuming. For example, useapplication/x-www-form-urlencoded
in/token
endpoints.
Note: For Sandbox,
keyId
andalgorithm
inSignature
are static stringsclientId
andrsa-sha256
.
Note: For production APIs,
keyId
inSignature
is the actual client ID provided to the client application andalgorithm
in signature is a static stringrsa-sha256
.
Example code: eIDAS signature creation
All of our PSD2 compliance APIs require a signature header created using the requested content and signed by eIDAS private key. We follow the HTTP Message Signatures but we have some restrictions as listed below:
- The only allowed
algorithm
isrsa-sha256
- The key size for the used RSA key pair has to be at least 2048 bit
- The
keyId
is theclientId
of your application originating from the API MArket Sandbox- We require the following headers to be used in the signature GET and DELETE request:
(request-target) X-Nordea-Originating-Host X-Nordea-Originating-Date
- POST, PUT and PATCH request:
(request-target) X-Nordea-Originating-Host X-Nordea-Originating-Date Content-type Digest
- The
request-target
is a combination of the HTTP action verb and the request URI path
- We require the following headers to be used in the signature GET and DELETE request:
The final signature should be created similar to this:
signature="Base64(RSA-SHA256(signing string))"
The following is some sample JAVA code to assist developers in creating the signature:
import lombok.SneakyThrows;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpRequest;
import org.tomitribe.auth.signatures.Signature;
import org.tomitribe.auth.signatures.Signer;
import java.net.URI;
import java.security.Key;
import java.security.KeyStore;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import static java.util.Optional.ofNullable;
import static org.apache.logging.log4j.util.Strings.isNotEmpty;
import static org.springframework.http.HttpMethod.GET;
/**
* This class is for example purpose only and is not intended for production use.
* You must modify it to correspond to your encryption keys and setup.
* Example of Signature headers:
* X-Nordea-Originating-Date: Wed, 06 Nov 2019 08:22:42 GMT
* X-Nordea-Originating-Host: open.nordea.com
* Signature: keyId="clientId",algorithm="rsa-sha256",headers="(request-target) x-nordea-originating-host x-nordea-originating-date content-type digest",signature="<signature code>"
* Digest: sha-256=7ehEApNpa/FuZeL9uEVCI/6Mwp41K0bYG71yTS06Kcs=
*/
public class QSealCSignatureExample {
{%raw%}// Insert keystore passphrase and location according to your environment
private static final char[] STORE_PASS = "YOUR KEYSTORE PASSPHRASE".toCharArray();
private static final String keyStoreLocation = "YOUR KEYSTORE.p12";
// Insert values accordingly to your clientid, host and date
private static final String keyId = "clientId";
private static final String originatingHost = "open.nordea.com";
private static final String originatingDate = "Wed, 06 Nov 2019 08:22:42 GMT";{%endraw%}
private static final String[] GET_HEADERS = new String[]{"(request-target)", "x-nordea-originating-host", "x-nordea-originating-date"};
private static final String[] INSERT_HEADERS = new String[]{"(request-target)", "x-nordea-originating-host", "x-nordea-originating-date", "content-type", "digest"};
private static Key key;
@SneakyThrows
public QSealCSignatureExample(@Value(keyStoreLocation) Resource keys) {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(keys.getInputStream(), STORE_PASS);
this.key = keyStore.getKey("privatekey", STORE_PASS);
}
@SneakyThrows
public String createSignatureHeader(HttpRequest request, byte[] body) {
if (request.getMethod() == GET) {
return createGetSignatureHeader(request);
}
return createInsertSignature(request, body);
}
@SneakyThrows
private String createGetSignatureHeader(HttpRequest request) {
String path = getPath(request.getURI());
Map<String, String> headers = createMandatoryHeaders();
Signature signature = new Signature(keyId, "rsa-sha256", null, GET_HEADERS);
return new Signer(key, signature).sign(request.getMethod().name(), path, headers).toString();
}
@SneakyThrows
private String createInsertSignature(HttpRequest request, byte[] body) {
String path = getPath(request.getURI());
Map<String, String> headers = createMandatoryHeaders();
headers.put("Digest", calculateDigest(body));
headers.put("Content-type", ofNullable(request.getHeaders().getContentType()).map(Objects::toString).orElse(null));
Signature signature = new Signature(keyId, "rsa-sha256", null, INSERT_HEADERS);
return new Signer(key, signature).sign(request.getMethod().name(), path, headers).toString();
}
private Map<String, String> createMandatoryHeaders() {
Map<String, String> headers = new HashMap<>();
headers.put("x-nordea-originating-host", originatingHost);
headers.put("x-nordea-originating-date", originatingDate);
return headers;
}
public String calculateDigest(byte[] body) {
return "SHA-256=" + new String(Base64.getEncoder().encode(DigestUtils.sha256(body)));
}
private String getPath(URI requestURI) {
return requestURI.getRawPath() + getQueryIfNotEmpty(requestURI.getRawQuery());
}
private String getQueryIfNotEmpty(String query) {
return isNotEmpty(query)
? "?" + query
: "";
}
}
How to test the Sandbox with eIDAS certificate?
For testing TPP authentication with signed requests user of Nordea Sandbox doesn’t need to acquire a real QSealC test certificate. A registered TPP can download a test/demo certificate from the Nordea API Market and use it for setting up and test API requests. The purposes of this setup are to make the test a simple and quick process, but the common QSealC test certificate is not used for identification in Sandbox. The demo certificate contains all roles (AIS, PIS, COF etc.)
Before downloading the test/demo certificate from the API Market, the user must accept the terms and conditions stated beside the download button. The terms and conditions require that the test/demo certificate is used only towards the Nordea API Market within the EU/EEA area.
Note, that testing with any other certificate than the current one which is downloadable from the API Market, will always lead to bad requests. You can set signature header to SKIP_SIGNATURE_VALIDATION_FOR_SANDBOX in case you want to test without a signature.
The downloaded demo certificate is in #p12 format, including the private key, and is placed in the .zip container together with some additional files like a readMe.txt -file. The certificate must be extracted from the .zip before the usage. The password for the .p12 certificate file is 1111.
The following flow diagram illustrates the signed API call flow.
Testing eIDAS signature
Testing eIDAS signature with Postman
Copy script from below and paste it to the Pre-request Script tab in Postman (note that Postman must be a standalone application instead of the Chrome extension):
{%raw%}// Load Forge library
// !!!!!!!!!!!!!! Unsafe: it is better to download and compile forge lib from https://github.com/digitalbazaar/forge and put it to service managed by you instead of using
// https://raw.githubusercontent.com/loveiset/RSAForPostman/master/forge.js or just copy whole script to Postman Pre-Script section{%endraw%}
if (!pm.globals.has("forgeJS")) {
pm.sendRequest("https://raw.githubusercontent.com/loveiset/RSAForPostman/master/forge.js", (err, res) => {
if(err) {
console.log(err);
} else {
pm.globals.set("forgeJS", res.text());
}
});
}
eval(postman.getGlobalVariable("forgeJS"));
{%raw%}// Common{%endraw%}
function getHeaderValue(headerName) {
const headerValue = request.headers[headerName];
if (headerValue === undefined) {
throw new Error(`Required header: ${headerName} is not defined`);
}
return resolveVariables(headerValue);
}
function resolveVariables(textWithPossibleVaraibles) {
return textWithPossibleVaraibles.replace {% raw %} (/{{(\w*)}}/{% endraw %}g, (str, key) => {
const value = environment[key];
return value === null ? "" : value;
});
}
{%raw%}// Digest Calculation{%endraw%}
function resolveRequestBody() {
const contentType = getHeaderValue("content-type");
if (contentType === "application/x-www-form-urlencoded") {
const data = Object.keys(request.data)
.sort((a, b) => {
if(a < b) { return -1; }
if(a > b) { return 1; }
return 0;
})
.map(key => key + "=" + request.data[key])
.join('&');
return resolveVariables(data);
} else if (Object.entries(request.data).length === 0 && request.data.constructor === Object) {
return "";
}
return resolveVariables(request.data.toString());
}
function calculateDigest() {
const requestData = resolveRequestBody();
console.log(`Request data: ${requestData}`);
const sha256digest = CryptoJS.SHA256(requestData);
const base64sha256 = CryptoJS.enc.Base64.stringify(sha256digest);
const calculatedDigest = 'sha-256=' + base64sha256;
console.log(`Digest header: ${calculatedDigest}`);
pm.environment.set("Digest", calculatedDigest);
return calculatedDigest;
}
{%raw%}// Signature Calculation{%endraw%}
const sdk = require("postman-collection");
const moment = require("moment")
const requestWithoutContentHeaders = "(request-target) x-nordea-originating-host x-nordea-originating-date";
const requestWithContentHeaders = "(request-target) x-nordea-originating-host x-nordea-originating-date content-type digest";
function getSignatureBaseOnRequest() {
const url = new sdk.Url(resolveVariables(request.url));
const host = url.getHost().toLowerCase();
const path = url.getPathWithQuery();
const method = request.method.toLowerCase();
const date = moment().utc().format("ddd, DD MMM YYYY HH:mm:ss") + " GMT";
let headers = requestWithoutContentHeaders;
let normalizedString =
`(request-target): ${method} ${path}\n` +
`x-nordea-originating-host: ${host}\n` +
`x-nordea-originating-date: ${date}`;
if (method === "post" || method === "put" || method === "patch") {
const contentType = getHeaderValue("content-type");
const digest = calculateDigest();
normalizedString += `\ncontent-type: ${contentType}\ndigest: ${digest}`
headers = requestWithContentHeaders;
}
return {host, path, method, date, headers, normalizedString};
}
function encryptSignature(normalizedSignatureString) {
const messageDigest = forge.md.sha256.create();
messageDigest.update(normalizedSignatureString, "utf8");
return forge.util.encode64(getPrivateKey().sign(messageDigest));
}
function getPrivateKey() {
let eidasPrivateKey = pm.environment.get("eidasPrivateKey");
if (!eidasPrivateKey.includes('PRIVATE KEY')) {
eidasPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + eidasPrivateKey + "\n" + "-----END RSA PRIVATE KEY-----";
}
console.log(eidasPrivateKey);
return forge.pki.privateKeyFromPem(eidasPrivateKey);
}
const clientId = getHeaderValue("x-ibm-client-id")
const signature = getSignatureBaseOnRequest();
const encryptedSignature = encryptSignature(signature.normalizedString);
const signatureHeader = `keyId="${clientId}",algorithm="rsa-sha256",headers="${signature.headers}",signature="${encryptedSignature}"`;
console.log(`Normalized signature string: ${signature.normalizedString}`);
console.log(`Signature header: ${signatureHeader}`);
pm.environment.set("Signature", signatureHeader);
pm.environment.set("X-Nordea-Originating-Host", signature.host);
pm.environment.set("X-Nordea-Originating-Date", signature.date);
Add “X-Nordea-Originating-Date” header with value X-Nordea-Originating-Date
Add “X-Nordea-Originating-Host” header with value X-Nordea-Originating-Host
Add “Signature” header with value Signature
In case of POST, PUT or PATCH method you have to also add “Content-Type” and “Digest” header with value Digest
You have to define also eidasPrivateKey
env variable. Sandbox private key for static eIDAS certicate is:
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
Testing eIDAS signature with Java
Sandbox uses a static certificate for all users: Sandbox Signature
Here is an example code which shows how to generate signatures for POST and GET methods with the usage of tomitribe-http-signatures library:
Maven:
<dependency>
<groupId>org.tomitribe</groupId>
<artifactId>tomitribe-http-signatures</artifactId>
<version>1.0</version>
</dependency>
Test signature generator:
package com.nordea.openbanking.sandbox.security;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.tomitribe.auth.signatures.Signature;
import org.tomitribe.auth.signatures.Signer;
import java.security.Key;
import java.security.KeyStore;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
@Component
public class TestSignatureGenerator {
private static final char[] KEY_STORE_PASSWORD = "1111".toCharArray();
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
@Value("classpath:sandbox-signature.p12")
private Resource sandboxKeyStore;
public String getPostMethodExampleSignature() throws Exception {
{%raw%}// Order is important{%endraw%}
Map<String, String> requestHeaders = new LinkedHashMap<>();
requestHeaders.put("(request-target)", null);
requestHeaders.put("X-Nordea-Originating-Host", "api.nordeaopenbanking.com");
requestHeaders.put("X-Nordea-Originating-Date", DATE_FORMAT.format(new Date()));
requestHeaders.put("Content-Type", "application/json");
requestHeaders.put("Digest", "SHA-256=mmbe/mYuzO+LGeL92Nvc4tFu+aSP/NFE8oJoBj8oLOI=");
return getTestSignatureHeader("POST", "/v4/create", requestHeaders);
}
public String getGetMethodExampleSignature() throws Exception {
{%raw%}// Order is important{%endraw%}
Map<String, String> requestHeaders = new LinkedHashMap<>();
requestHeaders.put("(request-target)", null);
requestHeaders.put("X-Nordea-Originating-Host", "api.nordeaopenbanking.com");
requestHeaders.put("X-Nordea-Originating-Date", DATE_FORMAT.format(new Date()));
return getTestSignatureHeader("GET", "/v4/get", requestHeaders);
}
private String getTestSignatureHeader(String method, String path, Map<String, String> headers) throws Exception {
Signature signature = new Signature(
"clientIdFromHeader",
"rsa-sha256",
null,
headers.keySet().toArray(new String[0])
);
Signer signer = new Signer(getTestKey(), signature);
return signer.sign(method, path, headers).toString();
}
private Key getTestKey() throws Exception {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(
sandboxKeyStore.getInputStream(),
KEY_STORE_PASSWORD
);
return keyStore.getKey("privatekey", KEY_STORE_PASSWORD);
}
}
The registration to the API Market
The registration to the API Market is described in Help Centre
Use of eIDAS qualified seal certificate for third party provider authentication in a production environment
After having a license granted by a National Competent Authority, the TPP must purchase an eIDAS QSealC certificate from one of the QTSPs listed in the EU Trusted List. This certificate will be used when enrolling in Nordea Open Banking production. After the successful enrollment, the TPP must use the same certificate in each API call.
Certificate validation and revocation check
When the client certificate is used, it will always be validated by Nordea’s API system. The validation contains normal trust chain validations including expiration check and verification towards revocation on top of the actual signature validation. If any of the validations are not passing the connection will be rejected. Please note that the owner of the certificate must renew the certificate before its expiration. In the case of the new certificate, the enrollment step must be re-initiated.
The role of the TPP mentioned in the certificate is verified and the authorisation must match with the API request in question.
Terminology
Word | Meaning |
---|---|
ASPSP | Account Servicing Payment Services Provider. An entity authorised to operate customer accounts, with a line of credit and payment facilities online. |
API | Application Programming Interface. A set of definitions, protocols, and tools that can be used to create applications, interact with other applications, and exchange data. |
Certificate | An electronic ‘passport’ used to certify the identity of a person, machine, or organisation over the Internet. |
Electronic Seal | An electronic ‘signature’ used by a legal entity to certify electronic documents as genuine. |
EBA | European Banking Authority. The body is responsible for publishing the Regulatory Technical Standards (RTS), Implementing Technical Standards (ITS), and a central register for PSD2. |
MTLS | Mutual TLS connection |
PSD2 | Payment Service Directive. EU Directive, administered by the European Commission (Directorate General Internal Market) to regulate payment services and payment service providers throughout the European Union (EU) and European Economic Area (EEA). The Directive’s purpose was to increase pan-European competition and participation in the payments industry also from non-banks, and to provide for a level playing field by harmonising consumer protection and the rights and obligations for payment providers and users |
PSP | Payment Service Provider. An entity authorised to provide payment services to customers. PSPs include ASPSPs and TPPs. |
QTSP | Qualified Trust Service Provider. An entity permitted by Member State Supervisory Body to issue Qualified Digital Certificates that are recognized across the EU. |
QSealC | Qualified certificates for electronic seals. Qualified electronic seals can be considered as a digital equivalent to seals of legal entities on paper. According to the eIDAS regulation, a qualified electronic seal must be created by a qualified electronic device and based on a qualified certificate for electronic seal. |
QWAC | A qualified website authentication certificate (QWAC certificate) is a qualified digital certificate under the trust services defined in the eIDAS Regulation. |
TPP | Third Party Provider. An entity authorised to access accounts on behalf of customers but that does not operate those accounts themselves. TPPs include PISPs and AISPs. |
RTS | Regulatory Technical Standards. Commission Delegated Regulation (EU) 2018/389 of 27 November 2017 supplementing Directive (EU) 2015/2366 of the European Parliament and the Council with regard to regulatory technical standards for strong customer authentication and common and secure open standards of communication |
TLS | Transport Layer Security. The TLS protocol aims primarily to provide privacy and data integrity between two or more communicating computer applications |
TSP | Trust Service Provider. An entity that provides digital services that enable the issuance and proving mechanisms to secure and protect information online. Examples include Certificates and Electronic Seals. |