Backend (Exchange API)
Exchange the grant_code for an pass_token and the verification results.
Recommended feed
- The frontend retrieves
#grant_code=...fromdata-success-path - The frontend sends
grant_codeto your backend (POST /api/zykay/exchange) - Your backend calls
POST https://api.zykay.com/v1/exchange - Your backend creates the application session (cookie/JWT)
⚠️
Never make the api.zykay.com/v1/exchange call from the browser: the Partner Secret must remain server side.
Partner API request
- URL:
https://api.zykay.com/v1/exchange - Method:
POST - Body:
{"grant_code":"g_xxx"} - Required headers:
X-Partner-IDX-Partner-Timestamp(Unix seconds)X-Partner-Nonce(UUID v4)X-Partner-Signature
HMAC signature (canonical)
bodyHash = base64url(SHA256(rawBody))
canonical = `${bodyHash}.${timestamp}.${partnerId}.${nonce}`
signature = base64url(HMAC-SHA256(canonical, base64_decode(partnerSecret)))Node.js example
import crypto from 'crypto';
function base64url(buffer) {
return buffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
export async function exchangeGrantCode(grantCode) {
const partnerId = process.env.ZYKAY_PARTNER_ID;
const partnerSecret = process.env.ZYKAY_PARTNER_SECRET;
const body = JSON.stringify({ grant_code: grantCode });
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomUUID();
const bodyHash = base64url(crypto.createHash('sha256').update(body, 'utf8').digest());
const canonical = `${bodyHash}.${timestamp}.${partnerId}.${nonce}`;
const secret = Buffer.from(partnerSecret, 'base64');
const signature = base64url(crypto.createHmac('sha256', secret).update(canonical).digest());
const response = await fetch('https://api.zykay.com/v1/exchange', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Partner-ID': partnerId,
'X-Partner-Timestamp': timestamp,
'X-Partner-Nonce': nonce,
'X-Partner-Signature': signature,
},
body,
});
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.error || `Exchange failed (${response.status})`);
}
return response.json();
}Reading the hash on the frontend side
const hash = new URLSearchParams(window.location.hash.slice(1));
const grantCode = hash.get('grant_code');
if (grantCode) {
await fetch('/api/zykay/exchange', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ grant_code: grantCode }),
});
}Typical answer
{
"pass_token": "p_abc123...",
"expires_in": 14400,
"token_type": "Bearer",
"attributes": {
"age_over_18": true
}
}