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:
Parameter Value Description Content Encryption A256CBC-HS512AES-256-CBC with HMAC SHA-512 Key Wrap Algorithm A256KWAES-256 Key Wrap Content Type application/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.
Recommended Libraries
Use these well-tested libraries for creating and decrypting JWE tokens:
jose The most popular JWT/JWE library for Node.js: Documentation jose-jwt Comprehensive JWT/JWE support for .NET: dotnet add package jose-jwt
Documentation jwt-framework Modern JWT/JWE library for PHP: composer require web-token/jwt-framework
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
Node.js (jose)
Python
PHP
C#
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
Node.js (jose)
Python
PHP
C#
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:
Success
Declined
Unauthorized
Successful Requests Returns a JWE token containing the response data: {
"Token" : "eyJhbGciOiJBMjU2S1ciL..."
}
Decrypted payload: {
"id" : "0ce32626-08cf-405c-adab-6d384a8871da" ,
"qrBody" : "LAawP8GKxWCwWA1gf4MVisVgs..."
}
Declined Requests Returns a JWE token containing the error object: {
"Token" : "eyJhbGciOiJBMjU2S1ciL..."
}
Decrypted payload: {
"code" : "10000002" ,
"error" : "Account is disabled. Please contact support for assistance." ,
"referenceNumber" : "338e2710-8268-4afe-8ef9-9765b0b74688"
}
Unauthorized Requests Returns the error object directly (not encrypted): {
"code" : "10000002" ,
"error" : "Account is disabled. Please contact support for assistance." ,
"referenceNumber" : "338e2710-8268-4afe-8ef9-9765b0b74688"
}
Authentication errors bypass encryption since the credentials are invalid.
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)
Library Compatibility Issues
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