---
page_title: JWE/JWT Encryption Guide
product: API Reference
page_source: https://juspay.io/in/docs/api-reference/docs/jwt-encryption/jwejwt-encryption-guide
llms_txt: https://juspay.io/in/docs/llms.txt
product_llms_txt: https://juspay.io/in/docs/api-reference/llms.txt
---


# JWT Encryption Guide



Juspay uses JWS to sign request payloads, and JWE to encrypt the signed request. These are created by using 2 sets of Private Key & Public Keys issued for merchants and Juspay. Merchants will use their private key to sign the API request payload and then encrypt the signed API request with Juspay’s public key. Juspay will use its private key to decrypt the message and Merchant’s public key to verify the signature.


### Step 1.1. Keys Generation


How to Generate 2 sets of Public Private Key required for JWT Authentication:

1. Login to Juspay Portal (Sandbox: [https://sandbox.portal.juspay.in](https://sandbox.portal.juspay.in); Production: [https://portal.juspay.in](https://portal.juspay.in)[)](https://portal.juspay.in)2)
2. Navigate to Payments » Settings » Security module
3. Scroll Down to JWT Keys section
4. Click on Upload New JWT
5. If merchants do not have a public-private key pair generated,click on option 1 - **I don’t have the JWT Keys, I want to auto generate the keys** .
   
   [Video](https://dth95m2xtyv8v.cloudfront.net/tesseract/assets/api-reference/Filter%20Check%20(17).mp4)
   
   
   
   * Juspay public key and merchant’s private key will be auto downloaded as a zip file in the merchant's default browser download folder.
   * **Juspay will never store a merchant's private key even though the keys are auto generated.**
6. If a merchant has already generated their set of public-private keys, they can click on option 2 - **I have the JWT Keys already, I want to manually upload the Public Key.** 
   
   [Video](https://dth95m2xtyv8v.cloudfront.net/tesseract/assets/api-reference/Filter%20Check%20(19).mp4)
   
   
   
   * Merchants will have to upload their private key in the upload file section.
   * Post upload, Juspay’s public Key will be auto downloaded in the merchant's default browser download folder.
7. At the end of this step, merchants will have 2 keys - **Merchant’s private key**  and **Juspay’s public key**  which will be used for signing and encryption.
8. Kindly keep a note of **Key Uuid**  for the JWT keys to be used for encryption and signing. This will be displayed on dashboard post generation.




## JWT Encryption




### Step 1.1. Steps to construct a JWT Payload


Every request sent to Juspay via JWT authentication must be signed and encrypted.

1. Create the JWS Header. The algorithm used to sign the message body is RSA-OAEP.
2. JWS key id is the Key uuid(Dashboard Key UUID) for JWT keys.
3. Sign the payload message by Merchant's private key and build the JWS serialization string.
4. Create the JWE Header. The algorithm used to encrypt the message body is A256GCM while the algorithm used to encrypt the encryption key is RSA_OAEP_256.
5. Encrypt the JWS serialization string by Juspay’s public certificate and build the JWE serialization string.




## JWT Decryption




### Steps to Decrypt a JWT Payload Substep


1. Parse the Juspay Signed and Encrypted Message to Encrypted JWE Object.
2. Decrypt the Encrypted JWE Object by Merchant's private key and get the JWE payload from Encrypted JWE Object.
3. Parse the JWE payload to Signed JWS Object.
4. Verify the signature of the Signed JWS Object using Juspay’s public certificate
5. Get the Signed Message from the decrypted JWE Object.




## Encryption Logic




#### GET - Request Code Snippet:

```get - request
const crypto = require("crypto");
const fs = require("fs");

(async () => {
  const claims     = "{ \"order_id\" : \"test_2340\", \"options.add_full_gateway_response\" : \"true\"}"; // add Stringify JSON Request Here
  const keyId      = "key_XXXXXXXXXXXXXX"; // add Key ID Here
  const publicKey  = readPublicKey("-----BEGIN PUBLIC KEY-----********************-----END PUBLIC KEY-----"); // add Public Key Here
  const privateKey = readPrivateKey("-----BEGIN RSA PRIVATE KEY-----*********-----END RSA PRIVATE KEY-----"); // add Private Key Here

  const encryptedPayload = jwtEncrypt(claims, keyId, publicKey, privateKey);
  const serializedApiRequest = {
    JWT: encryptedPayload,
  };
  console.log(JSON.stringify(serializedApiRequest));
})();

//  ENCRYPT
/**
 * Encrypts the given data.
 * @param {string} data - Data to encrypt.
 * @param {string} keyId - Key ID.
 * @param {crypto.KeyObject} publicKey - Public key.
 * @returns {string} Encrypted token.
 * @throws {Error} If encryption fails.
 */
function encrypt(data, keyId, publicKey) {
  const headers = {
    alg: "RSA-OAEP",
    enc: "A256GCM",
    cty: "JWT",
    kid: keyId,
  };
  const aad = encodeBase64Url(JSON.stringify(headers));
  const cek = crypto.randomBytes(32);
  const cekOptions = {
    key: publicKey,
    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
  };
  const encryptedKey = encodeBase64UrlFromBuffer(
    crypto.publicEncrypt(cekOptions, cek)
  );
  const iv = crypto.randomBytes(12);
  const cipher = crypto.createCipheriv("aes-256-gcm", cek, iv);
  cipher.setAutoPadding(false);
  cipher.setAAD(Buffer.from(aad));
  const cipherOutput = Buffer.concat([cipher.update(data), cipher.final()]);
  const authTag = cipher.getAuthTag();
  const ivText = encodeBase64UrlFromBuffer(iv);
  const cipherText = encodeBase64UrlFromBuffer(cipherOutput);
  const tag = encodeBase64UrlFromBuffer(authTag);
  return `${aad}.${encryptedKey}.${ivText}.${cipherText}.${tag}`;
}

/**
 * Signs the given claims.
 * @param {string} claims - Claims to sign.
 * @param {string} keyId - Key ID.
 * @param {crypto.KeyObject} privateKey - Private key.
 * @returns {string} Signed string.
 * @throws {Error} If signing fails.
 */
function sign(claims, keyId, privateKey) {
  const signer = crypto.createSign('RSA-SHA256');
  const signatureHeader = `{"alg":"RS256","kid":"${keyId}"}`;
  const header = encodeBase64Url(signatureHeader);
  const payload = encodeBase64Url(claims);
  const data = `${header}.${payload}`;
  signer.update(data);
  const signedBuffer = signer.sign(privateKey);
  const signed = encodeBase64UrlFromBuffer(signedBuffer);
  return `${header}.${payload}.${signed}`;
}

/**
 * Encrypts the given data and returns a jwe token.
 * @param {string} data - Data to encrypt.
 * @param {string} keyId - Key ID.
 * @param {crypto.KeyObject} publicKey - Public key.
 * @param {crypto.KeyObject} privateKey - Private key.
 * @returns {string} Encrypted token.
 */
function jwtEncrypt(data, keyId, publicKey, privateKey) {
  const signed = sign(data, keyId, privateKey);
  return encrypt(signed, keyId, publicKey);
}

// UTILS
/**
 * Encodes a string into Base64 URL format.
 * @param {string} original - The original string to encode.
 * @returns {string} The Base64 URL encoded string.
 */
function encodeBase64Url(original) {
  return encodeBase64UrlFromBuffer(Buffer.from(original));
}

/**
 * Encodes a buffer into Base64 URL format.
 * @param {Buffer} buffer - The buffer to encode.
 * @returns {string} The Base64 URL encoded string.
 */
function encodeBase64UrlFromBuffer(buffer) {
  return buffer
    .toString('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '');
}
/**
 * Reads a public key from a string.
 * @param {string} [keyString] - Public key string in PEM format.
 * @returns {crypto.KeyObject} Public key object.
 * @throws {Error} If the key string is undefined or invalid.
 */
function readPublicKey(keyString) {
  if (keyString === undefined) {
    throw new Error("IllegalPublicKey");
  }
  return crypto.createPublicKey({
    key: keyString,
    format: "pem",
  });
}

/**
 * Reads a private key from a string.
 * @param {string} [keyString] - Private key string in PEM format.
 * @returns {crypto.KeyObject} Private key object.
 * @throws {Error} If the key string is undefined or invalid.
 */
function readPrivateKey(keyString) {
  if (keyString === undefined) {
    throw new Error("IllegalPrivateKey");
  }
  return crypto.createPrivateKey({
    key: keyString,
    format: "pem",
  });
}

```

#### POST - Request Code Snippet:

```post - request
const crypto = require("crypto");

(async () => {
  const claims     = "{ \"order_id\" : \"test_2340\", \"options.add_full_gateway_response\" : \"true\"}"; // add Stringify JSON Request Here
  const keyId      = "key_XXXXXXXXXXXXXXXXX"; // add Key ID Here
  const publicKey  = readPublicKey("-----BEGIN PUBLIC KEY-----********************-----END PUBLIC KEY-----"); // add Public Key Here
  const privateKey = readPrivateKey("-----BEGIN RSA PRIVATE KEY-----*************-----END RSA PRIVATE KEY-----"); // add Private Key Here

  const encryptedPayload = jwtEncrypt(claims, keyId, publicKey, privateKey);
  const serializedApiRequest = {
    JWT: encryptedPayload,
  };
  console.log(JSON.stringify(serializedApiRequest));
})();


//  ENCRYPT
/**
 * Encrypts the given data.
 * @param {string} data - Data to encrypt.
 * @param {string} keyId - Key ID.
 * @param {crypto.KeyObject} publicKey - Public key.
 * @returns {string} Encrypted token.
 * @throws {Error} If encryption fails.
 */
function encrypt(data, keyId, publicKey) {
  const headers = {
    alg: "RSA-OAEP",
    enc: "A256GCM",
    cty: "JWT",
    kid: keyId,
  };
  const aad = encodeBase64Url(JSON.stringify(headers));
  const cek = crypto.randomBytes(32);
  const cekOptions = {
    key: publicKey,
    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
  };
  const encryptedKey = encodeBase64UrlFromBuffer(
    crypto.publicEncrypt(cekOptions, cek)
  );
  const iv = crypto.randomBytes(12);
  const cipher = crypto.createCipheriv("aes-256-gcm", cek, iv);
  cipher.setAutoPadding(false);
  cipher.setAAD(Buffer.from(aad));
  const cipherOutput = Buffer.concat([cipher.update(data), cipher.final()]);
  const authTag = cipher.getAuthTag();
  const ivText = encodeBase64UrlFromBuffer(iv);
  const cipherText = encodeBase64UrlFromBuffer(cipherOutput);
  const tag = encodeBase64UrlFromBuffer(authTag);
  return `${aad}.${encryptedKey}.${ivText}.${cipherText}.${tag}`;
}

/**
 * Signs the given claims.
 * @param {string} claims - Claims to sign.
 * @param {string} keyId - Key ID.
 * @param {crypto.KeyObject} privateKey - Private key.
 * @returns {string} Signed string.
 * @throws {Error} If signing fails.
 */
function sign(claims, keyId, privateKey) {
  const signer = crypto.createSign('RSA-SHA256');
  const signatureHeader = `{"alg":"RS256","kid":"${keyId}"}`;
  const header = encodeBase64Url(signatureHeader);
  const payload = encodeBase64Url(claims);
  const data = `${header}.${payload}`;
  signer.update(data);
  const signedBuffer = signer.sign(privateKey);
  const signed = encodeBase64UrlFromBuffer(signedBuffer);
  return `${header}.${payload}.${signed}`;
}

/**
 * Encrypts the given data and returns a jwe token.
 * @param {string} data - Data to encrypt.
 * @param {string} keyId - Key ID.
 * @param {crypto.KeyObject} publicKey - Public key.
 * @param {crypto.KeyObject} privateKey - Private key.
 * @returns {string} Encrypted token.
 */
function jwtEncrypt(data, keyId, publicKey, privateKey) {
  const signed = sign(data, keyId, privateKey);
  return encrypt(signed, keyId, publicKey);
}

// UTILS

/**
 * Encodes a string into Base64 URL format.
 * @param {string} original - The original string to encode.
 * @returns {string} The Base64 URL encoded string.
 */
function encodeBase64Url(original) {
  return encodeBase64UrlFromBuffer(Buffer.from(original));
}

/**
 * Encodes a buffer into Base64 URL format.
 * @param {Buffer} buffer - The buffer to encode.
 * @returns {string} The Base64 URL encoded string.
 */
function encodeBase64UrlFromBuffer(buffer) {
  return buffer
    .toString('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '');
}

/**
 * Reads a public key from a string.
 * @param {string} [keyString] - Public key string in PEM format.
 * @returns {crypto.KeyObject} Public key object.
 * @throws {Error} If the key string is undefined or invalid.
 */
function readPublicKey(keyString) {
  if (keyString === undefined) {
    throw new Error("IllegalPublicKey");
  }
  return crypto.createPublicKey({
    key: keyString,
    format: "pem",
  });
}

/**
 * Reads a private key from a string.
 * @param {string} [keyString] - Private key string in PEM format.
 * @returns {crypto.KeyObject} Private key object.
 * @throws {Error} If the key string is undefined or invalid.
 */
function readPrivateKey(keyString) {
  if (keyString === undefined) {
    throw new Error("IllegalPrivateKey");
  }
  return crypto.createPrivateKey({
    key: keyString,
    format: "pem",
  });
}

```



## Decryption Logic




#### Decrypt Response Code Snippet:

```decrypt response
const crypto = require("crypto");

(async () => {
  const apiResponse = {
    JWT : "xxxxxxxxxxxxxxxxxxxxxx" // Add Message here
  };
  const JWTToken   = apiResponse.JWT;
  const publicKey  = readPublicKey("-----BEGIN PUBLIC KEY-----********************-----END PUBLIC KEY-----"); // add Public Key Here
  const privateKey = readPrivateKey("-----BEGIN RSA PRIVATE KEY-----*********-----END RSA PRIVATE KEY-----"); // add Private Key Here

  const decryptedResponse = jwtDecrypt(JWTToken, publicKey, privateKey);
  console.log(decryptedResponse);
})();

// UTILS
/**
 * Decodes a Base64 URL encoded string.a
 * @param {string} base64url - The Base64 URL encoded string to decode.
 * @returns {string} The decoded string.
 */
function decodeBase64Url(base64url) {
  return decodeBase64UrlToBuffer(base64url).toString();
}

/**
 * Decodes a Base64 URL encoded string into a buffer.
 * @param {string} base64url - The Base64 URL encoded string to decode.
 * @returns {Buffer} The decoded buffer.
 */
function decodeBase64UrlToBuffer(base64url) {
  // Decode the Base64 URL encoded string to a Base64 encoded string
  const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
  return Buffer.from(base64, 'base64');
}

/**
 * Reads a public key from a string.
 * @param {string} [keyString] - Public key string in PEM format.
 * @returns {crypto.KeyObject} Public key object.
 * @throws {Error} If the key string is undefined or invalid.
 */
function readPublicKey(keyString) {
  if (keyString === undefined) {
    throw new Error("IllegalPublicKey");
  }
  return crypto.createPublicKey({
    key: keyString,
    format: "pem",
  });
}

/**
 * Reads a private key from a string.
 * @param {string} [keyString] - Private key string in PEM format.
 * @returns {crypto.KeyObject} Private key object.
 * @throws {Error} If the key string is undefined or invalid.
 */
function readPrivateKey(keyString) {
  if (keyString === undefined) {
    throw new Error("IllegalPrivateKey");
  }
  return crypto.createPrivateKey({
    key: keyString,
    format: "pem",
  });
}

// DECRYPT

/**
 * Decrypts the given encrypted token.
 * @param {string} cipher - Encrypted token or string representation of it.
 * @param {crypto.KeyObject} privateKey - Private key.
 * @returns {string} Decrypted data.
 * @throws {Error} If decryption fails.
 */
function decrypt(cipher, privateKey) {
  const cipherParts = cipher.split(".");
  if (cipherParts.length !== 5) {
    throw new Error("EncryptedCipherIllformed");
  }
  const data = {
    header: cipherParts[0],
    encryptedKey: cipherParts[1],
    iv: cipherParts[2],
    encryptedPayload: cipherParts[3],
    tag: cipherParts[4],
  };
  const aad = Buffer.from(data.header);
  const encryptedKey = decodeBase64UrlToBuffer(data.encryptedKey);
  const iv = decodeBase64UrlToBuffer(data.iv);
  const encryptedPayload = decodeBase64UrlToBuffer(data.encryptedPayload);
  const tag = decodeBase64UrlToBuffer(data.tag);
  const cekOptions = {
    key: privateKey,
    oaepHash: "sha256",
    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
  };
  const cek = crypto.privateDecrypt(cekOptions, encryptedKey);
  const decipher = crypto.createDecipheriv("aes-256-gcm", cek, iv);
  decipher.setAutoPadding(false);
  decipher.setAAD(aad);
  decipher.setAuthTag(tag);
  const cipherOutput = Buffer.concat([
    decipher.update(encryptedPayload),
    decipher.final(),
  ]);
  return decodeBase64Url(cipherOutput.toString("base64"));
}

/**
 * Verifies the given signed object or string.
 * @param {string} signed - Signed object or string representation of it.
 * @param {crypto.KeyObject} publicKey - Public key.
 * @returns {string} Verified payload.
 * @throws {Error} If verification fails.
 */
function verify(signed, publicKey) {
  const signedParts = signed.split(".");
  if (signedParts.length !== 3) {
    throw new Error("SignatureIllformed");
  }
  const data = {
    header: signedParts[0],
    payload: signedParts[1],
    signature: signedParts[2],
  };
  const verifier = crypto.createVerify("RSA-SHA256");
  const protect = `${data.header}.${data.payload}`;
  verifier.update(protect);
  const isVerified = verifier.verify(publicKey, data.signature, "base64");
  if (isVerified) {
    return decodeBase64Url(data.payload);
  } else {
    throw new Error("SignatureValidationFailed");
  }
}

/**
 * Decrypts the given data and returns the decrypted string.
 * @param {string} data - Data to decrypt.
 * @param {crypto.KeyObject} publicKey - Public key.
 * @param {crypto.KeyObject} privateKey - Private key.
 * @returns {string} Decrypted data.
 */
function jwtDecrypt(data, publicKey, privateKey) {
  const signed = decrypt(data, privateKey);
  return verify(signed, publicKey);
}


```



---



## Encrypted API Samples




#### 1. Session API




#### Sample Request Code Snippet:

```sample request
curl --location --request POST 'https://api.juspay.in/v4/session' \
--header 'x-merchantid: testhdfc1' \
--header 'Content-Type: application/json' \
--data-raw '{
    "header": "hbGciOiJIUzI1NiIsInR5cCI6Ikp",
    "encryptedKey": "NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9",
    "iv": "iIsInR5c",
    "encryptedPayload": "DkwIiwibmFtZSI6IkpvaG4gRG9lI",
    "tag": "2QT4fwpMeJf36POk6y"
}'

```



#### Sample Response Code Snippet:

```sample response
{
  "iv": "JDjdjfdndhfdjfnkdfkdjfkdjf",
  "encryptedKey": "jdsfhdsAJHS89888THBKK",
  "header": "eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoia2V5XzIyMmNkZWY1Yzk4MzQ3Y2M5NjdkMTkwYzZlMjc2NmEzIn0",
  "encryptedPayload": "Sggsdfjksdhf333Zqwekjrejrh",
  "tag": "U3Wr---JYnerFskWqrKiNg"
}

```



#### 2. Order Status API




#### Sample Request Code Snippet:

```sample request
curl -X POST 'https://api.juspay.in/v4/order-status' \
-H 'Content-Type: application/json' \
-H 'x-merchantid: SG3240' \
-d '{
  "header": "eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoia2V5XzQ5N2U3NWY0YjY5YjQ1ZjA4MTFmM2U1MjM1YmUwZDhlIn0",
  "encryptedKey": "<Base64URL-encoded-encrypted-CEK>",
  "iv": "<Base64URL-encoded-IV>",
  "encryptedPayload": "QF1T2JGZ8aLJgttj9xiWndBUebk61ZZOfhMQfIeNjaHgt16uWmhgap697_17JHAvQY5G-ggHOIMzrf2UIBDmjPzX0l70M0p...",
  "tag": "RIdnzXPt3XwvVs3XfIfgeQ"
}'
```



#### Sample Response Code Snippet:

```sample response
{
  "header": "eyJlbmMiOiJBMjU2R0NNIiwiaWF0IjoxNzcxMjM2MzkzLCJraWQiOiJrZXlfYjJiYTYzOTQ3YmU0NDNmYmFmYTlmNjYyNDU3YzBmZGEiLCJhbGciOiJSU0EtT0FFUC0yNTYiLCJleHAiOm51bGx9",
  "encryptedKey": "A1B2C3D4E5F6...",
  "iv": "X1Y2Z3A4B5C6...",
  "encryptedPayload": "M1N2O3P4Q5R6...",
  "tag": "uuBvOTK4-8tIXF1FpihdwA"
}
```



#### 3. Refund Order API




#### Sample Request Code Snippet:

```sample request
curl --location -g --request POST 'https://api.juspay.in/v4/orders/{order_id}/refunds' \
--header 'x-merchantid: testhdfc1' \
--header 'Content-Type: application/json' \
--data-raw '{
  "iv": "kdshfdjksfQQQQBBKFKJJFKKFJKJksadjfhjsdafjhd",
  "encryptedKey": "ksdfdksf123343djfdjfjdsf",
  "header": "eyJhbGciOiJSU0EtT0FFUC0yNjdfdsjhfsdfsdY5MjRiMGY4MDc4MzI4MzQwOGE1NDJmIn0",
  "encryptedPayload": "ksdfdshk9998jsdsdhfhfhjsdhf",
  "tag": "9YD4q-AaM4xYGMHz5dlarQ"
}'

```



#### Shell Code Snippet:

```shell
{
  "header": "eyJlbmMiOiJBMjU2R0NNIiwiaWF0IjoxNzcwOTc2NTA4LCJraWQiOiJrZXlfYjJiYTYzOTQ3YmU0NDNmYmFmYTlmNjYyNDU3YzBmZGEiLCJhbGciOiJSU0EtT0FFUC0yNTYiLCJleHAiOm51bGx9",
  "encryptedKey": "A1B2C3D4E5F6...",
  "iv": "X1Y2Z3A4B5C6...",
  "encryptedPayload": "M1N2O3P4Q5R6...",
  "tag": "uuBvOTK4-8tIXF1FpihdwA"
}
```



#### 4. Mandate Execution API




#### Sample Request Code Snippet:

```sample request
POST https://api.juspay.in/v4/txns
curl --location --request POST 'https://api.juspay.in/v4/txns' \
--header 'x-merchantid: testmerchant' \
--header 'Content-Type: application/json' \
--data-raw '{
    "header": "eyJraWQiOiJrZXlfYjEyMzQ1Iiwia...",
    "encryptedKey": "kdjfjsdfksdjfjsdhfJJJJsdjfhsdhfyyskdfjksdjf...",
    "iv": "ksdjfdjksfWWWsksdfjkd",
    "encryptedPayload": "ksdfjdksfAAAWWWWWBBJFJFFFKFJFsjfsdjfksdfndjfjdsfk887...",
    "tag": "PT7c70nump_MBePrlTDPIA"
}'
```



#### Sample Response Code Snippet:

```sample response
{
  "txn_uuid": "eulmN9X8JpeC7qK6AJq",
  "txn_id": "merchant_id-order_id-1",
  "status": "PENDING_VBV",
  "payment": {
    "authentication": {
      "url": "https://api.juspay.io/v2/pay/finish/merchant_id/eulmN9X8JpeCxxxxxJq/2621",
      "method": "GET"
    }
  },
  "order_id": "order_id"
}
```


---

## See Also

- [Security Specifications](https://juspay.io/in/docs/api-reference/docs/push-provisioning/security-specifications)
- [Introduction](https://juspay.io/in/docs/api-reference/docs/visa-agentic-transactions/introduction)
