Version:  1.0.3
State:  Published
Environment: Sandbox
Base URI:

 Introduction to LUXHUB One implementation


It is important to understand how LUXHUB One API works before reviewing account information or payment initiation implementation details.

Below you can find more information on various aspects:

  • Exposed resources
  • End user auhtorisation
  • Security considerations
  • Considerations related to eIDAS Certificates
  • API access
  • Message signing
  • Digest calculation


Exposed resources

There are number of different resources that are involved in the LUXHUB One API:

Resource Description
Consumer Third-party provider application using this API (your application)
User Owner of a financial account
Provider Financial institution providing specific access to User’s data
Permission Permission given by the User to a specific Consumer / Provider to access data and/or create payments on their behalf
Account User’s financial account with a Provider
Balance Amount of money in the account
Transaction Banking transaction
Payment Banking transaction involving transfer of funds from a User’s account


The resources are linked to each other, as described in the below diagram:

Each LUXHUB One API consumer has a set of users. Each of these users can create permissions for the different providers in order to access their accounts information. As well each user can initiate payments in the different providers.


End user authorisation

Users of a consumer's application will need to log in with their financial institutions - the provider - on order to grant explicit consent for your application to access their banking data. In most cases, this will involve redirecting users to their financial institution's authorisation screen. The user is redirected back to the consumer application via the URL that you have configured for your application when registering for One API - the consumer token (permission-id) will be used in a header for all the subsequent API calls.

Moreover, each consumer that is accessing the API will have to set specific HTTP headers in their requests, in order to access resources of a certain user held by a certain provider that are pertinent in identifying the provider and the user in question. Similarly, the request will also need to contain the permission request identifier, so the system will be able to identify that the user has indeed granted consent authorization via SCA.

For long-lived consent support - using the OAuth2 refresh token grant, an attempt will be made to refresh the associated access token when requesting financial data with an expired access token.


Security considerations

Consumer connection to the LUXHUB One API is secured via MTLS authentication at protocol layer, while the consumer is identified via credentials access token. Message integrity is insured via http-signature. On the providers' side, LUXHUB One connects via PSD2 APIs, so the regulatory compliance requirements of PSD2 must be followed, i.e. using eIDAS certificates for identification (QWAC) and message integrity (http-signing with QSEALC certificate), along with applicative authorization via OAuth2 protocol. User access authorization to its resources - accounts, balances, transactions history - is given using SCA, as part of OAuth2 Authorization Code Grant flow. The SCA procedure implemented by each provider needs to comply with the regulatory items published by the EBA for PSD2 compliance.


Considerations related to eIDAS Certificates

The eIDAS certificates referred to in this guide are X509 certificates with dedicated protection profiles that enables them to be used in the PSD2 context and are issued by Qualified Trusted Service Providers. To achieve PSD2 security requirements, banks and PSD2 service providers will use Qualified Certificates for Websites and Qualified Certificates for Electronic Seals. Those certificates will be issued by Qualified Trust Service Providers (QTSPs) based on the technical standard, ETSI TS 119 495, which was published in May 2018. Qualified Certificates enable identification and verification of the payment institution by a third party. Identification will be based on the legal name of an organization, registration number and its main role(s) in the payments space.

There are two types of such certificates:

·         QWAC (Qualified Website Authentication Certificate): used as Client Certificates in MA-TLS

·         QSeal (Qualified Certificate for Seals): used to sign requests using http-signature

PSD2 APIs might require usage of both types of certificates - QWAC to access the API and QSeal for http-signature, i.e. message signing. As such, it is required to provide a pair of QWAC/QSEALC certificates for onboarding and usage purposes.

LUXHUB will use the eIDAS certificates provided by each consumer to authenticate towards each respective provider onboarded.


API access

Access to the LUXHUB One API is controlled at application level, via Oauth2 client credentials grant type during the onboarding process. 

In order to request a Client Credentials access token, LUXHUB One consumers must inform the grant_type, client_id and scope in the payload. Please find below an example using cURL.

Curl –location –request POST ‘’ \
--cert ‘{location of your certificate}’\
--key ‘{location of your private key}’ \
--header ‘Content-Type: application/x-www-form-urlencoded’ \
--header ‘Authorization: Basic {client:secretbase 64 encoded}’ \
--data-raw ‘grant_type=client_credentials&client_id={client_id}&scope=OneAPI’

In response, you will receive a JSON object:

“access_token”: “the_access_token”,
“token_type”: “Bearer”,
“expires_in”: 3600,
“scope”: “OneAPI”

Once you get your access token, you must add it to every call made to LUXHUB One API through header “Authorization”. E.g.: Authorization: Bearer ACCESS_TOKEN.


Message signing

All the requests sent by API consumers to LUXHUB One must be signed at application level using the http-signature protocol as defined in RFC draft. In current version LUXHUB One supports the asymmetric algorithm rsa-sha256.

The certificate and key to sign messages can be downloaded from the application section in LUXHUB developer portal

The corresponding certificate will be present in header TPP-Signature-Certificate in all the requests made towards the API.

LUXHUB One implementation of the http-signature is using RFC2253 for formatting the distinguished name of the CA that issued the certificate used for signing - as part of keyId element of signature header.

For example, considering Java as implementation language, and referring X500Principal class documentation, the correct code for implementing the keyId parameter should be:

public static String getKeyIdFromCertificate(String certificate) {
  X509Certificate cert = loadCertificate(certificate); // please implement loadCertificate method that converts certificate string to X509Certificate object
  String issuer = cert.getIssuerX500Principal().getName(X500Principal.RFC2253);
  String serialNumberHexadecimal = cert.getSerialNumber().toString(16);

  return String.format("SN=%s,CA=%s", serialNumberHexadecimal, issuer);

LUXHUB One does not require any particular header for the signature, the only restriction is that the header must be present in the API specification for the endpoint being called. But as a recommendation, “x-request-id” and “date” headers should be used for signing messages. Note that only requests made by the consumer of the API must be signed and that responses sent back by LUXHUB One are not signed.

The string to be signed is calculated based on header names and its values separated by a colon “:”. Each header must be on a different line, separated by a “line separator” character.
For instance, consider the request:

curl –location –request GET ‘https://localhost/foo’ \
--header ‘LH-BIC-Provider: DEMOLULLXX1’ \
--header ‘X-Request-ID: 6d8bcdd1-f172-4582-bf78-ae51f4b361b9’ \
--header ‘Date: Mon, 22 Jun 2020 12:25:30 GMT’


The string to be signed is:

x-request-id: 6d8bcdd1-f172-4582-bf78-ae51f4b361b9\n
lh-bic-provider: DEMOLULLXX1\n
date: Mon, 22 Jun 2020 12:25:30 GMT

Note that

  • header names are lowercased,
  • line separator “\n” separates each header and the last header does not end with “\n”
  • the date must be in the format “EEE, dd MMM YYYY HH:mm:ss GMT” in GMT+0. and it must be in English, e.g.:  Mon, 22 Jun 2020 12:25:30 GMT

The produced signature header should look like:

keyId=”{calculated based on previous section}”,algorithm=”rsa-sha256”,headers=”x-request-id lh-bic-header date”,signature=”{the signed string}”

Note that “headers” declaration contains the list of headers present in the signature. The order must be the same as in the signature.

Below there is an example of a signature calculated using the “x-request-id” and the “date” headers. 

keyId="SN=0,CA=CN=Mock-eIDAS IntermediateCA,OU=PRD,O=LUXHUB SA,L=Strassen,ST=Luxembourg,C=LU",algorithm="rsa-sha256",headers="x-request-id date",signature="j+2WkoexL0y45uCCv3onOcrqfHgRgF2djnm/vHHqZro2IzFXgKwV2xqmrvYatFERfUySJt5tbYZ0L0/9gw7LSPPBZv27x5Vvcqu+ED52E2q5zWmivIuc42fiaMwfuyZis+mBPqEap6ZqFihbOlJVbhTxm67hcgppLAuBMvS1olBX3HIZOMhz4L1PXn3JJE2MRmxN1MJ5rMCyNPuMChr1gutiWO70UwC4wALTqOSnUfw83Zm+AhvaRkQ/AjhagpAI1PVTMNts0OtXeu5D38gZNsylWNg+jopPLuxxKbUDC1uYD1R0jME3/32PGw/5WPXhxBovK+IkNGXV5foLJEqg4A=="


Following code snippet (Java) is using tomitribe library for signing http requests:

import org.tomitribe.auth.signatures.Signature;
import org.tomitribe.auth.signatures.Signer;
public HttpSignature sign(Map<String, String> headers, String method, String uri, String payload, String keyId) {
    LOGGER.debug("Calculating signature of {} {}", method, uri);
    HttpSignature httpSignature = new HttpSignature(); 
    LOGGER.debug("Request headers: {}", headers);
    Map<String, String> headersToSign = headersToSign(headers, method, uri);
    LOGGER.debug("Headers to be signed: {}", headersToSign);   
    List<String> signatureHeaders = new ArrayList<String>();
    headersToSign.keySet().forEach(key -> signatureHeaders.add(key));
    Signature signature = new Signature(keyId, Constants.RSA_SHA256, null, signatureHeaders);
    PrivateKey privateKey = loadPrivateKey();
    Signer signer = new Signer(privateKey, signature);
    Signature signed = sign(method, uri, headersToSign, signer);
    setSignatureHeader(httpSignature, signed);
    LOGGER.debug("Calculated signature: {}", httpSignature);
    return httpSignature;   
protected void setSignatureHeader(HttpSignature httpSignature, Signature signed) {
    httpSignature.setSignature(signed.toString().replaceFirst("Signature ", ""));
private Signature sign(String method, String uri, Map<String, String> headersMap, Signer signer) {
    try {
      LOGGER.debug("Performing signature for method {}, uri {} and headers {}", method, uri, headersMap);
      return signer.sign(method, uri, headersMap);
    } catch (Exception e) {
      LOGGER.error("Error when siging the request.", e);
      throw new HttpSignatureException("Error when signing the request.", e);
protected Map<String, String> headersToSign(Map<String, String> headers, String method, String uri) {
    Map<String, String> headersToSign = new HashMap<String, String>();
    List<String> allowedHeaders = getAllowedHeadersToSign();
    LOGGER.debug("Allowed headers to sign: {}" , allowedHeaders);
    headers.entrySet().forEach(entry -> {
      if (!allowedHeaders.contains(entry.getKey())) {
      headersToSign.put(entry.getKey(), entry.getValue());
    return headersToSign;
protected abstract List<String> getAllowedHeadersToSign(); //returns the header names that are used in the signature. Ex: x-request-id date
protected abstract String getSignatureKey(); //returns the keyId as indicated at the beginning.
public class HttpSignature {
    private String digest;
    private String signature;
    private Map<String, String> extraHeaders = new HashMap<>();
    public String getDigest() {
      return digest;
    public void setDigest(String digest) {
      this.digest = digest;
    public String getSignature() {
      return signature;
    public void setSignature(String signature) {
      this.signature = signature;
    public Map<String, String> getExtraHeaders() {
      return extraHeaders;
    public void setExtraHeaders(Map<String, String> extraHeaders) {
      this.extraHeaders = extraHeaders;
    public String toString() {
      return ToStringBuilder.reflectionToString(this);

Want to learn more about http signature? Please check out the official documentation where you can find some code snippets:


Digest calculation

Digest header is used to verify the integrity of the message body that has been sent to the API. In current version LUXHUB One supports algorithm SHA-256 to calculate digest, that is base 64 encoded.

Following code snippet (Java) is using class to calculate Digest:

private static final String ALGORITHM = "SHA-256";

  public static String calculate(String payload) {
    try {
      byte[] digest = MessageDigest.getInstance(ALGORITHM).digest(payloadAsByteArray(payload));
      String calculatedDigest = new String(Base64.getEncoder().encode(digest));
      return String.format("%s=%s", ALGORITHM, calculatedDigest);
    } catch (NoSuchAlgorithmException e) {
      throw new IllegalArgumentException("Wrong algorithm", e);

  private static byte[] payloadAsByteArray(String payload) {
    if (payload == null) {
      return "".getBytes();
    return payload.getBytes(StandardCharsets.UTF_8);

The result must look like 



Now you are familiar with the LUXHUB One basics and you can learn about AIS and PIS flows in the next tabs.


This website uses cookies.  By continuing to use our website, you accept the use of these cookies.