Workflow for PSD2 Payments API

In this document, we will guide you through each step you need to take in order to access our PSD2 Payments API.

All API calls here are made using mutually authenticated TLS.

References

Open Banking Security Profile

FAPI - FAPI Read-Only profile

FAPI-RW

OIDC Connect Core

MTLS

Terminology & abbreviations

  • TPP: the Third-Party Provider accessing the API.
  • Client App: an application developed by the TPP for accessing the API.

Prerequisites

Before you can start using our PSD2 APIs, ensure that you meet the requirements outlined in this section.

Sandbox

  • A registered app on OP Developer: API key and client secret
  • Certificates and related keypairse from our certificate generation API for mutually authenticated TLS
  • Successful registration via the TPP registration endpoint (incl. functional JWKS endpoints, redirect uris...)

In Test, the host addresses are the following:

ServiceHost
Authentication and authorizationhttps://authorize.psd2-sandbox.op.fi
Business APIshttps://mtls-apis.psd2-sandbox.op.fi
JWKS endpoint for ID token signature verificationhttps://authorize.psd2-sandbox.op.fi/oauth/jwks/PSD2_OIDC

Production

  • Valid AISP/PISP registration and approval from a European financial authority, passported if necessary
  • Valid eIDAS certificates: QWAC for establishing MTLS, QSEAL for signing JWTs
  • Successful registration via the TPP registration endpoint (incl. functional JWKS endpoints, redirect uris...)

Overview

Sequence diagram of PIS workflow

Succesfully using the PSD2 Payment Initiation Service API requires completing the following steps:

  1. The end user requests to submit a Payment
  2. Client App authenticates with Client Credentials Flow
  3. Client App sets up one or more payments at /sepa-payments
  4. End user authenticates and authorizes all payments with the same authorizationId
  5. Client App receives an Authorization Code and exchanges it for an Access Token
  6. Client App creates payment-submission
  7. Client App polls payment status.

Now let's look at each of these steps one by one.

Flow for accessing the Payments API

Accessing the Payments API requires some extra details to the flow described in the previous section

1. End user requests payment creation

The user simply requests that the TPP client make a payment on their behalf. The client application may receive several payment orders from the end user, committing them at once.

2. TPP app authenticates using the Client Credentials flow and receives an access token with the scope payments.

To begin interacting with the Payment Initiation Service, the client application authenticates with the Oauth Client Credentials Flow.

The authentication request has the following format:

POST /oauth/token HTTPS/1.1
Host: mtls-apis.psd2-sandbox.op.fi
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&scope=payments&client_id=<APP_CLIENT_ID>&client_secret=<APP_CLIENT_SECRET>

The following cURL call can be used for completing this step. The call assumes that your current directory contains your client certificate (client.crt) and the related private key (key.pem) - both are created by the Registration Helper App.

curl -vk --key key.pem --cert client.crt https://mtls-apis.psd2-sandbox.op.fi/oauth/token -d 'grant_type=client_credentials&scope=payments&client_id=<client_id>&client_secret=<client_secret>'

If the request is successful, the response will look like this:

{
  "token_type" : "bearer",
  "access_token" : "Axqx362CnSmLABgzqcBasG0pxBj9",
  "scope" : "payments",
  "status" : "approved",
  "expires_in" : "86399",
}

Save the access_token for use in the next step.

3. TPP sets up the payment(s)

Now the TPP application is in possession of an access token. To advance, the TPP establishes mutually authenticated TLS with the host and uses the access token to make a POST call to /sepa-payments.

After creating the first payment, the client application receives an authorizationId. If multiple payments are to be authorized in one batch, subsequent POST requests to /sepa-payments can include this authorizationId in the request body. The table below explains the relationship between authorizationId and paymentId.

NameLegend
authorizationIdA value representing a single batch of payments. Can be reused if several payments are to be authorized simultaneously.
paymentIdA string representing and identifying the individual payment resource. Used when submitting authorized payments.
submissionIdA value generated by the TPP for each submission. A string 1 to 40 characters in length. Its main purpose is for the TPP to track submitted payments.

With the authorization ID, the client application can create a bundle of payments by replaying the value when creating additional payments. Payments are created one-by-one, but the authorization ID links them together.

Examine or delete created payments

After creation, a single payment can be examined or deleted (i.e. erased before submission) with a GET or DELETE call to /sepa-payments/{paymentId}.

Examine and revoke authorizations

The authorizationId can be used for examining the details of the authorization and/or the payments associated with it, or for deleting it, revoking the entire bundle of associated payments.

4. Authentication and authorization

Authentication and authorization take place between the end user and the auth server of the account servicer. The role of the TPP is to prepare an appropriate JWT request, fill in the necessary query parameters, and redirect to the authorization address.

4.1 Constructing the authorization request

The request is a JSON Web token that has been signed with a QSEALC private key (or a key which emulates QSEALC) using RS256 or ES256. The corresponding public key must be made available at a registered JWKS endpoint (not required in sandbox).

The header and payload of the JWT request have the following structure:

{
  "alg": "ES256",
  "typ": "JWT",
  "kid": "Vi3hb" --> This value varies per key
}
.
{
  "aud": "https://mtls.apis.op.fi",
  "iss": "1ztgnp8NZzUmlmiAdNoC",
  "response_type": "code id_token",
  "client_id": "1ztgnp8NZzUmlmiAdNoC",
  "redirect_uri": "https://localhost:8181/oauth/access_token",
  "scope": "openid payments",
  "state": "bb16b497-097f-4528-9380-4305eb760232",
  "nonce": "76fb7d3a-3a14-4bf7-96fd-e770c3c1e33e",
  "max_age": 86400,
  "claims": {
    "userinfo": {
      "authorizationId": {
        "value": "976da7b0-1db2-410d-a5c9-e3a37bc4d7a1",
        "essential": true
      }
    },
    "id_token": {
      "authorizationId": {
        "value": "976da7b0-1db2-410d-a5c9-e3a37bc4d7a1",
        "essential": true
      },
      "acr": {
        "essential": true,
        "values": [
          "urn:openbanking:psd2:sca"
        ]
      }
    }
  }
}
.
<signature>
FieldTypeRequiredLegend
typstringtrueType of the token. Always gets value JWT .
algstringtrueThe signing algorithm used. Possible values are RS256 and ES256 .
kidstringtrueID of the signing key. A key corresponding to this value must be available at a public JWKS endpoint. If you have used the registration app in sandbox, this value is found in the field ssaSigningKid in registration-result.txt (the key is in ssa-signing-key-pem).
audstringtrueIntended audience of the JWT, i.e. the host URL of the API.
issstringtrueIssuer of the token, i.e. Client ID of the TPP.
response_typestringtrueSpecifies the type of the authorization flow. Allowed values are code and code id_token .
client_idstringtrueUnique identifier of the client application.
redirect_uristringtrueThe address to which the end user wil be redirected after authentication and authorization. The access token or error descriptors will be attached to this uri as query parameters.
scopestringtrueScope names that identify the extent of the requested authorization, as a list of space-separated names. Per the OIDC specification all requests must contain the openid scope. Only one of the following scopes is allowed for each request: accounts , payments , fundsconfirmations . For PIS, the required value is "openid payments".
statestringtrueA string that will be returned to the TPP. Used for validating responses and correlating requests with responses. Must only contain supported characters specified in RFC 4648 Section 5 , Table 2.
noncestringtrueA unique string that is used for protecting against replay attacks. Generated by the Client App.
max_agenumbertrueMaximum allowed age for the authentication, in seconds.
claimsobjecttrueContains information related to the request. The field authorizationId contains the authorizationId obtained while registering intent.
userinfoobjecttrueContains information related to the request. The field authorizationId contains the authorizationId obtained while registering intent.
id_tokenobjecttrueThe contents of this field are similar to userinfo.
acrobjtrueSpecifies the requested authorization contexts. Currently, the only allowed value is urn:openbanking:psd2:sca .

Once the body and header have been constructed, follow these steps: 1. Base64Urlencode the header and body separately. Append the body to the header, separating the two parts with a dot. 2. Create a signature by taking a copy of the newly created string and encrypting it with your private key (from QSEALC or emulated) using the algorithm specified in the header. This is the signature. 3. Attach the signature to the encoded header and body with a dot. You'll get something like this:

eyJhbGciOiJIUzI1NiJ9.eyJtc2ciOiJIZWxsbyJ9.N6-M8oOwCq9x820PR6HMtWo-_FL5GeWUSqQ66H2F5kA

Example code for signing JWTs

This code assumes that a file named ssa-signing-key.pem exists in the current directory. In sandbox, developers may use the Registration Helper App, which automatically generates a key with this filename.

import * as fs from 'fs';
import * as jwt from 'jsonwebtoken'; 

//claims is a valid pre-created JWT body
const asymmetricSign = (claims: object) => {
  const algorithm = 'ES256';
  const signCert = fs.readFileSync(`./ssa-signing-key.pem`, 'ascii');
  const jwtOptions = {
    header: {
      typ: 'JWT',
      kid: 'BSyrDg'
    },
    algorithm
  };
  return jwt.sign(JSON.stringify(claims), signCert, jwtOptions);
};

Learn more about JWTs on jwt.io. Note that the JWT editor is currently incapable of processing this type of key.

4.2 Redirection

Once the request is complete, the TPP will redirect the customer to OP's auth server. The client app must append the following query parameters to the redirection URI:

  • request (the JWT request)
  • response_type
  • client_id
  • scope

The values of the parameters MUST match with those in the JWT request. Remember that all query parameters must be URLencoded.

The redirect will have the following structure:

https://authorize.psd2-sandbox.op.fi/oauth/authorize
?request=<your_JWT_string>
&response_type=code
&client_id=******
&scope=openid%20payments

Direct the customer to this URI in the browser.

4.3 The customer authenticates and authorizes

Authentication and authorization are the business of the customer and the auth server. The customer logs in, selects the authorizations they wish to consent to, and approves or rejects them.

4.4 Back to Client Application

The auth server will redirect the end user to the redirect URI specified in the request. There are two possible outcomes:

Success:
<REDIRECT_URI>?state=<state>&code=<authorization_code>

Success with ID token:
<REDIRECT_URI>#state=<state>&code=<authorization_code>&id_token=<id_token>

Error:
<REDIRECT_URI>?state=<state>&error=<error_description>

For example:
https://localhost:8181/oauth/access_token?error=temporarily_unavailable&state=373b23cd-a1b3-449a-810f-cd4b36f7a60c

ID tokens are created and returned on two occasions:

  1. When using response type code id_token in the authorization request. The id token will be delivered in a query parameter attached to the client app's redirect URI. Note that when an ID token is present, the query string is in fact an URI fragment (begins with "#" instead of "?").
  2. When requesting a token from a token endpoint and scope includes openid (in OP PSD2, always). The token will be returned in the response body to the token request.

The ID token contains information about the authorization, and provides an additional layer of security.

The ID token contains the following information:

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "12345"
}
.
{
  "iss": "https://authorize.psd2-sandbox.op.fi",
  "iat": 1234569795,
  "sub": "28c52f98-7541-42a2-9ca0-b86ceac47692",
  "acr": "urn:openbanking:psd2:sca",
  "authorizationId": "28c52f98-7541-42a2-9ca0-b86ceac47692,
  "aud": "s6BhdRkqt3",
  "nonce": "n-0S6_WzA2Mj",
  "exp": 1311281970,
  --ALTERNATIVE 1: AUTHORIZATION CODE FLOW---
  "s_hash": "76sa5dd",
  "c_hash": "asd097d"
  --ALTERNATIVE 2: TOKEN ENDPOINT---
  "rt_hash": "wFsWWP49hHo4sXwwVSHX-A",
  "at_hash": "bSUTLJiqrooANhJJ2X7pPw"
  }
.
{
<<signature>>
}
FieldTypeLegend
issstring/URIIssuer of the token.
iattimestampToken issuance time as seconds since 1970-01-01T0:0:0Z.
substringThe authorizationId to which the ID token relates to.
acrstringAuthentication Context Class Reference, i.e. field that describes the level of authentication achieved. Currently, only urn:openbanking:psd2:sca is supported.
authorizationIdstringThe authorizationId to which the ID token relates to.
audstring/client IDIntended audience of the ID token. WIll contain the Client Id of the Client Application.
noncestringA value originally provided by the TPP in the request, used for protecting against replay attacks.
exptimestampTime of expiry, i.e. the time after which the token is no longer valid.
s_hashstring, case-sensitiveReturned with authorization code. State hash value: base64Url encoding of the left-most half of the hash of the octets of the ASCII representation of the state value, where the hash algorithm used is the hash algorithm used in the algHeader Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, hash the code value with SHA-256, then take the left-most 256 bits and base64url encode them. The s_hashvalue is a case sensitive string.
c_hashstring, case-sensitiveReturned with authorization code. Code hash value: base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the code value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE Header.
rt_hashstringReturned from token endpoint. Refresh token hash value: base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the refresh_token value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, hash the refresh_token value with SHA-256, then take the left-most 128 bits and base64url encode them. The rt_hash value is a case sensitive string.
at_hashstringReturned from token endpoint. Access token hash value: base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the access_token value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, hash the access_token value with SHA-256, then take the left-most 128 bits and base64url encode them. The at_hash value is a case sensitive string.

The ID token must be validated following the OIDC spec. In addition, the TPP should do the following:

Alternative 1: ID token received in redirection URI

  • Validate the authorizationId against that received earlier
  • Validate the signature of the ID token
  • Validate s_hash (state hash)
  • Validate c_hash (code hash).

Alternative 2: ID token in access token response body

  • Validate the authorizationId against that received earlier
  • Validate the signature of the ID token
  • Validate rt_hash (refresh token validation)
  • Validate at_hash (access token validation).

The public key for validating ID token signatures is available at https://authorize.psd2-sandbox.op.fi/oauth/jwks/PSD2_OIDC.

5. TPP request an access token for the user

At this point the TPP is in possession of an authorization code. Thus must now be exchanged for an access token.

POST /oauth/token HTTPS/1.1
Host: mtls-apis.psd2-sandbox.op.fi/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=<code>&redirect_uri=<redirect_uri>&client_id=<APP_CLIENT_ID>&client_secret=<APP_CLIENT_SECRET>

cURL:

curl -vk --key key.pem --cert client.crt https://mtls-apis.psd2-sandbox.op.fi/oauth/token -d 'grant_type=authorization_code&client_id=VxEjWZCQ98ngGN0imOOd&client_secret=WOf1v30GGeJq7MZL6FXs&code=wpj94iuZRzFvmHHpIFba7b1QiZweXm&redirect_uri=https%3A%2F%2Flocalhost%3A8181'

A successful request will yield a response with the following content:

{
  "access_token":"TfOJ0yOywdg8cwthvKUE",
  "refresh_token":"TYiOBpLHMWSgXsTg0SJ7TlqVMhSsWnJ6vxL6dUk9",
  "id_token": "eyJraWQiOiI2ZzNRRU9SY0hNOVdVZTZlcVowZi1wdjNEUEV1Y2NHVTRUdDFVMHo0cFIwIiwiYWxnIjoiUlMyNTYifQ.eyJydF9oYXNoIjoiMDJaODNSbVFqbTYtQWJEYXRkLTBIdyIsIm5vbmNlIjoiMTIzNCIsImF1dGhvcml6YXRpb25JZCI6ImMxMzgxMDM1LTU2OGMtNGUzYS04OGM5LTQ1Mzk2ZTU2OTBlMiIsImlhdCI6MTU1MjQ3NDkyNSwiaXNzIjoiaHR0cHM6Ly9hdXRob3JpemUua2VoaS5vcC1wYWx2ZWx1dC5uZXQiLCJhdF9oYXNoIjoidmU0MnhDS2ExOTJ4MnplV00zaE5wdyIsInN1YiI6InVpZDUwMDczNTA5IiwiZXhwIjoxNTUyNDc4MjI1LCJhY3IiOiJ1cm46b3BlbmJhbmtpbmc6cHNkMjpzY2EiLCJhdWQiOiJPUF90ZXN0X2NsaWVudCJ9.OMxF_fyVTS0af_so533ODdbbVfw3DOHZmzR-Yosl1N8df6zNZ6FIvFt8PjhcNBG2cMUyAIvfwpt9hKcMt0zwtccyVJzUPPL-ArVuiMIysjJiWo5pp_0AyRaeB-ThqlzMG0yBMibILUobOaZJqNZb1fodE6u_eHEjD2kUEHuxCglIGs8Zu47rhXnSY044YUsukhY1XgThmDPXd0T4vZsmfh_1dDGgp5TX6disKXh1ltspukqnimtI8uf9RrihE5QHu83slodFepHXzYBdlr63wOB15QLvVJXJtZgoM9QUmsAlh9efCEvgGGMcVtB04UEfkGWRQye9sQXEGMHW_S7bsA",
  "scope":"openid payments",
  "token_type":"bearer",
  "expires_in":3599,
}
FieldtypeLegend
access_tokenstringThis is the access token. Use as a bearer token in the Authorization header to access protected resources.
refresh_tokenstringCan be used to request additional access tokens. Valid for up to 45 days (Less if the authorization granted is shorter).
id_tokenstringA signed JWT containing information about the authorization. See ID tokens for a full description on the contents and recommended validation procedures for ID tokens.
scopestringA space-separated list of scope names describing the services to which the token grants authorization.
token_typestringToken type. Always has the value "bearer".
expires_innumberThe period for which the token is valid, in seconds.

Refreshing tokens

Refresh tokens may be used for requesting additional access tokens, as well as for renewing refresh tokens. They remain valid for a maximum of 45 days.

Regardless of the length of the authorization, the end user must re-authorize the Client Application if the application fails to renew the refresh token within 45 days.

Refresh requests have the following structure:

POST /oauth/token HTTPS/1.1
Host: mtls-apis.psd2-sandbox.op.fi/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=<refresh_token>&client_id=<APP_CLIENT_ID>&client_secret=<APP_CLIENT_SECRET>

The response to this request is similar to that of a normal token request.

6. TPP submits the payment(s)

Armed with the access token and payment IDs, the Client Application submits each individual payment with a PUT call to

/sepa-payments/{paymentId}/submissions/{submissionId}.

Here, submissionId is a value generated by the TPP for the submission.

7. TPP invokes the /payments endpoint for querying payment status.

Finally, the status of each payment can be polled by paymentId.