Hyperwallet v4 – Postman Docs

πŸ“„ Overview

Hyperwallet requires requests and responses to be:

  1. Signed (JWS) using your RSA private key
  2. Encrypted (JWE) using Hyperwallet’s RSA public key

This setup ensures:

DIAGRAM OVERVIEW
πŸ” Pre-request Script

// Load jsrsasign (offline)
(function () {
  if (typeof KJUR !== 'undefined') return;
  const lib = pm.collectionVariables.get('JSRSASIGN_LIB');
  if (!lib) throw new Error('JSRSASIGN_LIB missing');
  eval(lib);
})();

// Sign + Encrypt Request
(function signAndEncrypt() {
  if (!pm.request.body || !pm.request.body.raw) return;

  const now = Math.floor(Date.now() / 1000);
  const privateJWK = JSON.parse(pm.environment.get('PRIVATE_JWK'));
  const publicJWK  = JSON.parse(pm.environment.get('PUBLIC_JWK'));
  const payload = JSON.parse(pm.request.body.raw);

  const jwtPayload = {
    ...payload,
    iss: pm.environment.get('EXPECTED_ISS'),
    aud: pm.environment.get('EXPECTED_AUD'),
    iat: now,
    exp: now + 3600,
    jti: KJUR.crypto.Util.getRandomHexOfNbytes(16)
  };

  const signedJWT = KJUR.jws.JWS.sign(
    'RS256',
    JSON.stringify({ alg: 'RS256', typ: 'JWT', kid: privateJWK.kid }),
    JSON.stringify(jwtPayload),
    KEYUTIL.getKey(privateJWK)
  );

  const encryptedJWT = KJUR.crypto.Cipher.encrypt(
    signedJWT,
    KEYUTIL.getKey(publicJWK),
    'RSAOAEP256'
  );

  pm.request.body.raw = JSON.stringify({ request: encryptedJWT });
})();
πŸ”‘ Post-response Script

// Decrypt + Verify Response
pm.test("Encrypted response present", () => {
  pm.expect(pm.response.text()).to.be.a('string').and.not.empty;
});

const privateJWK = JSON.parse(pm.environment.get('PRIVATE_JWK'));
const serverJWKS = JSON.parse(pm.environment.get('SERVER_JWKS'));
const encrypted = pm.response.text();

let decryptedJWS;
pm.test('JWE decrypted successfully', () => {
  decryptedJWS = KJUR.crypto.Cipher.decrypt(encrypted, KEYUTIL.getKey(privateJWK), 'RSAOAEP256');
  pm.expect(decryptedJWS).to.be.a('string');
});

const header = JSON.parse(KJUR.crypto.Util.b64utoutf8(decryptedJWS.split('.')[0]));
pm.test('JWS header contains kid', () => { pm.expect(header.kid).to.exist; });

const serverKey = serverJWKS.keys.find(k => k.use === 'sig' && k.kid === header.kid);
pm.test('Hyperwallet signing key found', () => { pm.expect(serverKey).to.exist; });

const signatureValid = KJUR.jws.JWS.verify(decryptedJWS, KEYUTIL.getKey(serverKey), ['RS256']);
pm.test('Response signature valid', () => { pm.expect(signatureValid).to.be.true; });

const payload = JSON.parse(KJUR.crypto.Util.b64utoutf8(decryptedJWS.split('.')[1]));
const now = Math.floor(Date.now() / 1000);
pm.test('iss valid', () => { pm.expect(payload.iss).to.eql(pm.environment.get('EXPECTED_ISS')); });
pm.test('aud valid', () => { 
  const expectedAud = pm.environment.get('EXPECTED_AUD'); 
  if (Array.isArray(payload.aud)) { pm.expect(payload.aud).to.include(expectedAud); } 
  else { pm.expect(payload.aud).to.eql(expectedAud); } 
});
pm.test('token not expired', () => { pm.expect(payload.exp).to.be.above(now); });
pm.environment.set('DECRYPTED_RESPONSE', JSON.stringify(payload));
πŸ”— HATEOAS Automation

if (payload && payload.links && Array.isArray(payload.links)) {
  payload.links.forEach(link => {
    if (!link.rel || !link.href) return;
    if (link.rel === 'self') {
      const token = link.href.split('/').pop();
      pm.environment.set('LAST_RESOURCE_TOKEN', token);
    }
    pm.environment.set(`HATEOAS_${link.rel.toUpperCase()}`, link.href);
  });
}
πŸ§ͺ Newman CLI – Production

npm install -g newman

newman run postman/hyperwallet.postman_collection.json \
  -e postman/production.postman_environment.json \
  --env-var PRIVATE_JWK="$PRIVATE_JWK" \
  --env-var PUBLIC_JWK="$PUBLIC_JWK" \
  --env-var SERVER_JWKS="$SERVER_JWKS" \
  --env-var EXPECTED_ISS="$EXPECTED_ISS" \
  --env-var EXPECTED_AUD="$EXPECTED_AUD" \
  --timeout-request 60000 \
  --reporters cli,json \
  --reporter-json-export newman-report.json
βœ… Compliance Notes