Unified Transfer is in active development. As a pioneer merchant, you will be informed of the updates through your Maya Relationship Manager.
Overview
A JSON Web Key Set (JWKS) endpoint is a critical component for the Unified Transfer authentication. It hosts your public signing keys, which Maya uses to verify the digital signatures on your API requests.
This guide walks you through:
- Generating key pairs
- Creating a compliant JWKS file
- Hosting it at the required
.well-knownURI - Signing API requests correctly
- Key rotation and operational best practices
Before proceeding, make sure you have read the About Unified Transfer guide to understand the foundational concepts.
What is a JWKS Endpoint?
A JSON Web Key Set (JWKS) endpoint is a publicly accessible HTTPS URL that returns your public cryptographic keys in JSON format.
Maya uses your JWKS endpoint to:
- Retrieve your public key(s)
- Verify signed API requests
- Validate key identifiers (
kid) - Support secure key rotation
Your JWKS endpoint establishes cryptographic trust between your system and Maya.
Building the JWKS Endpoint for Unified Transfer
Step 1: Generate a Signing Key Pair
Unified Transfer requires asymmetric signing using RSA or ECDSA.
- Recommended Algorithm:
RS256(RSA with SHA-256) - Minimum RSA key size: 2048 bits
Using OpenSSL
# Generate private key (2048-bit RSA)
openssl genrsa -out private-key.pem 2048
# Extract public key
openssl rsa -in private-key.pem -pubout -out public-key.pem
# Generate a unique key ID (kid)
openssl rand -hex 16
Using Node.js
const crypto = require('crypto');
const fs = require('fs');
// Generate RSA key pair
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});
// Save keys to files
fs.writeFileSync('private-key.pem', privateKey);
fs.writeFileSync('public-key.pem', publicKey);
// Generate a unique key ID (kid)
const kid = crypto.randomBytes(16).toString('hex');
console.log('Key ID:', kid);
Using Python
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
import secrets
# Generate private key
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048
)
# Save private key
with open('private-key.pem', 'wb') as f:
f.write(private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
))
# Extract and save public key
public_key = private_key.public_key()
with open('public-key.pem', 'wb') as f:
f.write(public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
))
# Generate a unique key ID (kid)
kid = secrets.token_hex(16)
print(f'Key ID: {kid}')
Important: Store your private key securely. Never expose it in your JWKS endpoint or commit it to version control.
Step 2: Create Your JWKS File
The JWKS file contains your public key information that Maya will use to verify your request signatures.
JWKS File Structure
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "your-unique-key-id-12345",
"alg": "RS256",
"n": "vTq5T5xN8mZ...your-public-key-modulus..."
}
]
}
Field Descriptions
| Field | Description | Example |
|---|---|---|
kty | Key Type | "RSA" |
e | Exponent of the RSA public key | "AQAB" (typically) |
use | Public Key Use | "sig" (for signature) |
kid | Key ID - A unique identifier for the key | "your-unique-key-id-12345" |
alg | Algorithm used with this key | "RS256" |
n | Modulus of the RSA public key (base64url-encoded) | "vTq5T5xN8mZ..." |
Converting PEM to JWKS
Convert your public key to JWKS format.
Using Node.js
const fs = require('fs');
const crypto = require('crypto');
const jose = require('node-jose');
async function createJWKS() {
// Read public key
const publicKeyPEM = fs.readFileSync('public-key.pem', 'utf8');
// Convert to JWK
const keystore = jose.JWK.createKeyStore();
const key = await keystore.add(publicKeyPEM, 'pem', {
kid: 'your-unique-key-id-12345',
use: 'sig',
alg: 'RS256'
});
// Export as JWKS
const jwks = keystore.toJSON();
// Save to file
fs.writeFileSync('jwks.json', JSON.stringify(jwks, null, 2));
console.log('JWKS file created successfully');
}
createJWKS();
Using Python
import json
from jwcrypto import jwk
# Read public key
with open('public-key.pem', 'rb') as f:
public_key_pem = f.read()
# Convert to JWK
key = jwk.JWK.from_pem(public_key_pem)
# Set key parameters
key_dict = json.loads(key.export_public())
key_dict['kid'] = 'your-unique-key-id-12345'
key_dict['use'] = 'sig'
key_dict['alg'] = 'RS256'
# Create JWKS
jwks = {
'keys': [key_dict]
}
# Save to file
with open('jwks.json', 'w') as f:
json.dump(jwks, f, indent=2)
print('JWKS file created successfully')
Manual Conversion
If you prefer to create the JWKS manually, you'll need to extract the modulus (n) and exponent (e) from your public key:
# Extract modulus and exponent
openssl rsa -pubin -in public-key.pem -text -noout
Then base64url-encode these values and construct the JWKS JSON structure.
Step 3: Host Your JWKS File
Your JWKS file must be publicly accessible via HTTPS at the .well-known path.
Requirements
- URL Format:
https://your-domain.com/.well-known/jwks.json - Protocol: HTTPS only (TLS 1.2 or higher)
- Authentication: None (publicly accessible)
- Content-Type:
application/json - Availability: High availability (99.9%+ uptime recommended)
- CORS: Not required (server-to-server communication)
Hosting Options
Option 1: Static File Hosting
Host the JWKS file as a static file on your web server.
Nginx Example:
server {
listen 443 ssl;
server_name your-domain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location /.well-known/jwks.json {
alias /var/www/jwks.json;
add_header Content-Type application/json;
add_header Cache-Control "public, max-age=3600";
}
}
Apache Example:
<VirtualHost *:443>
ServerName your-domain.com
SSLEngine on
SSLCertificateFile /path/to/cert.pem
SSLCertificateKeyFile /path/to/key.pem
Alias /.well-known/jwks.json /var/www/jwks.json
<Location /.well-known/jwks.json>
Header set Content-Type "application/json"
Header set Cache-Control "public, max-age=3600"
</Location>
</VirtualHost>
Option 2: Dynamic Endpoint
Create a dynamic endpoint that serves the JWKS.
Express.js Example:
const express = require('express');
const fs = require('fs');
const app = express();
app.get('/.well-known/jwks.json', (req, res) => {
const jwks = JSON.parse(fs.readFileSync('jwks.json', 'utf8'));
res.setHeader('Content-Type', 'application/json');
res.setHeader('Cache-Control', 'public, max-age=3600');
res.json(jwks);
});
app.listen(443);
Flask Example:
from flask import Flask, jsonify
import json
app = Flask(__name__)
@app.route('/.well-known/jwks.json')
def jwks():
with open('jwks.json', 'r') as f:
jwks_data = json.load(f)
response = jsonify(jwks_data)
response.headers['Cache-Control'] = 'public, max-age=3600'
return response
if __name__ == '__main__':
app.run(ssl_context='adhoc', port=443)
Option 3: Cloud Storage
Use cloud storage services with HTTPS access.
AWS S3 Example:
- Upload
jwks.jsonto S3 bucket - Enable static website hosting
- Set bucket policy for public read access
- Use CloudFront for HTTPS and caching
- Configure custom domain with
.well-knownpath
Google Cloud Storage Example:
- Upload
jwks.jsonto GCS bucket - Make object publicly readable
- Use Cloud CDN for HTTPS
- Configure custom domain
Step 4: Validate the JWKS Endpoint
Verify that your JWKS endpoint is accessible and returns valid JSON.
Using cURL
curl -v https://your-domain.com/.well-known/jwks.json
Expected Response:
HTTP/2 200
content-type: application/json
cache-control: public, max-age=3600
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "your-unique-key-id-12345",
"alg": "RS256",
"n": "vTq5T5xN8mZ..."
}
]
}
Validation Checklist
- Endpoint returns HTTP 200 status
- Response Content-Type is
application/json - HTTPS is enabled (not HTTP)
- JSON structure matches JWKS format
-
kidmatches the value you'll use in request signatures -
algmatches your signing algorithm (e.g., RS256) - Endpoint is publicly accessible (no authentication required)
- Response time is under 1 second
Online Validators
You can use online tools to validate your JWKS:
Step 5: Register the JWKS URL to Maya
During the onboarding process, provide Maya with your JWKS endpoint URL:
https://your-domain.com/.well-known/jwks.json
Maya will:
- Verify the endpoint is accessible
- Retrieve your public key(s)
- Configure their system to verify signatures using your public key
- Confirm successful registration
Key Rotation
Regular key rotation is a security best practice.
Rotation Process:
- Generate new key pair with a new
kid - Add new key to JWKS (keep old key active)
- Update your application to sign with new key
- Notify Maya of the key rotation
- Wait for transition period (recommended: 24-48 hours)
- Remove old key from JWKS after confirmation
Example JWKS with Multiple Keys
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "new-key-id-67890",
"alg": "RS256",
"n": "xYz9..."
},
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "old-key-id-12345",
"alg": "RS256",
"n": "vTq5..."
}
]
}
Rotation Best Practices
- Rotate keys every 90 days
- Never remove a key without confirming Maya has updated their configuration
- Keep rotation logs for audit purposes
- Test new keys in Sandbox before Production
- Have a rollback plan in case of issues
Developer Notes and Tips
Private Key Storage
- Never include private keys in your JWKS endpoint
- Store private keys in secure key management systems:
- Hardware Security Module (HSM)
- Cloud key vaults (AWS KMS, Azure Key Vault, GCP KMS)
- Encrypted file storage with restricted access
- Use environment variables or secure configuration management
- Implement access controls and audit logging
JWKS Endpoint Setup
- Use HTTPS with TLS 1.2 or higher
- Implement rate limiting to prevent abuse
- Set appropriate cache headers (e.g.,
max-age=3600) - Ensure high availability (load balancing, redundancy)
- Use a CDN for better performance and DDoS protection
- Monitor access logs for anomalies
Monitoring and Alerts
Set up monitoring for:
- JWKS endpoint availability (uptime monitoring)
- Response time and latency
- SSL certificate expiration
- Unusual access patterns
- Failed signature verifications (from Maya's side)
Troubleshooting JWKS Endpoint
Issue: Maya cannot access the JWKS endpoint
Symptoms: Authentication failures, 403 errors
Solutions:
- Verify endpoint is publicly accessible (no firewall blocking)
- Check the HTTPS certificate is valid
- Ensure no authentication is required
- Verify correct URL format
Issue: Signature verification fails
Symptoms: 401 or 403 errors with signature-related messages
Solutions:
- Verify
kidin JWS header matches JWKS - Ensure you're signing with the correct private key
- Check algorithm matches (
algfield) - Confirm the public key in JWKS is correct
Issue: JWKS endpoint is slow
Symptoms: Timeout errors, performance issues
Solutions:
- Implement caching (CDN or reverse proxy)
- Optimize server response time
- Use static file hosting instead of dynamic generation
- Consider geographic distribution (multi-region hosting)
FAQs
Q: Can I use the same key pair for multiple environments?
A: No. Use separate key pairs for Sandbox and Production environments for better security isolation.
Q: What happens if my JWKS endpoint goes down?
A: Maya will be unable to verify your request signatures, and all API requests will fail with authentication errors. Ensure high availability.
Q: Can I host JWKS on a different domain than my main application?
A: Yes, as long as it's a domain you control and can verify during onboarding. The domain should be related to your institution for trust purposes.
Q: How many keys can I include in my JWKS?
A: You can include multiple keys (typically 2-3 during rotation periods). However, keep the file size reasonable for performance.
Q: Do I need to notify Maya every time I rotate keys?
A: Yes, coordinate with Maya before removing old keys to ensure a smooth transition. Adding new keys can be done independently.
Next Steps
After setting up your JWKS endpoint, proceed to JWS Implementation Guide for Unified Transfer to prepare for the digital signing process.