Skip to main content

Overview

The Modulus Labs QR API uses JWE (JSON Web Encryption) tokens to encrypt request and response payloads. This ensures that sensitive payment data is protected and maintains integrity throughout the entire transaction lifecycle.
What is JWE? JSON Web Encryption is a standard for encrypting data in a compact, URL-safe format. It ensures that the payload cannot be read or modified during transit.

Why JWE Encryption?

Data Protection

Sensitive payment information is encrypted end-to-end

Integrity Verification

Any tampering with the token will cause verification to fail

Compliance

Meets security standards for financial transactions

Standardized

Uses industry-standard encryption algorithms

Encryption Specifications

The QR API requires specific encryption algorithms for JWE tokens:
ParameterValueDescription
Content EncryptionA256CBC-HS512AES-256-CBC with HMAC SHA-512
Key Wrap AlgorithmA256KWAES-256 Key Wrap
Content Typeapplication/jsonJSON payload format

Your Encryption Key

Along with your Secret Key, Modulus Labs will provide you with an Encryption Key for JWE operations:
6eaNv7t5xyCuiBfGCq2q7wu9uHEH3mTQ1Mq0c53XO1c53vfG
Store your Encryption Key securely! Never commit it to version control or expose it in client-side code.
Use these well-tested libraries for creating and decrypting JWE tokens:

jose

The most popular JWT/JWE library for Node.js:
npm install jose
Documentation

Creating Request Payloads

Here’s how to encrypt your request data into a JWE token:

Step 1: Prepare Your Data

First, construct the JSON payload with your transaction details:
{
  "activationCode": "A9X4-B7P2-Q6Z8-M3L5",
  "currency": "PHP",
  "amount": "98.00",
  "merchantReferenceNumber": "5e91bc6c-f7e2-4a39-ae0e-5e93985c94a4"
}

Step 2: Encrypt into JWE Token

const jose = require('jose');

// Your encryption key from Modulus Labs
const encryptionKey = '6eaNv7t5xyCuiBfGCq2q7wu9uHEH3mTQ1Mq0c53XO1c53vfG';

// Convert the key to the proper format
const secretKey = Buffer.from(encryptionKey, 'utf-8');

// Your payment data
const payload = {
  activationCode: 'A9X4-B7P2-Q6Z8-M3L5',
  currency: 'PHP',
  amount: '98.00',
  merchantReferenceNumber: '5e91bc6c-f7e2-4a39-ae0e-5e93985c94a4'
};

// Create the JWE token
const jwe = await new jose.CompactEncrypt(
  new TextEncoder().encode(JSON.stringify(payload))
)
  .setProtectedHeader({ alg: 'A256KW', enc: 'A256CBC-HS512' })
  .encrypt(secretKey);

console.log('JWE Token:', jwe);
// Result: eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0...

Step 3: Wrap in Request Payload

Send the JWE token in your API request:
{
  "Token": "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0.sOoEpD55U9NMZ4JQwgFMsHGGVH0ZJvsc7Gn7LXwc8mwrFk36ordNAwJDU23S1xGQtK7ZJno3hHnvm3rhJPcXTw27ry-Lz0Gi.LDd-bawV4AhjUi9hXttE5Q.TUxl65r-0HnOBQj01ggZDMKT8pH2FDJEAKIQp_SfWXDNYZWizgZD5fmtdNxbPgj37WHFVVAvYlfWcAHYvbH-Q.p4drK620DM3qpkdVjw-xaB473Hu3hQFWpoDn_WEwYdw"
}

Decrypting Response Payloads

The API returns JWE tokens in successful responses. Here’s how to decrypt them:

Example Response

{
  "Token": "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0.htNErE1kLWIP0dwXcozgxLsS1bpeDIa1Qwvqdsx3MpUmyuag3ebonJbh87NrKgDjMOzzCiDwX_AefkTJtRCReYJ1rvrWmgo.ERm2rmdwPJhUncoLL6ofDA.6zqtciKiDbmx_s2AY_bRrXmedr1jafiD2HZVAxS-pWEcZo8VnpovL6epILjfTNdrITogtvCekeoZMhIuDTN6yqOlcqfl2SsblwZF5NfTMKdTUcqF8Sdx3Cg7XzT4UgLhQxd1l441E3dGNvp4coOyGkAfT5_2yVN3uGXVuz9sU.kkhQrE6cPmkOdJqZPJBa4Xphi-u2xG6y-ETcReT203Q"
}

Decrypt the Token

const jose = require('jose');

// Your encryption key
const encryptionKey = '6eaNv7t5xyCuiBfGCq2q7wu9uHEH3mTQ1Mq0c53XO1c53vfG';
const secretKey = Buffer.from(encryptionKey, 'utf-8');

// The JWE token from the response
const jweToken = response.Token;

// Decrypt the token
const { plaintext } = await jose.compactDecrypt(jweToken, secretKey);

// Parse the payload
const payload = JSON.parse(new TextDecoder().decode(plaintext));

console.log('Decrypted payload:', payload);
// {
//   "id": "0ce32626-08cf-405c-adab-6d384a8871da",
//   "qrBody": "LAawP8GKxWCwWA1gf4MVisVgsBrA+wIvFYrFYDGB9gBeLxWKxGMD6AC8Wi8ViMYD1AV4sFovFYgDrA7xYLBaLxQDWB3ixWCwWiwGsD/BisVgsFgNYH+DFYrFYLAawPsCLxWKxWAxgfYAX8VisRjA+gAvFovFYjGA9QFeLBaLxf/fXh0LAAAAAAzytx7GnpKIgYABYCBgABgIGAAGAgaAgYABYCBgABgIGAAGAgaAgYABYBC6Qa/HfkoA6gAAAABJRU5ErkJggg=="
// }

Response Types

The API returns different response formats based on the request status:

Successful Requests

Returns a JWE token containing the response data:
{
  "Token": "eyJhbGciOiJBMjU2S1ciL..."
}
Decrypted payload:
{
  "id": "0ce32626-08cf-405c-adab-6d384a8871da",
  "qrBody": "LAawP8GKxWCwWA1gf4MVisVgs..."
}

Common Issues & Solutions

Cause: Mismatch in encryption algorithms or incorrect keySolution:
  • Verify you’re using A256KW for key wrap
  • Verify you’re using A256CBC-HS512 for content encryption
  • Check that your encryption key is correct
  • Ensure the key is properly encoded (UTF-8)
Cause: Token has been modified or key mismatchSolution:
  • Ensure the token hasn’t been modified during transit
  • Verify you’re using the same encryption key for encryption and decryption
  • Check that the token is complete (not truncated)
Cause: Not all JWT libraries support JWESolution:
  • Use the recommended libraries listed above
  • Ensure your library supports both A256KW and A256CBC-HS512
  • Update to the latest version of your chosen library
Cause: Incorrect encoding of binary dataSolution:
  • Use URL-safe Base64 encoding (no padding)
  • Ensure consistent encoding between encryption and decryption
  • Use library-provided encoding functions

Security Best Practices

Key Storage

Store encryption keys in secure vaults or environment variables, never in code

Key Rotation

Implement a key rotation strategy for enhanced security

Validate Tokens

Always validate token structure and algorithms before decryption

Monitor Failures

Log and monitor decryption failures for potential security issues

Complete Example

Here’s a complete end-to-end example:
const jose = require('jose');
const axios = require('axios');

const SECRET_KEY = process.env.MODULUS_SECRET_KEY;
const ENCRYPTION_KEY = process.env.MODULUS_ENCRYPTION_KEY;

async function createDynamicQR() {
  // 1. Prepare payload
  const payload = {
    activationCode: 'A9X4-B7P2-Q6Z8-M3L5',
    currency: 'PHP',
    amount: '98.00',
    merchantReferenceNumber: crypto.randomUUID()
  };

  // 2. Encrypt payload
  const secretKey = Buffer.from(ENCRYPTION_KEY, 'utf-8');
  const jweToken = await new jose.CompactEncrypt(
    new TextEncoder().encode(JSON.stringify(payload))
  )
    .setProtectedHeader({ alg: 'A256KW', enc: 'A256CBC-HS512' })
    .encrypt(secretKey);

  // 3. Make API request
  const response = await axios.post(
    'https://qrph.sbx.moduluslabs.io/v1/pay/qr',
    { Token: jweToken },
    {
      auth: { username: SECRET_KEY, password: '' },
      headers: { 'Content-Type': 'application/json' }
    }
  );

  // 4. Decrypt response
  const { plaintext } = await jose.compactDecrypt(
    response.data.Token,
    secretKey
  );
  const result = JSON.parse(new TextDecoder().decode(plaintext));

  console.log('Transaction ID:', result.id);
  console.log('QR Body:', result.qrBody);

  return result;
}

createDynamicQR().catch(console.error);

Next Steps