TPP Setup for PSD2 APIs

In order to access PSD2 APIs in sandbox or in production, the TPP must ensure that they are in possession of valid certificates prior to registering using the TPP registry.

The TPP needs:

  • Valid eIDAS certs (QWAC), or generated certs from OP for TLS MA
  • Keypair for signing JWTs (QSEALC)
  • A signed Software Statement Assertion (SSA)
  • A signed registration JWT with the SSA as one of its fields.

Note: This document assumes cURL version 7.65.0 or higher.

Java helper app

Certificate management and TPP registration are complicated operations. To help you get started, we have provided a Java application which you can use to complete these steps in the sandbox environment, and which you may find helpful when building your production application.


Certificates

The TPP must either possess valid eIDAS certificates or use our TPP certificate generation API for generating certificates for use in our PSD2 sandbox environment.

Note that while some certificate vendors offer test certificates, OP's PSD2 sandbox only supports real certificates and certificates from our sandbox certificate API. In other words, 3rd party test certificates are not supported.

Two types of certificates are required:

CertificateAlt NameUse
QWACMTLS cert/keyTransport Layer Security: Mutually authenticated TLS
QSEALCSigning cert/keyDigital Signatures, i.e. signing JWTs.

If the TPP is in possession of valid certificates, they may choose to ignore this step and move on to TPP registration (below).

Certificates can be purchased from Qualified Trust Dervice Providers (QTSPs). The European Union maintains a list of Trust Services. Search the list by Type of Service and select "Qualified certificate for electronic seal" and "Qualified certificate for website authentication", and select the countries you are interested in.

More information on certificates at e.g. Open Banking Europe's FAQ.

Generating certificates for sandbox

The certificate generation service provides two certificates: one for emulating QWAC (for establishing mTLS), and another for emulating QSEALC (JWT signing).

Once the certificates have been generated and delivered to the TPP, the generation service makes the public keys available to services in our sandbox environment. This means that the TPP does not need to publish a JWKS endpoint for their app, unless they want to use keypairs they have obtained from another authority.

Call the certificate generation API as follows:

curl -X POST -v 'https://sandbox.apis.op-palvelut.fi/oop-test-client-certs-psd2/v1/client-cert?c=<COUNTRY>&cn=<TPP_NAME>' \
-H 'x-api-key: <APP_API_KEY>' \
-H 'Content-Length: 0' \
-H 'Accept: application/json' \

Query parameters

Parameter nameExplanation
cCountry as ISO 3166-1 Alpha 2 (e.g. FI).
cnCommon name of the TPP company (e.g. "Brinklyfy.io").

Sample Response

HTTP/1.1 201 Created
Date: Tue, 05 Mar 2019 14:03:25 GMT
Content-Type: application/jwk-set+json; charset=UTF-8
Location: /oop-test-client-certs-psd2/v1/client-cert/[tppId]

{
  "publicJwks": {
    "keys": [
      {
        "kty": "RSA",
        "e": "AQAB",
        "use": "sig",
        "kid": "b9_EDg",
        "x5c": [
          "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"
        ],
        "alg": "RS256",
        "n": "lW7KSQnUjSJFaUDi3JWe3C7R0Cbl8PQdgdZYj7TLjUei9ImgqOGhm3d41xol-BZxTHQltzZ3KvhqWvXEEzMUn3fs7MKngf0s8GgH5VwJrx3rRaXYEkm-R--rXN2JHbLiKO7w2CFqgc9XeOOKtOV0xsGLUiUnFqoIMxv-JiZ8UM5OU4Lk9wqdfsXDNXCRJHBk3d4DTBwoQM8ZbvpJ_dJKO5T8V8T6823I2BJ39b9SGCDLeTfWkBl5Y3RuBCRQZbI8Q-LgCOHQRBlF2gNWEXITMgAoA0ZGVpjejOFb_CeogPkt1MyzdR90H00kTMm76BSpWiTqm20fC5r7th_HRrXAzxU6T5S9UssmpB9-7N8j_kc4Ht_2CzG_mmzDTLzkYZZpgtr5Dxb-MVc36xurJKP0-7riQIckFnoc6ZAp9gtVlmlmBdBfv0WeI8BuXMam2as9DD6XRLhkQFyiht6qbB6Vw03X80rU0SnvM1r9g5TyBi4eE_FWNIwTEZwGrbYJmiyf7hNsqMLYvey8xiA0POFzBctgoK5ZnNaV6ZWy-a6ERU1liOqhQ5WvB6YGBHs5wLa6ThADszXltXGVIxyJ7-4mxlFKa_CTcxE2kt7W9BNBUiezHF-rK6jJCPcMpb6BDkkmEwpR__HhmsGIoyvREOlwSq8a8pmMSz-BdU7MpFLt99c"
      },
      {
        "kty": "EC",
        "use": "sig",
        "crv": "P-256",
        "kid": "R2Zktg",
        "x5c": [
          "MIICnDCCAkKgAwIBAgIER2ZktjAKBggqhkjOPQQDAjBBMQswCQYDVQQGEwJGSTEyMDAGA1UEAwwpT1AgU2VydmljZXMgUFNEMiBBVFkgVGVzdCBJbnRlcm1lZGlhdGUgQ0EwHhcNMTkwMzE0MDcyODI4WhcNMjQwMzE0MDcyODI4WjBZMQswCQYDVQQGEwJGSTEjMCEGA1UEAwwaU0FOREJPWFN1Y2Nlc3NmdWwgRmluYW5jZXMxJTAjBgNVBGEMHFBTRE9QLVRFU1QtVFBQLWI1M2QxYWY2LThjNTYwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQQYYh2/5q/HbI2GtKe/cHAyn6Ba+GcpC/uAZSUHiEhUQoDBfIJLtbNQToiHx1UjasiQwjXahR2/BGFc9HQSo7Jo4IBDjCCAQowCQYDVR0TBAIwADAUBgNVHSAEDTALMAkGBwQAi+xAAQEwgZAGCCsGAQUFBwEDBIGDMIGAMH4GBgQAgZgnAjB0MEwwEQYHBACBmCcBAQwGUFNQX0FTMBEGBwQAgZgnAQIMBlBTUF9QSTARBgcEAIGYJwEDDAZQU1BfQUkwEQYHBACBmCcBBAwGUFNQX0lDDBxOYXRpb25hbCBDb21wZXRlbnQgQXV0aG9yaXR5DAZFVS1OQ0EwDgYDVR0PAQH/BAQDAgeAMCUGA1UdIwQeMByAFJ7XE/T+dSSKrrLeC7l/vhAMF7DOggQPulJhMB0GA1UdDgQWBBTLUlO/XYxNo0RKqaCLP7/aWD9rwTAKBggqhkjOPQQDAgNIADBFAiB3Klrjlx2Igdtx7sE8N8XpGg5Y7bnXGvb9eBCEZASZoAIhAI9qJSiRvOPjKjqLGMQ9uoTFODLMQBh/dxj96UnYHHan"
        ],
        "x": "EGGIdv-avx2yNhrSnv3BwMp-gWvhnKQv7gGUlB4hIVE",
        "y": "CgMF8gku1s1BOiIfHVSNqyJDCNdqFHb8EYVz0dBKjsk",
        "alg": "ES256"
      }
    ]
  },
  "privateJwks": {
    "keys": [
      {
        "kty": "RSA",
        "d": "OOoOj4z38J0p9LGq9jriW5hVf0MhdwlzJ51885b3pCKxtROqJkhxpeX2F281IO1jT6w5JTPQZYRh9az_dhSReT9S9MYayI6mDBaDU18frRvnHQrSZfzCRWHKUb9q1u3n55FCF_96MmDHPxrjHIz8UisrJilSXvZ50wWJYfxsgtZgzLvg-xqG697kv7jU_xzzvfEuLlHXHwAB_tvDTLJIZbMT_VXgAtFAmtNF70pkOR5cgRm8Vfvt2Oxibp9qI6GkBHMnwxpGmXlKuoNkx5Y8K2IV21FrvtXQhbXbtAliy2SciR1CCIznTHi0J676K6Wa7uN6m6IPHft0fWRTIOJewaludIKzYNgJ2__pNczjor4sMv3L-El-JpEbnLksoo_QerQOkQbzgsjmknVfOTxCXSdT8u6f2YpkIrpeHNFtqnpMDrHsywcrryhOlyerbnTFt5EYawpLnDz7_fVHcfPBfW-m4XE8aeN2aUCfOraMpAr3oPRJ8eJcQGVBwBR4vpMYv5u_eK-sg9BakFxI34fuujm7z0AfkKeYc2wqAzagkV_ECiinH_5ow4-m1e9qVoefKawtSTTXoMwrV39GhCBJGYbpXEMac64-j7rJu3eUMsUcVLnQYqNkcUn3pxaQSvQZydeNSDoczpZajwvacNaJkNVO_vlyYs03x7cr9NCnM-k",
        "e": "AQAB",
        "use": "sig",
        "kid": "b9_EDg",
        "x5c": [
          "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"
        ],
        "alg": "RS256",
        "n": "lW7KSQnUjSJFaUDi3JWe3C7R0Cbl8PQdgdZYj7TLjUei9ImgqOGhm3d41xol-BZxTHQltzZ3KvhqWvXEEzMUn3fs7MKngf0s8GgH5VwJrx3rRaXYEkm-R--rXN2JHbLiKO7w2CFqgc9XeOOKtOV0xsGLUiUnFqoIMxv-JiZ8UM5OU4Lk9wqdfsXDNXCRJHBk3d4DTBwoQM8ZbvpJ_dJKO5T8V8T6823I2BJ39b9SGCDLeTfWkBl5Y3RuBCRQZbI8Q-LgCOHQRBlF2gNWEXITMgAoA0ZGVpjejOFb_CeogPkt1MyzdR90H00kTMm76BSpWiTqm20fC5r7th_HRrXAzxU6T5S9UssmpB9-7N8j_kc4Ht_2CzG_mmzDTLzkYZZpgtr5Dxb-MVc36xurJKP0-7riQIckFnoc6ZAp9gtVlmlmBdBfv0WeI8BuXMam2as9DD6XRLhkQFyiht6qbB6Vw03X80rU0SnvM1r9g5TyBi4eE_FWNIwTEZwGrbYJmiyf7hNsqMLYvey8xiA0POFzBctgoK5ZnNaV6ZWy-a6ERU1liOqhQ5WvB6YGBHs5wLa6ThADszXltXGVIxyJ7-4mxlFKa_CTcxE2kt7W9BNBUiezHF-rK6jJCPcMpb6BDkkmEwpR__HhmsGIoyvREOlwSq8a8pmMSz-BdU7MpFLt99c"
      },
      {
        "kty": "EC",
        "d": "dgFe-YT6xMaQ-xv4TQbt64bj6tTf3SkS7devXsOFqIo",
        "use": "sig",
        "crv": "P-256",
        "kid": "R2Zktg",
        "x5c": [
          "MIICnDCCAkKgAwIBAgIER2ZktjAKBggqhkjOPQQDAjBBMQswCQYDVQQGEwJGSTEyMDAGA1UEAwwpT1AgU2VydmljZXMgUFNEMiBBVFkgVGVzdCBJbnRlcm1lZGlhdGUgQ0EwHhcNMTkwMzE0MDcyODI4WhcNMjQwMzE0MDcyODI4WjBZMQswCQYDVQQGEwJGSTEjMCEGA1UEAwwaU0FOREJPWFN1Y2Nlc3NmdWwgRmluYW5jZXMxJTAjBgNVBGEMHFBTRE9QLVRFU1QtVFBQLWI1M2QxYWY2LThjNTYwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQQYYh2/5q/HbI2GtKe/cHAyn6Ba+GcpC/uAZSUHiEhUQoDBfIJLtbNQToiHx1UjasiQwjXahR2/BGFc9HQSo7Jo4IBDjCCAQowCQYDVR0TBAIwADAUBgNVHSAEDTALMAkGBwQAi+xAAQEwgZAGCCsGAQUFBwEDBIGDMIGAMH4GBgQAgZgnAjB0MEwwEQYHBACBmCcBAQwGUFNQX0FTMBEGBwQAgZgnAQIMBlBTUF9QSTARBgcEAIGYJwEDDAZQU1BfQUkwEQYHBACBmCcBBAwGUFNQX0lDDBxOYXRpb25hbCBDb21wZXRlbnQgQXV0aG9yaXR5DAZFVS1OQ0EwDgYDVR0PAQH/BAQDAgeAMCUGA1UdIwQeMByAFJ7XE/T+dSSKrrLeC7l/vhAMF7DOggQPulJhMB0GA1UdDgQWBBTLUlO/XYxNo0RKqaCLP7/aWD9rwTAKBggqhkjOPQQDAgNIADBFAiB3Klrjlx2Igdtx7sE8N8XpGg5Y7bnXGvb9eBCEZASZoAIhAI9qJSiRvOPjKjqLGMQ9uoTFODLMQBh/dxj96UnYHHan"
        ],
        "x": "EGGIdv-avx2yNhrSnv3BwMp-gWvhnKQv7gGUlB4hIVE",
        "y": "CgMF8gku1s1BOiIfHVSNqyJDCNdqFHb8EYVz0dBKjsk",
        "alg": "ES256"
      }
    ]
  },
  "publicJwksUrl": "https://example.generated.tpp/OP-TEST-TPP-b53d1af6-8c56"
}

The response contains everything you need for using PSD2 sandbox. See JWKS for more info on JWKs.

Some notes on each item:

FieldExplanationNote
publicJwksPublic JWKs for your applicationkty: RSA contains the QWAC emulation JWK. kty: EC contains the signing key (QSEALC emulation).
privateJwksPrivate JWKs for your appAs above, but for private keys.
publicJwksUrlJWKS available to sandbox servicesThis URL contains the above public keys for our sandbox services. In other words, TPP's are NOT required to set up a published JWKS endpoint for testing. If the TPP does not create their own JWKS endpoint, this value must be passed on in the registration call (field software_jwks_endpoint).

Note that the Location header contains a URI with tppId as its last element. Extract and save this value, as it is needed in TPP registration. Be aware that TPP ID is NOT equal to client ID.

Using the JWKS

The response value contains all elements needed for emulating PSD2 security.

  • typ: RSA - MTSL / QWAC emulation

    • Client certificate (public key) in the first element of the x5c array
    • Your private key, which must be built from n, e, and d in the corresponding privateJwks element.

      • Extract n, d and e
      • Base64urldecode n, d and e
      • Find p: the common factor of n, d and e
      • Resolve q: n divided by p. If p > q, swap p and q
      • Resolve first exponent: d % (p - 1)
      • Resolve second exponent: d % (q - 1)
      • Find the coefficient: q.modInverse(p)
      • Create the key set: new RSAPrivateCrtKeySpec(n, e, d p, q, exp1, exp2, coeff). Generate the private key.
      • Save the certificates in PEM format and create a p12 keystore.

Extracting private key

public void extractsCertificateAndPrivateKey() throws Throwable {
      JSONObject response = new JSONObject(jwkStr);
      JSONObject jwks = response.getJSONObject("privateJwks");
      JSONObject jwk = jwks.getJSONArray("keys").getJSONObject(0);

      BigInteger nBigInt = new BigInteger(1, java.util.Base64.getUrlDecoder().decode(jwk.getString("n")));
      BigInteger dBigInt = new BigInteger(1, java.util.Base64.getUrlDecoder().decode(jwk.getString("d")));
      BigInteger eBigInt = new BigInteger(1, java.util.Base64.getUrlDecoder().decode(jwk.getString("e")));

      tppPrivateCertKey = createCrtKey(nBigInt, eBigInt, dBigInt); 

      String tppPrivateKeyStr = toPemString("RSA PRIVATE KEY", tppPrivateCertKey.getEncoded());
      BufferedWriter privateKeyWriter = new BufferedWriter(new FileWriter("key.pem"));
      privateKeyWriter.write(tppPrivateKeyStr);
      privateKeyWriter.close();

      JSONArray certChain = jwk.getJSONArray("x5c");
      tppClientCert = certChain.getString(0);
      BufferedWriter clientCertWriter = new BufferedWriter(new FileWriter("client.crt"));
      clientCertWriter.write(toPemString("CERTIFICATE",  java.util.Base64.getMimeDecoder().decode(tppClientCert)));
      clientCertWriter.close();
  }

  public static RSAPrivateCrtKey createCrtKey(BigInteger n, BigInteger e, BigInteger d) throws Throwable {

      BigInteger p = findFactor(e, d, n);
      BigInteger q = n.divide(p);
      if (p.compareTo(q) > 1) {
          BigInteger t = p;
          p = q;
          q = t;
      }
      BigInteger exp1 = d.mod(p.subtract(BigInteger.ONE));
      BigInteger exp2 = d.mod(q.subtract(BigInteger.ONE));
      BigInteger coeff = q.modInverse(p);
      RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(n, e, d, p, q, exp1, exp2, coeff);
      KeyFactory kf = KeyFactory.getInstance("RSA");
      return (RSAPrivateCrtKey) kf.generatePrivate(keySpec);
  }
  • typ: EC - JWT signing keypair emulating QSEALC.

    • Build the private key from x, y, d, and crv
  • Public key: first element of the x5c array in the related publicJwks element.

Extracting private key

private void extractSsaSigningKey(String jwkStr) throws Throwable {
    JSONObject response = new JSONObject(jwkStr);
    JSONObject jwks = response.getJSONObject("privateJwks");
    JSONObject jwk = jwks.getJSONArray("keys").getJSONObject(1);

    ECKey.Builder builder = new ECKey.Builder(Curve.P_256, new Base64URL(jwk.getString("x")), new Base64URL(jwk.getString("y")));
    builder.d(new Base64URL(jwk.getString("d")));

    ecPrivateKey = builder.build().toECPrivateKey();
    ecPrivateKeyKid = jwk.getString("kid");
    jwksPublicUrl = response.getString("publicJwksUrl");

    String tppPrivateKeyStr = toPemString("PRIVATE KEY", ecPrivateKey.getEncoded());
    BufferedWriter privateKeyWriter = new BufferedWriter(new FileWriter("ssa-signing-key.pem"));
    privateKeyWriter.write(tppPrivateKeyStr);
    privateKeyWriter.close();
}

The certificates and keys should be available in PEM format and as a p12 keystore.

Key and certificate management is quite complicated. We recommend using standard libraries, e.g. java.security.Keystore and RestAssured.

Refer to our registration app for details on these procedures.


TPP Registration

To access PSD2 APIs, TPPs must register with the account/payment service provider. Registration serves a number of purposes:

  • Delivering an API Key & Client Credentials to the TPP (Provided by OP Developer in sandbox)
  • Keeping track of the TPPs using PSD2 APIs
  • Submitting important security information, e.g. JWKS endpoints.

In sandbox as well as in production, the TPP must take the following actions:

  • Publish a publicly available JWK Set in standard JWKS Set format. If using a sandbox test certificate, you may use the publicJwksUrl received in the certificate generation response.
  • Build the SSA (Software Statement Assertion) and sign it with a QSEALC/emulated signing key
  • Build the registration JWT, include the signed SSA JWT in it, and sign the registration JWT with the same QSEALC/emulated signing key
  • POST the registration JWT to the API.

Registration call

To register, you must construct, sign and and POST a JSON Web Token (JWT) to our TPP registration endpoint. While doing this, you must present your QWAC or, in sandbox, the equivalent certificate received from the certificate generation API.

POST /tpp-registration/register HTTPS/1.1
Host: mtls-apis.psd2-sandbox.op.fi
Content-Type: application/jwt
Accept: application/json
x-api-key: <APP_API_KEY> (Only required in sandbox)

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImEwNDdiZTkwZDkzYTNlZDVlOGZlMjdjZDc2YTYzMWY2MTM0YWI5OTgifQ.eyJpYXQiOjE1MzY3MjU4NzAsImV4cCI6NDY5MjQxODQ3MCwiYXVkIjoiaHR0cHM6Ly9vcC5maS8iLCJqdGkiOiJjZmM4MDg5MC1iNjQyLTExZTgtYjczOC0xNTc0MTU1MmNhZWMiLCJyZWRpcmVjdF91cmlzIjpbImh0dHBzOi8vdGVzdHRwcC5yZWRpcmVjdC51cmwiLCJodHRwczovL3Rlc3R0cHAucmVkaXJlY3QyLnVybCIsImh0dHBzOi8vdGVzdHRwcC5yZWRpcmVjdDMudXJsIl0sImdyYW50X3R5cGVzIjpbImNsaWVudF9jcmVkZW50aWFscyIsImF1dGhvcml6YXRpb25fY29kZSIsInJlZnJlc2hfdG9rZW4iXSwic29mdHdhcmVfc3RhdGVtZW50IjoiZXlKaGJHY2lPaUpGVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0lzSW10cFpDSTZJbUV3TkRkaVpUa3daRGt6WVRObFpEVmxPR1psTWpkalpEYzJZVFl6TVdZMk1UTTBZV0k1T1RnaWZRLmV5SnBjM01pT2lKalptTTNPVE0yTUMxaU5qUXlMVEV4WlRndFlqY3pPQzB4TlRjME1UVTFNbU5oWldNaUxDSnBZWFFpT2pFMU16WTNNalU0TnpBc0ltVjRjQ0k2TkRZNU1qUXhPRFEzTUN3aWFuUnBJam9pWTJaak56a3pOakl0WWpZME1pMHhNV1U0TFdJM016Z3RNVFUzTkRFMU5USmpZV1ZqSWl3aWMyOW1kSGRoY21WZlkyeHBaVzUwWDJsa0lqb2lUM0JsYmtKaGJtdHBibWNnVlc1cGNYVmxJR05tWXpjNU16WXhMV0kyTkRJdE1URmxPQzFpTnpNNExURTFOelF4TlRVeVkyRmxZeUlzSW5OdlpuUjNZWEpsWDNKdmJHVnpJanBiSWxCSlV5SXNJa0ZKVXlJc0lsQkpTVk1pWFN3aWMyOW1kSGRoY21WZmFuZHJjMTlsYm1Sd2IybHVkQ0k2SW1oMGRIQTZMeTlzYjJOaGJHaHZjM1E2T0RnNU9TOHVkMlZzYkMxcmJtOTNiaTlsWTJSellTOXFkMnR6TG1wemIyNGlMQ0p6YjJaMGQyRnlaVjlxZDJ0elgzSmxkbTlyWldSZlpXNWtjRzlwYm5RaU9pSm9kSFJ3T2k4dmJHOWpZV3hvYjNOME9qZzRPVGt2TG5KbGRtOXJaV1F2WldOa2MyRXZhbmRyY3k1cWMyOXVJaXdpYzI5bWRIZGhjbVZmWTJ4cFpXNTBYMjVoYldVaU9pSjBaWE4wTFdOc2FXVnVkQ0lzSW5OdlpuUjNZWEpsWDNKbFpHbHlaV04wWDNWeWFYTWlPbHNpYUhSMGNITTZMeTkwWlhOMGRIQndMbkpsWkdseVpXTjBMblZ5YkNJc0ltaDBkSEJ6T2k4dmRHVnpkSFJ3Y0M1eVpXUnBjbVZqZERJdWRYSnNJaXdpYUhSMGNITTZMeTkwWlhOMGRIQndMbkpsWkdseVpXTjBNeTUxY213aVhTd2ljMjltZEhkaGNtVmZZMnhwWlc1MFgzVnlhU0k2SW1oMGRIQnpPaTh2ZEhCd0xtTnZiU0lzSW05eVoxOXFkMnR6WDJWdVpIQnZhVzUwSWpvaWFIUjBjSE02THk5cWQydHpMbTl3Wlc1aVlXNXJhVzVuTG05eVp5NTFheTl2Y21kZmFXUXZiM0puWDJsa0xtcHJkM01pTENKdmNtZGZhbmRyYzE5eVpYWnZhMlZrWDJWdVpIQnZhVzUwSWpvaWFIUjBjSE02THk5cWQydHpMbTl3Wlc1aVlXNXJhVzVuTG05eVp5NTFheTl2Y21kZmFXUXZjbVYyYjJ0bFpDOXZjbWRmYVdRdWFtdDNjeUlzSW05eVoxOXVZVzFsSWpvaVZHVnpkQ0JVVUZBaUxDSnZjbWRmYVdRaU9pSmpabU0zT1RNMk1DMWlOalF5TFRFeFpUZ3RZamN6T0MweE5UYzBNVFUxTW1OaFpXTWlmUS5TbU1nRXRnRGpCV3BsZ3JpNkRURDR4c0ZEYkVyQmpLQlNYZHdkQjdIRHlvbU5tT2dCS2J6eE5QSF9RUGQ2VGhnTkNjWHQtYmdLTzBxS2l0Zm5UdVFYQSJ9.NFZ8i9pVbkYlHvuBufFZ06YXJozQLh3rJul3rJRYtAs-u6FWyk7AbXSv4Uhu8Ioi4d_Nw1kRNYnmI6iBeLCcq55sLdX4cAUGoWJtPDc0jhmUkDwYuO9RGvETtjic1kdjwP8Kg4u8iiFGZJOy1DXSQmh-HNrgIKjd9Srt85oLfRCqEuoNRrsmiA-lHX2CB3vAmgVVcblm5GoIkGlTg77sGozM28XU15KWAx7eiijcuFBD8vozoUzuJEGsZA5342NcCex42fUz74SK0hRksWNBXR7vZ7oYvCR5uvCetALqMuDDIIFky9bKDeWYEC5Bz_l_nlSJOKnNpFFKBbRJezpsbw

Client Registration Request

The registration request and software statement are signed with ES256 or RS256, using your QSEALC or generated signing key (in sandbox only).

Structure

{
  "alg": "ES256",
  "typ": "JWT",
  "kid": "a047be90d93a3ed5e8fe27cd76a631f6134ab998"
}
{
  "iat": 1536725870,
  "exp": 4692418470,
  "aud": "https://op.fi/",
  "jti": "cfc80890-b642-11e8-b738-15741552caec",
  "redirect_uris": [
    "https://testtpp.redirect.url",
    "https://testtpp.redirect2.url",
    "https://testtpp.redirect3.url"
  ],
  "grant_types": [
    "client_credentials",
    "authorization_code",
    "refresh_token"
  ],
  "software_statement": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImEwNDdiZTkwZDkzYTNlZDVlOGZlMjdjZDc2YTYzMWY2MTM0YWI5OTgifQ.eyJpc3MiOiJjZmM3OTM2MC1iNjQyLTExZTgtYjczOC0xNTc0MTU1MmNhZWMiLCJpYXQiOjE1MzY3MjU4NzAsImV4cCI6NDY5MjQxODQ3MCwianRpIjoiY2ZjNzkzNjItYjY0Mi0xMWU4LWI3MzgtMTU3NDE1NTJjYWVjIiwic29mdHdhcmVfY2xpZW50X2lkIjoiT3BlbkJhbmtpbmcgVW5pcXVlIGNmYzc5MzYxLWI2NDItMTFlOC1iNzM4LTE1NzQxNTUyY2FlYyIsInNvZnR3YXJlX3JvbGVzIjpbIlBJUyIsIkFJUyIsIlBJSVMiXSwic29mdHdhcmVfandrc19lbmRwb2ludCI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODg5OS8ud2VsbC1rbm93bi9lY2RzYS9qd2tzLmpzb24iLCJzb2Z0d2FyZV9qd2tzX3Jldm9rZWRfZW5kcG9pbnQiOiJodHRwOi8vbG9jYWxob3N0Ojg4OTkvLnJldm9rZWQvZWNkc2Evandrcy5qc29uIiwic29mdHdhcmVfY2xpZW50X25hbWUiOiJ0ZXN0LWNsaWVudCIsInNvZnR3YXJlX3JlZGlyZWN0X3VyaXMiOlsiaHR0cHM6Ly90ZXN0dHBwLnJlZGlyZWN0LnVybCIsImh0dHBzOi8vdGVzdHRwcC5yZWRpcmVjdDIudXJsIiwiaHR0cHM6Ly90ZXN0dHBwLnJlZGlyZWN0My51cmwiXSwic29mdHdhcmVfY2xpZW50X3VyaSI6Imh0dHBzOi8vdHBwLmNvbSIsIm9yZ19qd2tzX2VuZHBvaW50IjoiaHR0cHM6Ly9qd2tzLm9wZW5iYW5raW5nLm9yZy51ay9vcmdfaWQvb3JnX2lkLmprd3MiLCJvcmdfandrc19yZXZva2VkX2VuZHBvaW50IjoiaHR0cHM6Ly9qd2tzLm9wZW5iYW5raW5nLm9yZy51ay9vcmdfaWQvcmV2b2tlZC9vcmdfaWQuamt3cyIsIm9yZ19uYW1lIjoiVGVzdCBUUFAiLCJvcmdfaWQiOiJjZmM3OTM2MC1iNjQyLTExZTgtYjczOC0xNTc0MTU1MmNhZWMifQ.SmMgEtgDjBWplgri6DTD4xsFDbErBjKBSXdwdB7HDyomNmOgBKbzxNPH_QPd6ThgNCcXt-bgKO0qKitfnTuQXA"
}
FieldTypeDescription
algstringThe signing algorithm used. ES256 or RS256.
typstringType of the token. Always JWT.
kidstringHints at the key pair used with this signature. Helps the consumer fetch the right public key for validation.
iatnumber / integerThe time at which the JWT was issued. In epoch seconds.
expnumber / integerRequest expiration time. In epoch seconds.
audstringRequest audience (The ASPSP). Use value https://op.fi/.
jtistringJWT ID: a unique identifier for the ID. MUST be unique to prevent replay attacks.
redirect_urisarray / stringArray of whitelisted redirection URI strings for use in redirect-based flows (i.e. authorization code).
grant_typesarrayArray identifying the different grant types the TPP will use.
software_statementjwtSSA JWT signed by TPP in compact serialized format.

Software Statement Assertion

The Software Statement Assertion is a JWT created and signed by the TPP. It is signed with the TPP's QSEALC or the associated signing key received from OP's certificate generation service (only in sandbox). The signature is created using ES256 or RS256 algorithm and must match the registration request.

If you have a generated certificate, you may use the "publicJwksUrl" in the software_jwks_endpoint field: you don't need to set up a public JWKS endpoint to get started in the sandbox.

{
  "alg": "ES256",
  "typ": "JWT",
  "kid": "a047be90d93a3ed5e8fe27cd76a631f6134ab998"
}
{
  "iss": "OP-TEST-TPP-354d734d-1273",
  "iat": 1536725870,
  "exp": 4692418470,
  "jti": "cfc79362-b642-11e8-b738-15741552caec",
  "software_client_id": "2dc3102d-8f49-4c57-8de8-a63ccade3766",
  "software_roles": [
    "PIS",
    "AIS",
    "PIIS"
  ],
  "software_jwks_endpoint": "https://testtpp.url/.well-known/jwks.json",
  "software_jwks_revoked_endpoint": "https://testtpp.url/.revoked/jwks.json",
  "software_client_name": "test-client",
  "software_redirect_uris": [
    "https://testtpp.redirect.url",
    "https://testtpp.redirect2.url",
    "https://testtpp.redirect3.url"
  ],
  "software_client_uri": "https://tpp.com",
  "org_name": "Test TPP",
  "org_id": "OP-TEST-TPP-354d734d-1273"
}
FieldTypeDescription
issstringTPP ID.
iatnumber / integerThe time at which the JWT was issued. In epoch seconds.
expnumber / integerSSA expiration time. In epoch seconds.
jtistringJWT ID, e.g. 5e63fcae-0c46-4be3-bbf8-eb6eb9bb14a1.
software_client_idstringUnique Client ID of the software client. Format must be UUID, e.g. 2dc3102d-8f49-4c57-8de8-a63ccade3766
software_client_namestringHuman-readable name of the software client.
software_client_uristring / URIWebsite or resource root URI.
software_jwks_endpointstring / URIEndpoint that exposes all active signing public keys and certificates for the software. When you implement your own JWKS endpoint, ensure that it contains at least the JWK for validating registration JWT and the JWK for validating authorization JWT. Both of these JWKs MUST contain x5c with the QSEALC certificate. The contents of this endpoint must be in JWK Set format. For Sandbox, this field may contain the URI received during certificate generation.
software_jwks_revoked_endpointstring / URIEndpoint that contains all revoked JWKs for the software. Same format as software_jwks_endpoint.
software_redirect_urisarray / stringContains all redirect URIs for the software client.
software_rolesarray / stringPSD2 roles this software is authorized to perform.
org_idstringProduction: Unique ID for the software creator as assigned by a national competent authority. Must match the organizationId in the certificate, excluding the PSD prefix. Sandbox: The TPP ID received from the certificate generation service.
org_namestringLegal Entity Identifier or other known organisation name.

Registration Response

Once successfully registered, you will receive a JSON response containing your client information. Keep the response secure, as it contains the client credentials for accessing PSD2 APIs.

Sample response body

{
    "client_id": "4vPsWdBi5d6G2dezEmfvh",
    "client_id_issued_at": 1552553405,
    "client_secret": "jqGQqZ6s6AXahdiMVFj9N",
    "client_secret_expires_at": 0,
    "api_key": "cGY7jn9z8Y141GZWMu6pNlCT0tfYlD6PM",
    "client_name": "software-client-1",
    "redirect_uris": [
        "https://localhost:8181",
        "https://tpp-demo-app.demo/oauth/access_token"
    ],
    "grant_types": [
        "client_credentials",
        "authorization_code",
        "refresh_token"
    ],
    "software_id": "6fdd0ca8-33f1-4b84-932a-c92e2f179bc8",
    "scope": "openid accounts payments",
    "jwks_endpoint": "https://example.tpp/jwks.url",
    "software_roles": [
        "AIS",
        "PIS"
    ]
}
Field NameTypeDescription
client_idstringClient ID of the TPP application.
client_id_issued_atDate / timestampTime of issuance of the client credentials. Seconds since Epoch.
client_secretstringClient secret of the TPP application.
api_keystringAPI Key of the TPP application. In sandbox, this is a replayed value as the API key has already been received from OP Developer.
redirect_urisarray -> string / URIRedirect URIs registered to the application.
grant_typesarray -> stringGrant types allowed to the application.
software_idstringYour registered application Software ID.
scopestringSpace-delimited list of scopes allowed to the application. Possible values: openid, accounts, payments, fundsconfirmations.
jwks_endpointstring / URIJWKS endpoint registered to the application.
software_rolesarray -> stringLicense-based roles registered to the application. Possible values: AISP, PISP, CBPII.

Verifying your registration

After registering successfully, you can verify the validity of your certificates and API key with the following cURL call (assuming you have AIS access):

curl -v --key key.pem --cert client.crt https://mtls-apis.psd2-sandbox.op.fi/accounts-psd2/v1/accounts/FI1000000 -H "x-api-key: <your-api-key>"

If the certificates and API key are valid, you will receive a 401 response:

HTTP/1.1 401 Invalid authorization
Date: Mon, 02 Sep 2022 08:48:30 GMT
Content-Type: application/json;charset=UTF-8
Content-Length: 90
Connection: keep-alive
Set-Cookie: TSxxx=xxx; Path=/

Although the response is an error, it indicates that TLS has been established successfully and the API key has been accepted. Any other type of error signals an issue with either the API key or certificate.

If you received a 401 response similar to the sample above, you are ready to start working on authorization. See our workflow documentation for Account Information, Payment Initiation and Confirmation of Funds services.