Non-repudiation API Authentication

Non-repudiation API authentication is a type of authentication that allows PaymentsOS to verify that the request originated from you and has not been tampered with during transmission.

In non-repudiation API authentication, the authenticity of the request is verified using a digital signature based on the use of a private and public key pair: The private key is a secret key that is known only to you. You use this key to sign the requests you send to the Payments API. PaymentsOS then uses the public key to verify the signature you generated using the corresponding private key, thus ensuring the authenticity and integrity of the data sent in the request.

To use non-repudiation API authentication with your requests, you need to follow three steps:

  1. Create an asymmetric key pair (that is, a public and private key).

  2. Configure your Business Unit(s) to use non-repudiation API authentication.

  3. Sign the payment requests you send to the Payments API.

Let’s take a look at each of those steps in detail.

Step 1: Create an Asymmetric Key Pair

An asymmetric key pair is pair of cryptographic keys that consists of a public key and a private key. To generate the keys, you can choose any cryptographic software library or tool that supports public-key cryptography. Here’s an example of how you might generate a new RSA key pair using OpenSSL in a Linux command line (note that currently only the RSA algorithm is supported):

openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem

The public key will be used by PaymentsOS to verify your signed request (you will configure your Business Units to use the public key in the next step). Beware that the private key should be kept confidential and not shared with anyone else (including PayU).

Step 2: Configure your Business Unit(s) to use Non-repudiation API Authentication

By default, PaymentsOS authenticates your API requests using basic authentication with the HUB API keys defined in your Business Unit’s configuration settings. To use non-repudiation API authentication, you must update your Business Unit’s configuration settings and indicate that you want to use non-repudiation API authentication for transactions handled by that Business Unit.

Configuring your Business Unit(s) to use non-repudiation API authentication is easy. Just follow the steps below:

  1. Invoke the Create a Business Unit or Update a Business Unit API and pass a value of non_repudiation in the authentication_type field:

     {
       "id": "com.bussinessunit-1.mycomp",
       "default_processor": "f0a97e7c-efc4-4859-aca3-6bc7e30a6f7c",
       "description": "My Business Unit",
       "authentication_type": "non_repudiation"
     }
    
  1. Base64 encode the public key you generated in step 1 and then call the Assign a Public Key to the Account API to assign the public key to your PaymentsOS account. In the request body pass a descriptive name for your key, as well as the key itself:
  {
    "name": "My Base64 Encoded Public Key",
    "key":"LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KICAgICAgICAgICAg...",
    "type": "public_key"
  }

The response of this call will include a kid (key ID) representing your public key. The response will look something like this:

  {
    "name": "My Base64 Encoded Public Key",
    "kid": "03b941e3-3615-47a5-a046-766d5a4544e3",
    "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KICAgICAgICAgICAg...",
    "created_timestamp": "2023-08-22"
  }
  1. With the kid at hand, invoke the Assign a Public Key to a Business Unit API to assign the public key to your Business Unit(s). Notice that you pass the kid as a path parameter, while you pass the Business Units to which you want to assign the public key in the app_ids array in the request body:

    {
        "app_ids": [
          "com.bussinessunit-1.mycomp",
          "com.bussinessunit-2.mycomp"
        ]
    }
    

Step 3: Sign the payment requests you send to the Payments API

Steps 1 and 2 are one-off steps, as you need to do them only once. Signing the payment requests, however, is a recurring step: you need to sign each request sent to the Payments API.

Signing the payment requests is a two-step process:

  1. Construct a JSON Web Token (JWT).

  2. Pass the JWT in the header of the Payments API request.

Constructing a JSON Web Token (JWT)

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. If you’re not familiar with JWT’s, then you can read more on the jwt.io introduction site.

When constructing the JWT, you first include a header and payload and then sign the JWT with your private key. Make sure the header and payload adhere the specifications listed below (or if you’re already familiar with the specifications, just head right over to a code sample that shows you how to construct and sign the JWT).

Header

Include the attributes listed in the table below.

Attribute Specifications
alg The algorithm used for signing the JWT. Should be one of the following: RS256, RS384, RS512, PS256, PS384, PS512
typ The type of the JWT. Must be JWT
kid The ID representing your public key. This ID was returned to you when you called the Assign a Public Key to the Account API to assign the public key to your account.

Payload

Include the attributes listed in the table below.

Attribute Specifications
iat The time at wich the JWT was issued, in Epoch format.
exp The expiration time of the JWT, in Epoch format. It represents the date and time after which the token should no longer be considered valid. A valid expiration date is within range of 0-20 minutes after the issue time.
hashed_request A sha512 hashed value that includes the URI of the payment request you're invoking and the entire request body. Format: URI + '.' + the request body.
Important notes:
  • Make sure to pass exactly the same content you pass in the request body and URI to the function generating the sha512 hashed value. Any difference between the request body and its hashed value will result in an error when invoking a request. For instance, if you use a Postman client to test your implementation, then you should pass the request body exactly 'as is' to the sha512 function. Likewise, if your request body is JSON stringified (either by you or your http client) then you need to JSON stringify the body passed to the sha512 function as well.
  • Do not include any query params in the URI.

Here’s an example of creating the hashed_request in Node.js:

const requestUri = "/payments"
const requestBody = {
  "amount": 0,
  "currency": "USD"
  ....
}


const {createHash} = require('crypto');
const bodyToHash = requestBody ? JSON.stringify(requestBody) : '';
const hashed_request = createHash('sha512').update(${requestUri}.${bodyToHash}).digest('HEX');

Signing the JWT

With the header and payload in place, sign the JWT with your private key like so:

const JWT = require('jsonwebtoken');
JWT.sign(payload, merchantPrivateKey, { header: headers });

Piecing it all together: code sample for constructing and signing a JWT

Here’s a real-life example in Node.js that shows you how to construct and sign a JWT.

const JWT = require('jsonwebtoken');
const {createHash} = require('crypto');

const requestBody = {
  "amount": 0,
  "currency": "USD"
  ....
}

// Header
const kid = "03b941e3-3615-47a5-a046-766d5a4544e3."
const headers = { alg:'RS256', typ: 'JWT', kid};

// Payload
const iat = Math.floor(new Date().getTime() / 1000) - 1;
const exp = iat + 1200 - 1  // 1200 seconds = 20 minutes (max allowed range)
const requestUri = "/payments"  // The URI path of the payment request you're invoking. Do NOT include query params.
const bodyToHash = requestBody ? JSON.stringify(requestBody) : '';
const hashed_request = createHash('sha512').update(`${requestUri}.${bodyToHash}`).digest('HEX');
const payload = { iat, exp, hashed_request };

//Sign the JWT 
const merchantPrivateKey = "MIIJJwIBAAKCAgEArpQSiF6C5dDqAWIfR1fqkIeJb5YfoXWTx981KK1uF0YGNlWX...." // Your private key (this is an example; it is not recommended to store your private key in your source code)
JWT.sign(payload, merchantPrivateKey, { header: headers });

The signature returned by JWT.sign() will look something like this:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleSBpZCByZWNpdmVkIGZyb20gaHViLCBraWQgbXVzdCBiZSBhc3NvY2lhdGVkIHdpdGggdGhlIHNwZWNpZmljIGFwcCJ9.eyJpYXQiOjE2Nzg3ODI3MDAsImV4cCI6MTY3ODc4Mzg5OSwiaGFzaGVkUmVxdWVzdCI6IjM4NzRkOWIyY2U5MDljNWI3MzRmZDViOWZiZjI2OGIyNzliOTE5ZjkzZTcxZTM0ZjJmNDdmOWYzYjgxMjZhNDA2Y2Y0MjFlYmE3NTNjNTA3NDcwZTRlZmM2OTM2ZmFlM2IzN2Y1MWVhNGI1NTZmZGIxNDg2MzI4YzM0MmRmNTM0In0.Z8GrT2vYZDLQ18BjGPBJIpGAhC3DZyTmj9DByAmY-MXdcbxb_Y2erNgDlTuljAj3yFqIRcglYmFuvbPHTbFqxSguh7ayoNA0Lqw-nrdpE7LfqhetDivPI6QxrfNe8UTuaiPM-o4pYtleX1i8LNebOEEpBgd1lT...

Passing the JWT in the Header of the Payments API Requests

Now that you have the JWT, pass it in the authorization field in the request header of the Payments API requests you invoke, like so:

x-payments-os-env: test
api-version: 1.3.0
app-id: com.mycomp.docapp
authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9....
idempotency-key: AGJ8FJLkGHIpHUTK

Rotating your Private and Public Key Pair

While PayU does not enforce key rotation, rotating your private and public key pair is a common security practice and we strongly recommend you rotate your keys periodically.

Rotating the key pair involves generating a new private-public key pair and replacing the existing key pair. After generating the new pair, simply assign the new public key to your account and to your business units. Note that the public key you wish to retire can remain assigned to your Business Units, pending the transition to the new public key; in this manner transactions initiated during the transition period will still be authenticated using your existing private-public key pair. After you complete your implementation for transitioning to the new key pair, simply remove the old public key from your Business Units.

Supported Payments API Endpoints

While you can use non-repudiation API authentication with most Payments API endpoints, you cannot use it with the Create a Token and Retrieve a Token API (you can only authenticate those request using using basic authentication with the HUB API keys). Likewise, you can only validate the integrity of Webhook data using basic authentication with the HUB API keys.

Last modified April 13, 2023