Developer documentation
The purpose of this documentation is to give an overview of all the APIs which are included in Nordea’s Corporate API products - both existing production release and upcoming versions. There is separate documentation for each API which includes the full API reference of the particular API.
The Sandbox offers a safe and simple way to try out Nordea’s APIs. Developers can familiarize themselves with existing and upcoming functionality. Moreover, it allows the developers to experiment and build applications which use the Nordea API before release. See Sandbox for more information regarding the Sandbox and what it is.
API overview
The currently supported versions of Corporate APIs are:
API | Version |
---|---|
Corporate Access Authorization API | API Version 2 |
Corporate Accounts API | API Version 3 |
Corporate Payments API | API Version 2 |
Corporate Payments Single SCA API | API Version 2 |
Instant Reporting API | API Version 4 |
Corporate Payout API | API Version 2 |
Payments Track & Trace API | API Version 1 |
Multi-Payout Instant API | API Version 1 |
The Nordea API implementation is Representational State Transfer Service RESTful, and the responses produced by the Nordea API are in JavaScript Object Notation, JSON. The API also consumes JSON so requests sent to it should have the request body in JSON format.
An exception to the rule described above is Multi-Payout Instant, where instead of JSON body content we need to handle and maintain XML body content. This is based on MIG (Message Implementation Guidelines) specifications related to the pain.001.001.09 and pain.002.001.10 message structures. But the communication is in RESTful, just like other APIs.
The links to download the Swagger files can be found here
Corporate Access Authorization API
The Corporate Access Authorization API is utilized to acquire the customer’s authorization to access their online accounts and services. Such authorization must be obtained before using other API endpoints.
Before using accounts or payments endpoints you MUST perform an authorization flow and generate an access token. This process is described in the Corporate Access Authorization API documentation, see the quick start guide and the request examples
Please note, that access token does not apply for Multi-Payout Instant.
Corporate Accounts API
The Corporate Accounts API provides Account Information Services (AIS), that is, it provides information pertaining to the corporate customer’s bank account(s). The information can be categorized into following categories:
- list accounts
- filter accounts
- account details
- list transactions.
Corporate Payments API
Corporate Payments API provides Payment Initiation Services(PIS), that is, it enables the creation and authorization of payment instructions transferring funds from Nordea corporate customer accounts. Instructions can target credit transfers in a number of payment schemes. Support for additional payment types and payment schemes is being extended.
Corporate Payments Single SCA API
Corporate Payments Single SCA API provides Payment Initiation Services(PIS), that is, it enables the creation and authorization of payment instructions, with single strong customer authentication (SCA), transferring funds from Nordea corporate customer accounts. Instructions can target credit transfers in a number of payment schemes. Support for additional payment types and payment schemes is being extended.
Corporate Payout API
Corporate Payout API provides Payment Initiation Services(PIS) for premium customers, that is, it enables the creation and authorization of payment instructions transferring funds from Nordea corporate customer accounts. Instructions can target credit transfers in a number of payment schemes. Support for additional payment types and payment schemes is being extended.
Instant Reporting API
The Instant Reporting API provides Account Information Services (AIS), that is, it provides information pertaining to the corporate customer’s bank account(s), for which it provides extended data. The information can be categorized into following categories:
- list accounts
- filter accounts
- account details
- list transactions.
Payments Track & Trace API
Payments Track & Trace API provides information about the status of incoming and outgoing cross-border payments processed via the SWIFT network. Service can provide information about the status of single transaction based on the provided UETR identifier or list of transactions initiated over indicated period of time, for specific account.
Multi-Payout Instant API
Multi-Payout Instant is a fully automated payment solution for corporate customers with the need to send and process multiple SEPA instant transactions. This service reduces manual workload and speeds up processes by enabling you to execute pre-confirmed multiple payments in one API request, supported by real-time status responses. Multi-Payout Instant improves the process and provides full transparency and automation with a real-time experience.
Signing your requests
All the latest API versions have a mandatory signature header. Consumers are required to use certificates (eIDAS or self signed) when making API requests after September 2019. We have deprecated all earlier API versions and removed these from the Sandbox and production offerings.
The following signature headers are required for all GET requests:
- (request-target)
- x-nordea-originating-host
- x-nordea-originating-date
and the following signature headers for all POST or PUT request:
- (request-target)
- x-nordea-originating-host
- x-nordea-originating-date
- content-type
- digest
Terminology
To avoid confusion, we, in this section, go through some explanations of the terminology used in the documentation.
Term | Description |
---|---|
Access Token | A token which is retrieved by the client after a successful access authorization flow. The access token is used by the API to authenticate clients. The access token does not contain any information that the client (or client’s application) can use. |
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. |
API Call | API call is a request towards an API which produces 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 authorize the client. |
ASPSP | Account Servicing Payment Services Provider. An entity authorized to operate customer accounts, with a line of credit and payment facilities online. |
Authorisation | Authorisation is the process through which the client obtains permission to access the resource owner’s data and services at the bank. |
Authorisation Code | An authorization code is a code provided to the client during the access authorization flow; this code is short-lived and exchanged for an access token. |
Authoriser | Authoriser - authorizers are strictly required for both account access and payment initiation requests. The minimum number of authorizers is 1 although the customer may demand that two or more authorizers act together, on the resource owner’s behalf, to confirm any request. |
Authoriser Authentication | Authoriser authentication: the process which provides the correct identity of an authorizer acting to confirm the access request or payment confirmation on behalf of the resource owner. |
Availability level | Availability indicates in which part of the API support lifecycle the given operation, parameter, model or property is. |
Certificate | An electronic ‘passport’ used to certify the identity of a person, machine, or organization over the Internet. |
Client | The client refers to the consumer of the API. This is commonly an application provided by the corporate customer or a regulated third-party provider. |
Client Authentication | Client authentication: the process which provides the correct identity of the client; a key component in enforcing that clients are only able to access the resources that they are allowed to. |
Consent | Consent is agreed between the resource owner (customer) and the client (TPP). It includes what data may be shared, what services may be performed on the resource owner’s behalf, the duration of the agreement and to what purpose the data/services are provided. Consent agreed between resource owner and client, informs the access authorisation flow. |
Corporate Direct Access | Where a corporate customer has subscribed to a premium API product they may directly access their own data and/or services using their own API client application. In these circumstances it is not necessary for the client to utilise a registered third-party provider. |
EBA | European Banking Authority. The body responsible for publishing the Regulatory Technical Standards (RTS), Implementing Technical Standards (ITS), and a central register for PSD2. |
Electronic Seal | An electronic ‘signature’ used by a legal entity to certify electronic documents as genuine. |
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. |
QSealC | Qualified certificates for electronic seals. Qualified electronic seals can be considered as 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. |
QTSP | Qualified Trust Service Provider. An entity permitted by Member State Supervisory Body to issue Qualified Digital Certificates that are recognised across the EU. |
QWAC | A qualified website authentication certificate (QWAC certificate) is a qualified digital certificate under the trust services defined in the eIDAS Regulation. |
Refresh Token | A token which is retrieved by the client after a successful access authorisation flow. The refresh token is passed by the client in a token exchange call to obtain a fresh access token once the previous access token has expired. |
Resource owner | The resource owner refers to the corporate customer who holds a banking agreement with Nordea and uses a client application to access their account information and services. |
RTS | Regulatory Technical Standards. Commission Delegated Regulation (EU) 2018/389 of 27 November 2017 supplementing Directive (EU) 2015/2366 of the European Parliament and of the Council with regard to regulatory technical standards for strong customer authentication and common and secure open standards of communication |
Sandbox | Sandbox, in the context of the corporate APIs, is a test environment facility where the data returned by the API consists of example data. Its purpose is to mimic the real / production version of the API. The Sandbox will always have available the latest published version of the API, this means that all new versions appear in Sandbox before they are introduced into the production. |
SCA | Strong Cutomer Authentication refers to authentication based on the use of two or more elements categorised as knowledge (something only the user knows), possession (something only the user possesses) and inherence (something the user is) that are independent, in that the breach of one does not compromise the reliability of the others, and is designed in such a way as to protect the confidentiality of the authentication data. |
TLS | Transport Layer Security. The TLS protocol aims primarily to provide privacy and data integrity between two or more communicating computer applications |
TPP | Third-Party Provider is a regulated provider of one or more applications which resource owners (customers) can use to access their accounts/services via the API. A TPP is one type of API client/consumer. TPPs include PISPs and AISPs. See also Corporate Direct Access. |
TSP | Trust Service Provider. An entity that provides digital services which enable the issuance and proving mechanisms to secure and protect information online. Examples include Certificates and Electronic Seals. |
UETR | Unique End-to-end Transaction Reference. Number which describes each of SWIFT transaction. |
SWIFT | Society for Worldwide Interbank Financial Telecommunication. Interbank Institution which allows banks to do money transfers using multicurrency. |
API HTTP methods
RESTful APIs like these one use HTTP methods when performing actions to fetch, modify, add or patch 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.
- PATCH - This method modifies an existing resource. It returns 200 on success.
Following table shows which methods are supported by the APIs.
API | GET | POST | PUT | PATCH |
---|---|---|---|---|
Corporate Access Authorisation | X | X | X | |
Corporate Accounts | X | X | ||
Corporate Payments | X | X | X | |
Corporate Payments Single SCA | X | X | X | |
Corporate Payout | X | X | X | |
Instant Reporting | X | X | ||
Payments Track & Trace API | X | |||
Multi-Payout Instant | X | X |
API responses and response codes
The APIs consume JSON objects and returns responses as JSON objects. Consult the API reference or the examples sections of each API page to see what the response objects look like. The responses are returned encoded in UTF-8 format.
An exception to the rule described above is Multi-Payout Instant, where instead of JSON content we need to handle and maintain XML content. This is based on MIG (Message Implementation Guidelines) specifications related to the pain.001.001.09 and pain.002.001.10 message structures. But the communication is in RESTful, just like other APIs.
The objects which the API returns are specified in the API reference and they contain some attributes that are common to all returned objects. For instance, every object has a return code 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 API response format is following.
{
"group_header": {
"message_identification": "yP6EzKRjarI",
"creation_date_time": "2019-04-04T07:51:48.205Z",
"http_code": 200
},
"response": {
...
}
}
Where the group_header
contains the http_code
, creation time of the response and message identifier. The API generates a unique message_identification
for each response.
The response
object contains the actual payload of the response, the structure of which is dependent upon the endpoint in question.
API response codes
API response HTTP codes are returned 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 the APIs are covered here or in the API reference.
Connecting to the 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.
- Access token is required.
The client ID and client secret are generated after the application is created on 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 must be retrieved by using the Corporate Access Authorisation API endpoints, see the access authorisation flow documentation for how to do this.
When making requests, the client ID, client secret, and access token are sent in a header of each request.
See the examples section of any API to learn how these headers are sent.
Note that both, client ID and secret are always sent, in every single request that is made towards the API
If the client secret is lost or leaked, it can be regenerated by clicking the reset button on the apps page.
Note that the
X-IBM-Client-Id
andX-IBM-Client-Secret
parameters are only used by the client application. Also, the access token is made available for the client, the access token should be stored by the client application, and it should not be given to the end-user.
Please note that access token is not used for Multi-Payout Instant.
Client applications
A client application might be a website, mobile application, and so on. During the access authorisation flow, the authoriser(s) on behalf of the resource owner grants permission for the client application (consumer of the API) to use API resources. This permission is represented by the access token.
When developing an application, you should also check the security section below.
Migrating your application to the production environment
Whilst Nordea’s Sandbox environment is provided freely to all, there is restricted access to the production environment; to real customer account information and their payment services.
The Sandbox environment is accessed at the following url: https://api.nordeaopenbanking.com.
You will need separate client credentials and a different url to access the production environment.
To be able to use your application in the production environment with real data, the client application owner needs to raise a support ticket whereupon Nordea will provide more detailed information about the client onboarding process.
API Swagger definition
The OpenBanking 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 the corporate APIs can be downloaded from these links: Corporate Access Authorisation API
Corporate Accounts API
Corporate Payments API
Corporate Payments Single SCA API
Corporate Payout API
Instant Reporting API
Payments Track & Trace API
Multi-Payout Instant
In Nordea API there are some swagger 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 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 information when a 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.
Differences between production and Sandbox APIs
The production version of the API provides access to real customer data. That is, you will be able to get real account and transaction information. The Sandbox is the environment where the application developers can develop their applications before they are promoted(migrated) to production. The Sandbox API is a superset of the production API, and there will be 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 future. These beta features are included so the developers can already test them and give feedback regarding them.
Availability status in APIs
The availability status is meant to inform developers about the feature life-cycle. 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 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 future. The beta features might be even completely removed in future releases if deemed necessary. Note 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 upcoming change in the production API. |
Deprecated | The deprecated availability level is for features that will be removed in the future. The length of time deprecated features will remain supported has yet to be decided but developers will be informed well in advance of any features that will be deprecated. |
Draft | The draft availability level is for features that are still in design phase, they lack implementation. These are added to give a sneak peek for developers, to show what is envisaged for future versions of the APIs. |
Stable | The stable availability level signals that this feature is in production and should be used in production applications. No breaking changes will be made to stable features without deprecating them. |
Sandbox only | The “Sandbox only” is an availability level which indicates that a 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 Sandbox only availability level. |
Stable features are found in 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 in any environment, they can be reviewed only from the upcoming API features section.
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 feature, that is 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 feature 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/corporate/v2/some/api/endpoint
Then the version of the API and endpoint in question is version 2 (v2). When new versions are released, the version numbering will be incremented, that means, next version will have the following URL:
https://open.nordea.com/corporate/v3/some/api/endpoint
When version 3 is released, the old version 2 will still be available - although possibly deprecated, meaning version 2 will still work until removed. 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. This API is developed by using well-tested and widely adopted security schemes.
The cient ID, client secret, access token and signature are sent in the header of the request. All the communication between the API and 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 secrets falling into 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. The following table shows the return codes used by the corporate APIs.
HTTP Status Code | Text | Description of the code |
---|---|---|
200 | OK | Request was fulfilled. |
201 | OK | The request has been fulfilled and resulted in a new resource being created. Code 201 is returned after the successful creation of a new access authorisation or payment object. |
202 | OK, Created | Request has been received and a new resource has been created. |
400 | Bad Request | The server cannot or will not process the request due to an apparent client error (e.g., malformed request syntax, size too large, invalid request message framing, or deceptive request routing). |
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. |
405 | Method not allowed | A request method is not supported for the requested resource. |
409 | Conflict | Indicates that the request could not be processed because of conflict in the current state of the resource. |
415 | Unsupported Media Type | The request entity has a media type which the server or resource does not support. |
422 | Unprocessable Entity | The request was well-formed but was unable to be followed due to semantic errors. |
500 | Internal Server Error | A generic error message, given when an unexpected condition was encountered and no more specific message is suitable. |
503 | Service Unavailable | The server cannot handle the request (because it is overloaded or down for maintenance). Generally, this is a temporary state. |
504 | Gateway Timeout | The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. |
HTTP header fields
Each request requires a set of mandatory HTTP fields, as tabulated below:
Field | Description |
---|---|
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 browser while standard ‘Date’ is forbidden |
X-Nordea-Originating-Host | HTTP header element for 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 browser while standard ‘Host’ is forbidden |
Digest | Digest header as defined in [RFC3230] contains a Hash of the message body (only required for POST and PUT requests) |
Signature | Application-level signature of the request, formed using client certificate’s private key |
X-IBM-Client-Id | Client ID provided by Nordea, identifying the client application |
X-IBM-Client-Secret | Client Secret provided by Nordea, forming authentication credentials of the client application |
Please note that more information on these mandatory headers is available in the downloadable swagger definition files and API Reference sections.
Common issues
In this section, we describe some of the common issues that the users of the API face. This section is updated periodically.
Access token and authorisation code, 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 resource owner. An access token must be provided in order to use the Instant Reporting, Corporate Accounts, Corporate Payments and Corporate Payout API endpoints.
Authorisation code is exchanged for an access token at the conclusion of the corporate access authorisation flow.
Client secret is lost
If you lose the client secret, you must generate a new one in the API Market Sandbox.
Problems with access authorisation flow
Please see the Corporate Access Authorisation Flow section in the Corporate Access Authorisation API documentation.
Application ID is not registered
If you receive this error when making a request, please double-check in the API Market Sandbox that you are subscribed to the APIs you wish to use.
API returns 400 and not a meaningful error message
In this case, most commonly, the request or request payload is invalid. Please check the request you are making carefully.
Postman collection is not working properly
First, please check that the client secret and client id is 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. See capturing http requests.
Getting started with the API using Postman
In this section, we take a look 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:
Corporate API Collection
Premium Corporate API Collection
Multi-Payout Instant Collection
Import the collection to the postman. Next, you have to create an environment which has X-IBM-Client-ID and X-IBM-Client-Secret variables, i.e., your application id and application secret. You can find the correct values for those from the app console in API Market.
Next, make sure that you disable Automatically follow redirects from the Postman settings. For instructions how to do this, see this.
After these steps, you can run the collection.
Client application authentication with a PKI certificate
Adoption 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 third-party access to payment accounts and payment initiation services. RTS (article 34) specifies that for the purpose of identification, payment service providers shall rely on QSealC or QWAC certificates.
Nordea is using QSealC type eIDAS certificate for third-party client authentication as defined by RTS. In practice this obliges TPPs to digitally sign API requests using an eIDAS certificate issued by one of the QTSPs operating in EU/EEA.
The 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 Service Directive 2 - PSD2
- Regulatory Technical Standards - RTS
- Qualified Certificate Profiles by ETSI
Certificate types
As outlined above TPP clients must sign all API requests using an eIDAS QSealC type certificate.
API consumers that are not registered TPPs, that is corporate clients accessing their own account data, can utilise any PKI Certificate to sign their API requests - not necessarily an eIDAS type - just so long as the certificate used in the one they registered when the client application was on-boarded.
To use the Multi-Payout Instant API the dedicated Nordea PKI X509 SignerID certificate is needed to be used to sign the API requests for secure communication, and for access authorisation to the API product.
Consumer client Type | Certificate Type Required | Certificate Authority |
---|---|---|
Registered TPP | eIDAS QSealC | QTSP |
Corporate client | any PKI Certificate | any (self-signed certificate allowable) |
Multi-Payout Instant Corporate client | PKI X509 SignerID certificate | Issued and managed by Nordea |
Issuers of 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:
Issuer of Nordea PKI X509 SignerID Certificate
Nordea corporate customers who haves a Corporate Cash Management Aagreement with Corporate Access File transfer schedule need to use their existing Nordea PKI X509 SignerID certificate in order to use the Premium API product “Multi-Payout Instant. The public certificate key must be provided in text format in the Premium API on-board. A Nordea PKI509 SignerID certificate can also be used for other Premium API’s that require PKI509 certificate. New customers without an issued Nordea PKI X509 SignerID certificate, need to sign up for Corporate Access File Transfer service, before taking the Premium API product Multi-Payout Instant in use.
Issuers of other certificates
Nordea corporate customers who want to use our premium products to access their own data/services without an eIDAS certificate should generate a self-signed certificate. The certificate must be provided during client application on-boarding.
You can use tools, like OpenSSL, to generate your own certificate:
openssl req -new -x509 -sha256 -newkey rsa:2048 -nodes -keyout example-com.key.pem -days 365 -out example-com.cert.pem
Certificate validation and revocation checks
When your client certificate is used, it will always be validated by Nordea. 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. Whenever a new certificate is utilised, the client onboarding step must be re-initiated.
Where an eIDAS certificates is used by a registered TPP, the role of the TPP as recorded in the certificate is verified and this authorisation must match with the requirements of the API request in question.
How do I sign my API requests?
A PKI Certificate (eIDAS QSealC or otherwise) must be used by each client/consumer to digitally sign all API requests. The signing process is defined by ietf.org in HTTP Message Signatures.
- The
signature
is sent in the Signature HTTP header as described in the RFC. - The
keyId
is the client Id of your application, as recorded when you initially registered the client application on the API Market. - The
(request-target)
is a combination of the HTTP action verb and the request URI path. For example,(request-target): get /corporate/<version>/accounts
Signature header requirements are different for GET, POST and PUT requests as detailed below.
HTTP Method | Signature headers |
---|---|
GET | (request-target) x-nordea-originating-host x-nordea-originating-date |
POST | (request-target) x-nordea-originating-host x-nordea-originating-date content-type digest |
PUT | (request-target) x-nordea-originating-host x-nordea-originating-date content-type digest |
PATCH | (request-target) x-nordea-originating-host x-nordea-originating-date content-type digest |
Examples of GET and POST requests with requisite signature HTTP header are shown below.
For this GET request example consider the following ListAccounts request
The signature headers required for all GET requests are: "(request-target) x-nordea-originating-host x-nordea-originating-date"
GET: https://open.nordea.com/corporate/<version>/accounts/
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="(request-target) x-nordea-originating-host x-nordea-originating-date",
signature="<signature>"
Where
<client_id>
is the client Id assigned to the TPP’s client application<signature>
is the BASE64 encoded version of the RSA-SHA256 encrypted signing string
The client would compose the signing string as:
(request-target): get /corporate/<version>/accounts\n
x-nordea-originating-host: open.nordea.com\n
x-nordea-originating-date: Thu, 05 Jun 2019 21:31:40 GMT
Note that the ‘\n’ symbols above are included to demonstrate where the new line character should be inserted. There is no new line on the final line of the signing string.
For this example POST request consider the following access request
The signature headers required for all POST or PUT requests are: “(request-target) x-nordea-originating-host x-nordea-originating-date content-type digest”:
POST: https://open.nordea.com/corporate/v2/authorize
X-Nordea-Originating-Host: open.nordea.com
X-Nordea-Originating-Date: Thu, 05 Jun 2019 21:31:40 GMT
content-type: application/json
digest: <digest>
Signature: keyId="<clientId>",algorithm="rsa-sha256",
headers="(request-target) x-nordea-originating-host x-nordea-originating-date content-type digest",
signature="<signature>"
Where
<client_id>
is the client ID assigned to the TPP’s client application<digest>
is the hash (SHA256) digest of the request body<signature>
is the BASE64 encoded version of the RSA-SHA256 encrypted signing string
The client would compose the signing string as:
(request-target): get /corporate/v2/authorize\n
x-nordea-originating-host: open.nordea.com\n
x-nordea-originating-date: Thu, 05 Jun 2019 21:31:40 GMT\n
content-type: application/json\n
digest: SHA-256=jcC/ttW7JucGTN9hWfqMsFeON6D+vZtQGWJA+W0PL/g=
Note that the ‘\n’ symbols above are included to demonstrate where the new line character should be inserted. There is no new line on the final line of the signing string. Note also that the value for the digest shown above serves only as an example and in reality needs to be computed based on the request message body
Example code: signature creation
The final
The following is some sample JAVA code to assist developers in creating the normalised signing string:
import java.util.LinkedHashMap;
import java.util.Map;
public class SignatureNormalizedStringBuilder {
/**
* Produces signature normalised string:
* {@code
* "(request-target): post /corporate/v2/authorize\n" +
* "x-nordea-originating-host: open.nordea.com\n" +
* "x-nordea-originating-date: Fri, 20 Sep 2019 09:41:25 GMT\n" +
* "content-type: application/json\n" +
* "digest: SHA-256=jcC/ttW7JucGTN9hWfqMsFeON6D+vZtQGWJA+W0PL/g="
* }
* MAKE SURE THAT THE LAST LINE DOESN'T HAVE '\n' CHAR
*/
public static void main(String[] args) {
Map<String, String> claims = new LinkedHashMap<>();
// key should be lower case
claims.put("(request-target)", "post /corporate/v2/authorize");
claims.put("x-nordea-originating-host", "open.nordea.com");
claims.put("x-nordea-originating-date", "Fri, 20 Sep 2019 09:41:25 GMT");
claims.put("content-type", "application/json");
claims.put("digest", "SHA-256=jcC/ttW7JucGTN9hWfqMsFeON6D+vZtQGWJA+W0PL/g=");
SignatureNormalizedStringBuilder builder = new SignatureNormalizedStringBuilder();
claims.forEach(builder::append);
System.out.println("Normalized String: " + builder.normalize());
}
private final StringBuilder builder = new StringBuilder();
SignatureNormalizedStringBuilder append(String key, String value) {
if (builder.length() > 0) {
builder.append('\n');
}
builder.append(key).append(": ").append(value);
return this;
}
String normalize() {
String normalizedSignature = builder.toString();
return normalizedSignature;
}
}
How to test in Sandbox with a PKI certificate
Note, that Nordea’s Sandbox environment requires all clients signing requests to use the same supplied test PKI certificate. All details can be found below.
For testing TPP authentication with signed requests, the user of Nordea Sandbox does not need to acquire a real eIDAS QSealC test certificate. A registered TPP can download a test/sample certificate from Nordea API Market and use it to set up and test API requests. The purposes of this setup is to make testing our APIs a simple and quick process, but this sample QSealC test certificate obviously is not used for client identification in Sandbox and when using this certificate the keyId should be set to a fixed text of “clientId”. The sample certificate contains all roles (AIS, PIS, etc.)
Before downloading the test/sample 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 Nordea API Market and only within the EU/EEA area.
Note, that testing with any other certificate than the sample QSealC which is downloadable from the API Market, will always lead to bad request. You can set signature header to: SKIP_SIGNATURE_VALIDATION_FOR_SANDBOX in case you want to test without a signature.
The downloaded sample 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.
Creating signature header 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):
// 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
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"));
// Common
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 (/{{(\w*)}}/,g, (str, key) => {
const value = environment[key];
return value === null ? "" : value;
});
}
// Digest Calculation
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;
}
// Signature Calculation
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 or PUT method you have to also add “Content-Type” and “Digest” header with value Digest
You have to also define eidasPrivateKey
env variable. Sandbox private key for the sample QSealC certificate is:
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
Creating signature header with Java
Here is example code which shows how to generate signatures for POST and GET methods with 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 {
// Order is important
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 {
// Order is important
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);
}
}