Developer documentation
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.
API overview
The currently supported versions of Refund API(RAPI) are:
API | Sandbox | Production | Note |
---|---|---|---|
Refund API (RAPI) | API Version 1 | 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.
Refund API (RAPI)
Refund API provides capability to fully automate payment refund process. The current status of any payment initiation is always available, whether payment has been successfully initiated or where payment is already executed.
Terminology
To avoid confusion this section provides explanations of the terminology used in the documentation.
Term | Description |
---|---|
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. |
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 organisation 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. |
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. |
TLS | Transport Layer Security. The TLS protocol aims primarily to provide privacy and data integrity between two or more communicating computer applications. |
API responses and response codes
The APIs consume JSON objects and return 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.
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
Refund API response format is following:
{
"group_header": {
"message_identification": "7ddc011e-6f4a-4e",
"creation_date_time": "2021-04-04T07:51:48.205Z",
"http_code": 200
},
"refund_payment": {
...
},
"original_payment": {
...
}
}
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 refund_payment
and original_payment
objects are containing the actual payload of the response.
API response codes
API response HTTP codes are returned within the JSON object. These codes can be divided into four categories.
- 2xx Success
- 4xx Client errors
- 5xx Server error
The return codes used in the APIs are covered 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.
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.
When making requests the Client ID and Client Secret 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 and X-IBM-Client-Secret parameters are only used by the client application.
Client applications
A client application can be any corporate application capable of sending payments. When developing an application, you should also check the Security considerations 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.
The registration process on Developer Portal is described in the Registration Guide.
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
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 Client ID, Client Secret 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 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.
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 Refund API (RAPI).
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 payment object. |
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. |
API HTTP methods
RESTful APIs like this one use HTTP methods when performing actions to fetch, modify, add or delete resources.
- 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 RAPI API.
API | GET | POST | PUT | DELETE |
---|---|---|---|---|
Refund API (RAPI) | X | X |
HTTP header fields
Each request requires a set of mandatory HTTP fields as in table 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. |
Technical-user-id | Technical user id who has been given permissions to initiate refund payments from given account. |
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.
Client secret is lost
If you lose the client secret, you must generate a new one in the developer portal.
Application ID is not registered
If you receive this error when making a request, please double-check in the developer portal that you are subscribed to the APIs you wish to use.
API returns 500 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.
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: [Refund API Collection] https://developer.nordeaopenbanking.com/resources#downloads and import it 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 developer portal.
To be able to use this collection in your Postman, you need to add Postman environment variable “eidasPrivateKey” with the value of the private key, which is listed in the section Creating signature header with Postman.
Next, make sure that you disable Automatically follow redirects from the Postman settings. For instructions how to do this, see this.
In order to create a working refund request in Sandbox environment, get test data (payment archive id and technical user id) from the Sample Test Data section.
After these steps, you can run the collection.
Signing your requests
All the API versions have a mandatory Signature header. Consumers are required to use certificate when creating signature and making API requests. For Sandbox environment Nordea provides samples certificate which is to be used.
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 POST request:
- (request-target)
- x-nordea-originating-host
- x-nordea-originating-date
- content-type
- digest
How do I sign my API requests?
A PKI Certificate must be used by each client/consumer to digitally sign all API requests. The signing process is completed using the ‘Draft Cavage HTTP Signature method’ as defined by ietf.org in https://tools.ietf.org/html/draft-cavage http-signatures-10.
- 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 developer portal. - The
(request-target)
is a combination of the HTTP action verb and the request URI path. For example, (request-target): post /corporate/payment-refunds/v1/refunds
Signature header requirements are different for GET and POST 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 |
Examples of GET and POST requests with requisite Signature HTTP header are shown below.
For this GET request example consider the following request The signature headers required for the GET request are: “(request-target) x-nordea-originating-host x-nordea-originating-date
”
GET: https://open.nordea.com/corporate/payment-refunds/v1/refunds/{id}
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 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/payment-refunds/v1/refunds\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 request The signature headers required for the POST request are: “(request-target) x-nordea-originating-host x-nordea-originating-date content-type digest”:
POST: https://open.nordea.com/corporate/payment-refunds/v1/refunds
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 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): post /corporate/payment-refunds/v1/refunds\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 should be created like this: signature="Base64(RSA-SHA256(signing string))"
The following is some sample JAVA code to assist developers in creating the normalized signing string:
{%raw%}
import java.util.LinkedHashMap;
import java.util.Map;
public class SignatureNormalizedStringBuilder {
/**
* Produces signature normalized string:
* {@code
* "(request-target): post /corporate/payment-refunds/v1/refunds\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/payment-refunds/v1/refunds");
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;
}
} {%endraw%}
How to test in developer portal and sandbox with a PKI certificate
For testing of the API with signed requests the user of Nordea Sandbox does not need to acquire a real test certificate. A test/sample certificate can be downloaded from Nordea Developer portal and it can be used to set up and test API requests. The purposes of this setup is to make testing our APIs a simple and quick process.
Before downloading the test/sample certificate from Developer Portal, 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 developer portal and only within the EU/EEA area.
Note, that testing with any other certificate than the sample certificate, which is noted on this page below as eidasPrivateKey, will always lead to bad request.
Also note, that you can set the Signature header to: SKIP_SIGNATURE_VALIDATION_FOR_SANDBOX in case you want to test without a signature.
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):
{%raw%}
// Get Forge library
// Download and compile forge lib from https://github.com/digitalbazaar/forge and put it to service managed by you
// Or just copy whole script to Postman Pre-Script section: https://raw.githubusercontent.com/loveiset/RSAForPostman/master/forge.js
// After the Forge library section, add the code below
// Signature generation section
// Common
function getHeaderValue(headerName) {
const headerValue = request.headers[headerName];
if (headerValue === undefined) {
throw new Error(`Required header: ${headerName} is not defined`);
}
//return resolveVariables(headerValue);
return headerValue;
}
function resolveVariables(textWithPossibleVariables) {
return textWithPossibleVariables.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());
return (request.data.toString());
}
function calculateDigest() {
const requestData = resolveRequestBody();
const sha256digest = CryptoJS.SHA256(requestData);
const base64sha256 = CryptoJS.enc.Base64.stringify(sha256digest);
const calculatedDigest = 'sha-256=' + base64sha256;
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(request.url);
const host = url.getHost().toLowerCase();
const path = url.getPathWithQuery().toLowerCase();
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,"utf-8");
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-----";
}
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}"`;
pm.environment.set("Signature", signatureHeader);
pm.environment.set("X-Nordea-Originating-Host", signature.host);
pm.environment.set("X-Nordea-Originating-Date", signature.date);
{%endraw%}
- Add “X-Nordea-Originating-Date” header with value
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 method you have to also add “Content-Type” header with value “application/json” and “Digest” header with value
Digest
Add Postman environment variable “eidasPrivateKey” with sample certificate as value:
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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:
In the Java Signature generation approach, it is not possible, to use the private key directly in the text form (as shown above). Use the .p12 file instead, which you could download from the portal, in the testing app section.
Maven:
<dependency>
<groupId>org.tomitribe</groupId>
<artifactId>tomitribe-http-signatures</artifactId>
<version>1.0</version>
</dependency>
Test signature generator:
{%raw%}
package com.nordea.openbanking.sandbox.security;
import lombok.SneakyThrows;
import lombok.var;
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.util.LinkedHashMap;
import java.util.Map;
@Component
public class TestSignatureGenerator {
private static final String DIGEST = "Digest";
private static final String CONTENT_TYPE = "Content-Type";
private static final String X_ORIGINATING_DATE = "X-Nordea-Originating-Date";
private static final String X_ORIGINATING_HOST = "X-Nordea-Originating-Host";
private static final String REQUEST_TARGET_INDICATOR = "(request-target)";
private static final String VALID_GET_HEADERS = "(request-target) x-nordea-originating-host x-nordea-originating-date";
private static final String VALID_INSERT_HEADERS = "(request-target) x-nordea-originating-host x-nordea-originating-date content-type digest";
private static final char[] KEY_STORE_PASSWORD = "1111".toCharArray();
//Set your .p12 key store file location here, after you will download it from the portal.
private static final String TEST_KEY_STORE_FILE_NAME_PATH = "certificate/rapi-sandbox-certificate.p12";
private static final String TEST_KEY_ALIAS = "1";
public static String calculateTestSignature(String method, String uri, Map<String, String> headers) {
return getTestSignature(method, uri, headers).getSignature();
}
@SneakyThrows
private static Signature getTestSignature(String method, String uri, Map<String, String> headers) {
var signature = new Signature(
"clientId",
"rsa-sha256",
null,
method.equalsIgnoreCase("GET") ? VALID_GET_HEADERS.split(" ") : VALID_INSERT_HEADERS.split(" ")
);
var signer = new Signer(getTestKey(), signature);
return signer.sign(method, uri, headers);
}
@SneakyThrows
private static Key getTestKey() {
var keystore = KeyStore.getInstance("PKCS12");
keystore.load(
SignatureTestUtil.class.getClassLoader().getResourceAsStream(TEST_KEY_STORE_FILE_NAME_PATH),
KEY_STORE_PASSWORD
);
return keystore.getKey(TEST_KEY_ALIAS, KEY_STORE_PASSWORD);
}
public static Map<String, String> getValidHeadersForGet(String host, String date) {
var requestHeaders = new LinkedHashMap<String, String>();
requestHeaders.put(REQUEST_TARGET_INDICATOR, null);
requestHeaders.put(X_ORIGINATING_HOST.toLowerCase(), host);
requestHeaders.put(X_ORIGINATING_DATE.toLowerCase(), date);
return requestHeaders;
}
public static Map<String, String> getValidHeadersForPost(String host, String date, String contentType, String digest) {
var requestHeaders = new LinkedHashMap<String, String>();
requestHeaders.put(REQUEST_TARGET_INDICATOR, null);
requestHeaders.put(X_ORIGINATING_HOST.toLowerCase(), host);
requestHeaders.put(X_ORIGINATING_DATE.toLowerCase(), date);
requestHeaders.put(CONTENT_TYPE.toLowerCase(), contentType);
requestHeaders.put(DIGEST.toLowerCase(), digest);
return requestHeaders;
}
//Note: In this file is used the lombok plugin.
//https://mvnrepository.com/artifact/org.projectlombok/lombok-maven-plugin
//With the following methods, you can generate the POST/GET request signatures.
/*public static void main(String[] args) {
var algorithm = "algorithm=\"rsa-sha256\"";
var keyId = "keyId=\"73f713c8-2a4e-4b40-aded-7442870b68d8\"";
//Generating of the Signature for Insert data requests (POST,PUT,PATCH).
var postHeaders = "headers=\"(request-target) x-nordea-originating-host x-nordea-originating-date content-type digest\"";
var requestPostHeaders = getValidHeadersForPost("api.nordeaopenbanking.com", "Wed, 24 Apr 2019 14:00:37 GMT","application/json","SHA-256=mmbe/mYuzO+LGeL92Nvc4tFu+aSP/NFE8oJoBj8oLOI=");
var validPostSignature = calculateTestSignature("POST", "/corporate/payment-refunds/v1/refunds", requestPostHeaders);
var signaturePostHeaderReady = keyId.concat(",").concat(algorithm).concat(",").concat(postHeaders).concat(",").concat("signature=").concat("\"").concat(validPostSignature).concat("\"");
System.out.println(signaturePostHeaderReady);
//Generating of the Signature for Non-insert data requests (GET).
var getHeaders = "headers=\"(request-target) x-nordea-originating-host x-nordea-originating-date\"";
var requestGetHeaders = getValidHeadersForGet("api.nordeaopenbanking.com", "Wed, 24 Apr 2019 14:00:37 GMT");
//Note: The id, at the end of the corporate/payment... URL, should match the id, in the testing URL in the Postman/other testing tool).
var validGetSignature = calculateTestSignature("GET", "/corporate/payment-refunds/v1/refunds/8840410a-297b-4058-a2d0-8d761d6709", requestGetHeaders);
var signatureGetHeaderReady = keyId.concat(",").concat(algorithm).concat(",").concat(getHeaders).concat(",").concat("signature=").concat("\"").concat(validGetSignature).concat("\"");
System.out.println(signatureGetHeaderReady);
}*/
} {%endraw%}