The Payment Services Directive 2 (PSD2) regulates payment services and service providers throughout the European Economic Area (EEA). Thus, more non-banks are involved in payment transactions, consumer protection is further strengthened and the rights and obligations of payment providers and users are harmonised.

Following the details about our security implementation of the PSD2 requirements.

OAuth credentials

To be able to call our APIs, you must request an Application in the Profile private section of this portal. An Application is identified by the pair client_id:client_secret, which represents the OAuth credentials.

Certificate requirements

To ensure security requirements of confidentiality, authenticity and integrity, we implement:

  • Mutual TLS Authentication, at transport layer, to initialize a secure communication channel and ensure authenticity of both client and server;
  • HTTP Signature, at application layer, to ensure authenticity of the sender, and non-repudiation and integrity of the request content.

In order to provide those functionalities and meet PSD2 directive requirements, during enrollment process a new third party should submit two different Qualified certificates (eIDAS) as specified by technical standard ETSI TS 119 495:

  • a Qualified Website Authentication Certificate (QWAC), to be used at transport layer for client authentication purpose;
  • a Qualified Seal Certificate (QSealC), to be used at application layer for signing request content and headers

As defined by ETSI TS 119 495, we expect that:

  • certificates are signed by a Qualified Trust Service Provider CA;
  • certificates contain a PSD2 Authorization Number in organizationIdentifier attribute of the Subject Distinguished Name (syntax in ETSI TS 119 495) and Authorization Number must be present in National Competent Authority (NCA) registry;
  • certificates contain qcStatements extension with:
    • QcType (see also ETSI EN 319 412), determining the type of Qualified Certificate (QWAC or QSealC)
    • etsi-psd2-qcStatement, defining roles of Payment Service Provider (PSP), ncaId and ncaName of the National Competent Authority.

Mutual TLS Authentication

In order to initialize mutual TLS Authentication you should use your QWAC Certificate and the relative private key during the API Request. An example using CURL tool could be the following:

curl -X POST --cacert \
--cert tpp-qwac.crt --key tpp-qwac.key \ \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <access_token>' \
-H ... \
-d '{"my": "content", "request": "payload"}'

In the example, tpp-qwac.crt is the X.509 eIDAS QWAC Certificate, while tpp-qwac.key represents the private key.

HTTP Signature

Every request to API needs to be signed using QSealC certificate, uploaded during the Third Party PSD2 enrollment. Designing HTTP-Signature header, we followed the draft-cavage-http-signatures specification.

In every request the following headers are mandatory and allow us to verify your signature:

  • Digest: Sha256 hash of the request payload, base64 encoded. Absent only for GET request, otherwise it is mandatory; it's mandatory to set the prefix string "SHA-256=" (e.g.: SHA-256=gCiqHj8+/f+rnEh9PK29iltlovg6BCawVNLR08NlK4c=)
  • TPP-Request-ID: Universally Unique Identifier Number (UUID) generated by client (e.g.: cdf1e4bd-0fcc-4298-bd8b-38cf690a0c21)
  • Date: HTTP Full Date as defined by RFC 2616 (e.g.: Tue, 05 Mar 2019 17:58:03 GMT).
  • Signature: Composed by the following, four comma-separated parameters (case-sensitive):
    • keyId: this value is used to identify the client. In our implementation we decided to use application name. We will verify the signature using QSealC certificate associated to the Third Party who owns the application;`
    • algorithm: always "rsa-sha256";
    • headers: defines headers whose values are included in the signature. (request-target), tpp-request-id and date are mandatory; digest is absent only for GET request, otherwise it is mandatory as well.
    • signature: base64 encoding of the signature. See Signature calculation for details.

An example value for Signature Header is the following (we avoid including the whole signature parameter for better readability):

keyId="TEST_TPP_APP_01",algorithm="rsa-sha256",headers="(request-target) digest tpp-request-id date",signature="R7YTJZAt[...]60IW00rSmA="

Calculate Signature

Let's suppose that you are going to sign a POST request to We can start from the same CURL example request used for Mutual TLS Authentication:

curl -X POST --cacert \
--cert tpp-qwac.crt --key tpp-qwac.key \ \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <access_token>' \
-H ... \
-d '{"my": "content", "request": "payload"}' 

1. Calculate Digest Header

Digest is the SHA256 hash of the request payload. In our example, using Bash and OpenSSL commands:

$ echo -n '{"my": "content", "request": "payload"}' | openssl dgst -binary -sha256 | openssl base64

The digest header will be completed by "SHA-256=" prefix to the calculated value:

Digest: SHA-256=8XdhkUyj3ftifJIYZrvqRAcz+SK+p9UT4ZjvJXVqE60=

2. Calculate Date Header

Date can be calculated with bash command date, taking care to use GMT as timezone and en_US.UTF-8 as Locale settings:

$ echo $(LC_TIME=en_US.UTF-8 date -u "+%a, %d %b %Y %H:%M:%S GMT")
 Tue, 12 Mar 2019 08:49:49 GMT

The Date header will be the following:

Date: Tue, 12 Mar 2019 08:49:49 GMT

3. Calculate TPP-Request-ID Header

TPP-Request-ID header is a Universally Unique Identifier (UUID), and it should be sequential or randomly generated. Using bash commands:

$ echo $(uuidgen)

Thus, the TPP-Request-ID header will contains the following value:

TPP-Request-ID: 693d0d44-2693-43b3-bee0-bcb0e76cbdb4

4. Calculate Signature Header

The signature will be calculated on a signing string, that includes method, URL, date, digest and tpp-request-id header values. Header names, HTTP method and URI must be lower case and the headers should be expressed as name: value pair, separated each others by new line.

IMPORTANT: the URL should include also the querystring (if present) in the signing string used to calculate the signature

In our example, the signing string should be:

(request-target): post /private/test01
digest: SHA-256=8XdhkUyj3ftifJIYZrvqRAcz+SK+p9UT4ZjvJXVqE60=
tpp-request-id: 693d0d44-2693-43b3-bee0-bcb0e76cbdb4
date: Tue, 12 Mar 2019 08:49:49 GMT

Saving the signing string in a variable signString, you should calculate the signature in the following way:

$ printf %s "$signString" | openssl dgst -sha256 -binary -sign tpp-qsealc.key | openssl base64 -A

In order to compose the Signature Header, you should comma-separate keyId, algorithm, headers and signature parameters:

Signature: keyId="TEST_TPP_APP_01",algorithm="rsa-sha256",headers="(request-target) digest tpp-request-id date",signature="EYnCl/2jtoxRrzdB0biDwukaL1neDCC0viATwCl8qMYPazmAdIZri8Fk4XSXtZj0XmtGtKtGrWJ1D7CMI98x9wk2X/f5oguZk0Ul0zws4QJ5j8/pxFXZx0ixmS6nyfJu1ntfJnUHz2ac0afxguxd6EjOpfWAoN3UVcjW0PRm2BxNrDrm7DvfgxJ+xNQaMtJziepyGVMqj+rpvYd9DJI6vtSIpcF9T9il3dIMg/hIDYTa9+hg6jpNamhq0Bn8QZABEC2TWKZN53RnU1xtbO6wJcBBifYUdcfeJwwnF0LNyHnXCueVmkaMBkN9bjYSrwmoOkoFTa7H5Xs+WRtBH0mBEv/7ySIaaM/NSR3mNDPQffumfehuOOg2ZvPsZ5XqQl1+0jJdVVHywZzDAgXncENdRwRX19rw7cGut7cFUNbU2HhHUHbp0Zp9awxTIcLlBjuHClsqKt3WAvvqzfwgZ8vohKSCE2DAim8Ltd8LoDcKeMm+oxd5KmthhKbYQ1PFb0dYTxLfUx3JQpgn0sikuQ+M6+yrZ2bTuQsqMdx9st0yAutYCRynZtuT/ru6MQAh1d3M6LECs7/VTXLYSL/vpoxWJP+ajQu7yuFMmOrH4GvcZ1GrhphiP2zKc7+7OcAAR3dYtmThx9HH6FhmKKOVCHBkkDjteF+2nGv1gE1S8hEq1so="

Pay attention to _headers_ parameter value: it should contain the names of the headers in the same order they appear in the signing string.

5. Send the request

Now you can send the request with both Mutual TLS Authentication and signature of content. Here the CURL request for our example:

curl -X POST --cacert \
--cert tpp-qwac.crt --key tpp-qwac.key \ \
-H 'Date: Tue, 12 Mar 2019 08:49:49 GMT' \
-H 'Digest: SHA-256=8XdhkUyj3ftifJIYZrvqRAcz+SK+p9UT4ZjvJXVqE60=' \
-H 'TPP-Request-ID: 693d0d44-2693-43b3-bee0-bcb0e76cbdb4' \
-H 'Signature: keyId="TEST_TPP_APP_01",algorithm="rsa-sha256",headers="(request-target) digest tpp-request-id date",signature="EYnCl/2jtoxRrzdB0biDwukaL1neDCC0viATwCl8qMYPazmAdIZri8Fk4XSXtZj0XmtGtKtGrWJ1D7CMI98x9wk2X/f5oguZk0Ul0zws4QJ5j8/pxFXZx0ixmS6nyfJu1ntfJnUHz2ac0afxguxd6EjOpfWAoN3UVcjW0PRm2BxNrDrm7DvfgxJ+xNQaMtJziepyGVMqj+rpvYd9DJI6vtSIpcF9T9il3dIMg/hIDYTa9+hg6jpNamhq0Bn8QZABEC2TWKZN53RnU1xtbO6wJcBBifYUdcfeJwwnF0LNyHnXCueVmkaMBkN9bjYSrwmoOkoFTa7H5Xs+WRtBH0mBEv/7ySIaaM/NSR3mNDPQffumfehuOOg2ZvPsZ5XqQl1+0jJdVVHywZzDAgXncENdRwRX19rw7cGut7cFUNbU2HhHUHbp0Zp9awxTIcLlBjuHClsqKt3WAvvqzfwgZ8vohKSCE2DAim8Ltd8LoDcKeMm+oxd5KmthhKbYQ1PFb0dYTxLfUx3JQpgn0sikuQ+M6+yrZ2bTuQsqMdx9st0yAutYCRynZtuT/ru6MQAh1d3M6LECs7/VTXLYSL/vpoxWJP+ajQu7yuFMmOrH4GvcZ1GrhphiP2zKc7+7OcAAR3dYtmThx9HH6FhmKKOVCHBkkDjteF+2nGv1gE1S8hEq1so="' \
-H 'Authorization: Bearer <access_token>' \
-H ... \
-d '{"my": "content", "request": "payload"}' \

If the API call is successful, you will receive the correct response and HTTP status code 200.

If signature verification fails, you will receive an HTTP Response with status code 412 and the following JSON payload:

    "result": {
                "message":"Signature verification failed",
    "requestId": "426418442887269164852009"

For GET requests, the Digest header is not required since there is no payload by design. For other HTTP Methods Digest header is mandatory.

CheBanca! Signature Verification

As we verify your request signature, you can verify our signature calculated on the response as well.

In fact, our response contains some headers that should be used for that purpose:

  • CB-Certificate: contains the certificate we use to sign the request;
  • Digest: contains the digest of the response payload (see Digest calculation);
  • CB-Response-ID: It represents the counterpart of TPP-Request-ID and it's a randomly generated Universally Unique Identifier (UUID)
  • Date: It represents the date in HTTP Full Date format;
  • Signature: It contains a signature header calculated as described in Signature Header Calculation

Our responses always contain a payload, so the digest will always be generated.

An example of response could be the following:

HTTP/1.1 200 OK
< Server: nginx/1.15.9
< Date: Tue, 12 Mar 2019 15:14:22 GMT
< Content-Type: application/json;charset=UTF-8
< CB-Response-ID: de4da138-3119-4c42-86fb-13b0a848a8e7
< Signature: keyId="chebanca",algorithm="rsa-sha256",headers="(request-target) digest cb-response-id date",signature="H0lIa88JXUMcnSMVApgA92JImIoP9MoNTqxBZ/WPehET2eL+bYqgBpEI3fjN0VZcXV0EzNnCSZr/4LbnfAvR0TIe7WiTwwPLle5+v5tIKUIB2nKjZQQCofP37469823hCLjm1tFLXxRdbpHj0PXMm7KVtD5Pj4TYHbT7sH3TgiqpOw07HgNmLgQ8alLo+eYyPLQ8P6e/Xbf2RtzZV4dckWIpAv6YcbIRNyeUHtzeepLb0PykJrTQ/lzaAELgrP8jiNJS/eWP46ZU/BQj+sC+GdBXgdU+gYoCpWqLDDdwXiu51QnliQuEezKbELQEa7bap2DKBlnmncqyO4LtRKoXvX8Isoyv1LR27q/UKcyA5t61h6jbm+1xe3khcI6H6stMOE7TfGrKClszlEtaUXw5SQ+G0YjdWHkpzYHZ7d+V3ZCfL6tk5AGz1oHxAGwHZPL/p2rUs31Il6IFWgGtK3E6qRt+intfYMiwkx4+am5CyqG1CwHOeFizckPRVY8GZ/vDRSsNlMj8b5c+qS+axMvCqEXDsrKGLPVBFQG/pdStMulG+uzHVBGGCLR2s3O3mFMS7ZeNwbG2ivKjVhlFRpw7cOGYDWgueP08LWYS0hM6PqaX1LpIGdLktpExAvCPFJG6mMbyvdemo/H2wa/tIEsD2Ve39U6yYERnn5FDMynk/kU="
< Digest: SHA-256=FsPmGo2FgYSVAdAXUX/0oKLy/J6awr0i4uOFRBquLc8=
  "data": {
  "result": {
    "requestId": "2c4a8d2d-653c-41d7-b43c-f36f4adb5ba6",
    "outcome": "SUCCESS",
    "flushMessages": true,
    "messages": []
  "_embedded": {}

To verify the signature, you should:

  1. Calculate the digest of the response payload and compare it to the Digest header;
    $ echo -n "$responsePayload" | openssl dgst -binary -sha256 | openssl base64
  2. Accept a Date response header not too far by now (e.g. 30 minutes could be an acceptable maximum interval);
  3. Extract public key from certificate in CB-Certificate header:
    $ openssl x509 -pubkey -noout -in $(CB-Certificate-to-file)  > /tmp/pubkey.pem
  4. Compose the signing string as defined previously, using the headers parameter defined in Signature HTTP header, in the same order of appearance (new line separated, no new line at the end):
    (request-target): post /private/test01
    digest: SHA-256=FsPmGo2FgYSVAdAXUX/0oKLy/J6awr0i4uOFRBquLc8=
    cb-response-id: de4da138-3119-4c42-86fb-13b0a848a8e7
    date: Tue, 12 Mar 2019 15:14:22 GMT
  5. Decode Base64 of the signature and write the output in file signatureBin:
    $ signature=H0lIa88JXUMcnSMVApgA92JImIoP9MoNTqxBZ/WPehET2eL+bYqgBpEI3fjN0VZcXV0EzNnCSZr/4LbnfAvR0TIe7WiTwwPLle5+v5tIKUIB2nKjZQQCofP37469823hCLjm1tFLXxRdbpHj0PXMm7KVtD5Pj4TYHbT7sH3TgiqpOw07HgNmLgQ8alLo+eYyPLQ8P6e/Xbf2RtzZV4dckWIpAv6YcbIRNyeUHtzeepLb0PykJrTQ/lzaAELgrP8jiNJS/eWP46ZU/BQj+sC+GdBXgdU+gYoCpWqLDDdwXiu51QnliQuEezKbELQEa7bap2DKBlnmncqyO4LtRKoXvX8Isoyv1LR27q/UKcyA5t61h6jbm+1xe3khcI6H6stMOE7TfGrKClszlEtaUXw5SQ+G0YjdWHkpzYHZ7d+V3ZCfL6tk5AGz1oHxAGwHZPL/p2rUs31Il6IFWgGtK3E6qRt+intfYMiwkx4+am5CyqG1CwHOeFizckPRVY8GZ/vDRSsNlMj8b5c+qS+axMvCqEXDsrKGLPVBFQG/pdStMulG+uzHVBGGCLR2s3O3mFMS7ZeNwbG2ivKjVhlFRpw7cOGYDWgueP08LWYS0hM6PqaX1LpIGdLktpExAvCPFJG6mMbyvdemo/H2wa/tIEsD2Ve39U6yYERnn5FDMynk/kU=
    $ printf %s "$signature" | base64 --decode > signatureBin
  6. Verify the signature. For example, supposing that you save the signing string in a file called signString, using OpenSSL:
    $ openssl dgst -sha256 -verify /tmp/pubkey.pem -signature signatureBin signString
     Verified OK

NOTE: We provide our CA used to sign the signing certificate here

Security requirements for Sandbox environment

When you call a Sandbox endpoint you are allowed to use only the following certificates:

WARNING: The certificates may change in the future. Please check again this page if you encounter a certificate issue

As a TPP, you can not access customer accounts data by default. In fact, the customer must be able to grant or revoke consent to give his own information to the TPP, specifying the privileges for each of his accounts.

Please note that the requested consent is not bound to a specific OAuth session, nor it has an expiration. Once requested, it will be valid until explicit revocation, so it's not necessary to request it for each login session.

The consent revocation may be requested by the TPP (with the dedicated APIs) or directly on our Internet Banking site by the customer.

To manage the customer consent, you must use these APIs (you can find the details in the API Reference section):

  • /private/customers/{customerId}/consent/list: get the customer consent current situation
  • /private/customers/{customerId}/consent/request: it allows the TPP to request privileges to access data / execute payments on behalf of the customer for a specific set of accounts
  • /private/customers/{customerId}/consent/request/{resourceId}/confirm: called after the customer has authorized the consent request, it actually add the privileges on CheBanca! databases
  • /private/customers/{customerId}/consent/revoke: it allows the TPP to revoke its privileges to access data / execute payments on behalf of the customer for a specific set of accounts
  • /private/customers/{customerId}/consent/revoke/{resourceId}/confirm: called after the customer has authorized the consent revocation request, it actually revokes the privileges on CheBanca! databases

These APIs can only be called with a valid OAuth access token.

The flow to request/revoke privileges is the same as the one for the payments (see here): the first API accepts the data, then you have to call the /private/auth/security/sca/{resourceId}/approach to start the authorization flow, and at the end you can call the confirmation API to actually grant/revoke privileges.

IMPORTANT: the consent is always requested for an authorization_code application. If the TPP also configured a client_credentials application, the consent will be automatically propagated to it.

Batch mode

The PSD2 directive states that a TPP can invoke APIs to obtain information about the customer's accounts even when the customer is not online. The directive impose some limitations to this access type:

  • a TPP can access the customer account information at most 4 times/day
  • since the information is sensitive, at least one customer SCA is required in the last 90 days

The batch mode can be implemented requesting a client_credentials application.

To implement this restrictions, in batch mode, CheBanca will:

  • return a specific error (HTTP status 420) in case the TPP exceeds the call limit
  • return 403 HTTP status with the specific error code CUSTOMER_SCA_EXPIRED if the TPP is trying to obtain information about a customer who has not performed a SCA in the last 90 days

    The customer SCA can be performed only within an authorization_code application, so the requirements is that the TPP should notify the user to log into the TPP application. This will refresh the counter.

To improve the performances, you can (although it's not required) cache the list of accounts of a customer returned by the /private/customers/{customerId}/accounts API.

This introduce a problem if the productId is no more valid (the account is closed, for example): in case you invoke an API with an invalid productId, it will return the specific error code ARC04 - "Invalid productId specified" (still with a 500 HTTP status).

If your application receives an ARC04 code, it must not cache anymore the productId related to the customer.