Setting Up Your JWKS Endpoint for Unified Transfer

⚠️

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-known URI
  • 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

FieldDescriptionExample
ktyKey Type"RSA"
eExponent of the RSA public key"AQAB" (typically)
usePublic Key Use"sig" (for signature)
kidKey ID - A unique identifier for the key"your-unique-key-id-12345"
algAlgorithm used with this key"RS256"
nModulus 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:

  1. Upload jwks.json to S3 bucket
  2. Enable static website hosting
  3. Set bucket policy for public read access
  4. Use CloudFront for HTTPS and caching
  5. Configure custom domain with .well-known path

Google Cloud Storage Example:

  1. Upload jwks.json to GCS bucket
  2. Make object publicly readable
  3. Use Cloud CDN for HTTPS
  4. 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
  • kid matches the value you'll use in request signatures
  • alg matches 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:

  1. Verify the endpoint is accessible
  2. Retrieve your public key(s)
  3. Configure their system to verify signatures using your public key
  4. Confirm successful registration

Key Rotation

Regular key rotation is a security best practice.

Rotation Process:

  1. Generate new key pair with a new kid
  2. Add new key to JWKS (keep old key active)
  3. Update your application to sign with new key
  4. Notify Maya of the key rotation
  5. Wait for transition period (recommended: 24-48 hours)
  6. 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 kid in JWS header matches JWKS
  • Ensure you're signing with the correct private key
  • Check algorithm matches (alg field)
  • 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.