Backend (Exchange API)

Échangez le grant_code contre un pass_token et les résultats de vérification.

Flux recommandé

  1. Le frontend récupère #grant_code=... sur data-success-path
  2. Le frontend envoie grant_code à votre backend (POST /api/zykay/exchange)
  3. Votre backend appelle POST https://api.zykay.com/v1/exchange
  4. Votre backend crée la session applicative (cookie/JWT)
⚠️

Ne faites jamais l'appel api.zykay.com/v1/exchange depuis le navigateur: le Partner Secret doit rester côté serveur.

Requête Partner API

  • URL: https://api.zykay.com/v1/exchange
  • Méthode: POST
  • Body: {"grant_code":"g_xxx"}
  • Headers requis:
    • X-Partner-ID
    • X-Partner-Timestamp (secondes Unix)
    • X-Partner-Nonce (UUID v4)
    • X-Partner-Signature

Signature HMAC (canonique)

bodyHash   = base64url(SHA256(rawBody))
canonical  = `${bodyHash}.${timestamp}.${partnerId}.${nonce}`
signature  = base64url(HMAC-SHA256(canonical, base64_decode(partnerSecret)))

Exemple Node.js

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();
}

Lecture du hash côté frontend

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 }),
  });
}

Réponse type

{
  "pass_token": "p_abc123...",
  "expires_in": 14400,
  "token_type": "Bearer",
  "attributes": {
    "age_over_18": true
  }
}