Node.js Example
Using the jose library for JWT signing.
Install
npm install jose
Complete example
import { SignJWT, importPKCS8 } from 'jose';
import { randomUUID } from 'crypto';
import fs from 'fs';
// --- Configuration ---
const PARTNER_ID = 'your-partner-id-uuid';
const API_KEY_ID = 'your-api-key-id-uuid';
const PRIVATE_KEY_PEM = fs.readFileSync('./private-key.pem', 'utf8');
const TOKEN_ENDPOINT = 'https://prod.rynopay.io/auth/partner/v1/token';
const API_BASE_URL = 'https://prod.rynopay.io';
// --- Token Manager ---
let cachedToken = null;
let tokenExpiresAt = 0;
async function getAccessToken() {
// Return cached token if still valid (with 10-minute buffer)
if (cachedToken && Date.now() < tokenExpiresAt - 10 * 60 * 1000) {
return cachedToken;
}
// Import your private key
const privateKey = await importPKCS8(PRIVATE_KEY_PEM, 'ES256');
// Build and sign the JWT assertion
const assertion = await new SignJWT({})
.setProtectedHeader({ alg: 'ES256', typ: 'JWT', kid: API_KEY_ID })
.setIssuer(PARTNER_ID)
.setSubject(PARTNER_ID)
.setAudience('https://prod.rynopay.io/auth/partner/v1/token')
.setIssuedAt()
.setExpirationTime('5m')
.setJti(randomUUID())
.sign(privateKey);
// Exchange for an access token
const response = await fetch(TOKEN_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grantType: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: assertion,
scope: 'partner.read partner.write',
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(
`Token exchange failed: ${error.error?.code} - ${error.error?.message}`
);
}
const tokenResponse = await response.json();
// Cache the token
cachedToken = tokenResponse.accessToken;
tokenExpiresAt = Date.now() + tokenResponse.expiresIn * 1000;
return cachedToken;
}
// --- Making API calls ---
async function callApi(path, options = {}) {
const token = await getAccessToken();
const response = await fetch(`${API_BASE_URL}${path}`, {
...options,
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
...options.headers,
},
});
// Handle expired token
if (response.status === 401) {
cachedToken = null; // Clear cache
const newToken = await getAccessToken();
return fetch(`${API_BASE_URL}${path}`, {
...options,
headers: {
Authorization: `Bearer ${newToken}`,
'Content-Type': 'application/json',
...options.headers,
},
});
}
return response;
}
// --- Usage ---
const response = await callApi('/api/partner/some-endpoint');
const data = await response.json();
console.log(data);