End to End Encryption

If you are SAQ D compliant, you can use our End-to-End Encryption (E2EE) service to encrypt a card holder's data. End-to-end encryption introduces an additional layer of security by safeguarding a user's card data during transit, from the moment you collect the card data until it reaches its destination.

PaymentsOS uses the Javascript Object Signing and Encryption (JOSE) framework for managing the encryption process. JOSE is a framework intended to provide a method to securely transfer claims (such as authorization information) between parties.

Provider Support

The providers you transact against must be able to process encrypted card data. Refer to the relevant provider integration guide, to determine whether a provider supports this functionality.

Flow Overview

The image below illustrates a flow in which you encrypt a user's card data.

Payment Flow

At a glance, the flow is identical to the payments flow in which you use our tokenization service when collecting a user's card information. Looking at the flow details, however, you will notice three differences:

  • Step 2: Collect the user's card data. Since you're SAQ D compliant, it's up to you to choose the best way of doing this.

  • Step 3: Use our encryption service to encrypt the user's card data (this includes both the card number and the CVV code). To do so, you will need to generate a RSA JSON Web Key (JWK) and use its public properties to encrypt the card data. For more information, see Encrypting a User's Card Information.

  • Step 4: Send us the encrypted data in the Create Authorization or Create Charge request. We then send the encrypted data on to the target provider.

Configuring Provider Settings

Notice how in step 2, we decrypt the card data and then re-encrypt it before sending it on. This is needed, else the target provider will not be able to decipher the card data in order to process it. To decrypt the data, we use the private properties of the RSA JSON Web Key (JWK) you generate. To re-encrypt the data, we will use a public key that you must generate with your provider. After generating this public key, make sure to configure it in your PaymentsOS provider configuration settings before proceeding with the steps below.

Encrypting a User's Card Information

The first step in encrypting a user's card information, is to generate a RSA JSON Web Key (JWK) using the Create a new Public and Private Key Pair API request. This API generates a key pair with both public and private properties. The public properties of the JWK are included in a jwk object returned by the API request; this object represents the public key. The private properties of the JWK (representing the private key) remain secured in PaymentsOS and are never exported. Here's a sample response, including the jwk object:

    "jwk": {
        "e": "AQAB",
        "n": "2UK9mj3WTqA1OrzfIC7DMweAKPhe6VENMOLgRO5a2kOk_t-3UYQobJ2usKGozdPAuKjqGnQEgOaAU3eQXLItRodaeCry-ReUlAdtbZqniYJqhBLqqhhmgoFunaus_QQ0s7RfbYAnQkfSzjuh-zpkPC4x1vZOuVyeFkhNc_Ukg-AG1ZR8VgOF7Sa0PH6vYVX19upc_5DmizcQkr1Y90EoXPANTAlIzCJHoQSYY1nQvn5J3aj3ABwmUlXXZpxGGom7WZUd6uq6JOibNyzhDSgxkdxOJhIW8Fzqhc4HzmuV_-axJZGgxc1T_2ael_weyACV6R4OXkkYxC0piQq_L7X3wH5ZoHcn4QSrq58nmsly7S4dEFQWdqknMS1Ui6AMOHLnbPZiS6m9vU-Zd1Bac0D1XCYWKTrumrjg3FMnq5HAgs848_ptiLPolTui5RUG5SeN2TAHCtnQzywZOw1FRK8TN-lyRN69QMb_-8FMJK3aHQeqLbDT6f0wM6FxMWTKO29VARxCc8lzD4Qc8m8J-UytgEdeLrdbkb0NAWSxObNHYGiJ3OHBXfdXPWJWF4DG43uAwSRpebv1NDkZNRrKYd_4SWTL2FFCxgaTx8MWGyyv7GZi8wjyS-0X9aJdPCYJB88wwEeDikNN96Gc8H3Zd-EHCRkEgawWSVjmMutFIh7uZls",
        "kty": "RSA",
        "kid": "AbBRI3RkjcMyg7I1yTle3RtOFDgAH0f-Q1NZgzSV6ac",
        "use": "enc"
    "created": "string",
    "version": "v1",
    "key_type": "RSA",
    "name": "string",
    "protected_headers": {
        "kid": "test_1",
        "enc": "A256GCM"

Let's take a moment to dissect part of the response data. The first attribute to notice is key_type. This represents the encryption algorithm that will be used to encrypt the card data. You specify this algorithm by passing it as a type in the request body of the Create a new Public and Private Key Pair API request. The types you can specify are RSA (resulting in a key size of 4096 bit) or EC (resulting in a key size of p-256).

Another attribute to notice is the protected_headers object:

 "protected_headers": {
            "kid": "test_1",
            "enc": "A256GCM"

The protected_headers is a JSON object that includes the header elements that need to be integrity protected by the signing algorithm. When encrypting a user's card data, we expect the protected_headers object to have four attributes. Two of those are returned in the response of the Create a new Public and Private Key Pair API request (as shown in the example above):

  • kid: Allows us to identify the private key to use in order to decrypt the data before passing it on to the target provider.

  • enc: Identifies the content encryption algorithm used to perform authenticated encryption on the card data to produce the encrypted result.

The other two attributes must hold the create date and expiration date of the JWE, in order to prevent replay attacks. Since the values are dynamic, you must add them to protected_headers yourself. Those attributes are:

  • iat: Short for "issued at". Holds the created date.

  • exp: Short for "expiration". Holds the expiration date.

We'll look at an example in a moment, just make sure to keep the following guidelines in the back of your mind:

  • The created date must be in the past; the expiration date must be in the future.

  • The difference between the created and expiration data can be maximum 10 minutes.

Now let's take a look at a complete example of how to go about encrypting the card data.

const {JWE} = require('jose');
const TOKEN_TTL_MIN = 10; // Used to compute the expiration date that will be added to protected_headers 
const CREDIT_CARD_DATA = JSON.stringify({credit_card_number: '4111111111111111', cvv: '090'});
const JWK_KEY = {
    "jwk": {
        "e": "AQAB",
        "n": "1_2coL0zqSsrdJEAxV_yDWjOyl2ezApltrhXa2axpLdzCLMwqaITlj04x1d6BnhpjuM0fs-VtWTaL745-ev2ZtoxHYqW-NRutvTf5Xa8D6RM1yFIeLn_Q-vSImw9Psed6GuR8xtLBycP37rB1-5qOyztJXihmDZQKoD90PgdBTymajpo1-2-JHxHIKiRAJoSlHmq3i9C-vuzXKaxsDAqnLW7eov5qbSvcHJ_Ewan1Q3kyHapBFX1GEWJM5IE4dEAOCJUyx13tyEYW3H0LMJjNSmtXNXLMkQ9hTgljHEvBWCCZkwW7YMZScis4ceXI8fYWztMDNwi4anYeESBS7a1bVFMyjTxvPqQMczgTtowYSpU6_1_PrOVfMZ6QZr8stXWkpcTXwGa3toEt2bgYFWEgPlPO8RFeLQ-xkoiwxvaBuX_sNcHQKc4I1DAOfHSkOdmEVQO8-Fjx6xvHVw3zSrnN462f1jKHwYw27tzasFbdpDhDVdfEEYj-_yqslmQY278fh2wSIGa4DzO9SttSi9055utfteMtJJ9txt6tNtyLL3gj8_8v2tsd-hv-Y-IhADJGiOlqxCE_sfpEV5RrD6iGxfBjX1gb3P6kX4LtEtFssoL9-zLVZQiMoPlhWyKevVf0ISlRWwYxunejSOLtRRhkNxKgHp9ARehH3SWv2xaI5M",
        "kty": "RSA",
        "kid": "test_1",
        "use": "enc"
    "created": 1594571540648,
    "version": 1,
    "key_type": "RSA",
    "name": "test",
    "protected_headers": {
        "kid": "test_1",
        "enc": "A256GCM"

// Creating the date object so we can add it to protected_headers
const createdDate = new Date(); 
const expiredDate = new Date(createdDate);
expiredDate.setMinutes(expiredDate.getMinutes() + TOKEN_TTL_MIN);
const date = {
    iat: createdDate.getTime(),
    exp: expiredDate.getTime(),
// Encrypting the card data. Making sure to add in the date object as well. 
const ciphertext = JWE.encrypt(CREDIT_CARD_DATA, JWK_KEY.jwk, Object.assign(JWK_KEY.protected_headers, date));

Downloading a .pem File

If desired, you can download a Key PEM File. You can choose from one of the following formats:

  • As a file holding the public key properties. This is the default.

  • As a certificate. The certificate is self-signed by PayU as the certificate authority. You can use this certificate to validate that the certificate you downloaded is self-signed by PayU.


If you choose to download the PEM file as a file holding the public key properties, you need to beware that it does not include the protectedheaders.kid and protectedheaders.enc fields. However, you do need them when encrypting the card data. So you either need to fetch them separately from the response of the Retrieve a Key by Version request, or define them yourself using the following formats:

  • kid: {name}_{version}
  • enc: A256GCM

Managing Key Pairs

There are some best practices you should follow when managing your key pairs.

Key Pair Names

First, there is the key pair name which you pass when creating a new public and private key pair. This name is commonly used to reflect the name of your application, so make sure to provide a meaningful name. If you use multiple applications, the name will help you identify the key pairs you want to use.

Maximum Number of Key Pairs

You can create up to 10 key pairs.

Key Pair Versions

You can create multiple versions of each key pair. Doing so is recommended, since it allows you to rotate your JWKs thereby providing an additional level of protection.

To create a new version of an existing key pair, simply specify an existing key pair name when creating a new public and private key pair. You can then use the Retrieve all Key Versions by Key Name request to retrieve all versions for a specific key pair name and use the latest version. Alternatively, use the Retrieve a Key by Version request to retrieve a specific key pair version.

Maximum Number of Key Pair Versions

You can create maximum 5 key pair versions for the same key. If you're running out of versions, you can always delete a key pair version before creating a new one.

results matching ""

    No results matching ""