Sites Adultes (ADULT_BLIND)

Intégration pour les sites adultes utilisant le rail de conformité ADULT_BLIND — double anonymat, sans échange de grant_code.

Concept

Le rail ADULT_BLIND est conçu pour les sites adultes qui ont besoin de vérification d'âge sans que ZYKAY puisse identifier l'opérateur du site. Le flux est différent du rail STANDARD :

Rail STANDARDRail ADULT_BLIND
Auth widgetdata-partner-iddata-session-token
Résultat#grant_code=... → échange API#attestation=... → vérification locale
Appel retourPOST /v1/exchange (HMAC)Aucun appel retour — vérification hors ligne
AnonymatZYKAY connaît le partenaireZYKAY ne voit jamais le partner_id dans le flux verifier

L'accès au rail ADULT_BLIND est configuré par l'équipe ZYKAY lors de l'onboarding. Contactez tech@zykay.com pour activer ce mode pour votre compte.

Vue d'ensemble du flux

Votre Serveur                  ZYKAY                      Téléphone utilisateur
    |                            |                              |
    |-- 1. Obtenir token (HMAC) ->|                              |
    |<----- session token --------|                              |
    |                            |                              |
    |-- 2. Intégrer widget ------>|                              |
    |   (token dans script tag)  |---- QR / deep link -------->|
    |                            |                    Face ID   |
    |                            |<--- preuve complète ---------|
    |                            |                              |
    |<--- 3. attestation ---------|                              |
    |                            |                              |
    |-- 4. Vérifier signature ----|  (local, pas d'appel API)   |
    |                            |                              |
    |   Utilisateur vérifié ✓    |                              |

Étape 1 : Obtenir un token de session (côté serveur)

Un appel API depuis votre backend. Le token est valide 5 minutes.

<?php
function getZykayToken(): ?string {
    $partnerId = 'pk_live_yoursite_xxxxxxxx';          // votre Partner ID
    $secret    = file_get_contents('/path/to/secret'); // votre secret base64
    $body      = json_encode([
        'scopes' => ['isAdult'],
        'origin' => 'https://votresite.com'            // doit correspondre à votre domaine
    ]);
 
    // Signature HMAC
    $timestamp = time();
    $nonce     = bin2hex(random_bytes(16));
    $bodyHash  = rtrim(strtr(base64_encode(hash('sha256', $body, true)), '+/', '-_'), '=');
    $canonical = "{$bodyHash}.{$timestamp}.{$partnerId}.{$nonce}";
    $signature = rtrim(strtr(base64_encode(
        hash_hmac('sha256', $canonical, base64_decode($secret), true)
    ), '+/', '-_'), '=');
 
    $ch = curl_init('https://app.zykay.com/api/billing/session');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $body,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            "x-partner-id: {$partnerId}",
            "x-partner-timestamp: {$timestamp}",
            "x-partner-nonce: {$nonce}",
            "x-partner-signature: {$signature}",
        ],
    ]);
    $response = curl_exec($ch);
    $status   = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
 
    if ($status !== 201) return null;
    $data = json_decode($response, true);
    return $data['token'] ?? null;
}
 
$sessionToken = getZykayToken();
?>
⚠️

Appelez getZykayToken() à chaque chargement de page. Ne mettez pas le token en cache entre les pages — il expire après 5 minutes.


Étape 2 : Intégrer le widget (frontend)

Ajoutez ce code HTML. C'est tout — pas de proxy, pas de configuration CORS, pas de JavaScript supplémentaire.

<!-- Widget de vérification d'âge ZYKAY -->
<div id="zykay-widget"></div>
<script
  src="https://widget-app.zykay.com/v4/loader.min.js"
  data-session-token="<?= htmlspecialchars($sessionToken) ?>"
  data-success-path="/verified"
  data-locale="fr">
</script>

Ce qui se passe automatiquement :

  • Le widget affiche un QR code (desktop) ou un bouton "Vérifier" (mobile)
  • L'utilisateur scanne le QR → ouvre l'app ZYKAY → Face ID (~2 secondes)
  • Le widget détecte la complétion → redirige vers /verified#attestation=eyJ...

Attributs disponibles :

AttributRequisDescription
data-session-tokenOuiLe token de l'étape 1
data-success-pathOuiOù rediriger après vérification (chemin relatif)
data-localeNonfr ou en (défaut : fr)

Le widget ADULT_BLIND utilise data-session-token au lieu de data-partner-id. Ne mélangez pas les deux attributs.


Étape 3 : Recevoir l'attestation (page de résultat)

Après vérification, l'utilisateur est redirigé vers data-success-path avec l'attestation dans le fragment URL :

https://votresite.com/verified#attestation=eyJhbGciOiJFZERTQSIsImtpZCI6ImFrLTIwMjYtMDIifQ...

Extrayez-la avec JavaScript et envoyez-la à votre backend :

<!-- verified.php (ou votre page de résultat) -->
<script>
  const params = new URLSearchParams(location.hash.slice(1));
  const attestation = params.get('attestation');
 
  if (attestation) {
    fetch('/api/check-age', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ attestation }),
    }).then(r => r.json()).then(result => {
      if (result.verified) {
        // Utilisateur vérifié — créer session, afficher contenu, etc.
        document.getElementById('status').textContent = 'Âge vérifié ✓';
      }
    });
  }
</script>

Étape 4 : Vérifier la signature de l'attestation (côté serveur)

L'attestation est un JWS signé (Ed25519). Vérifiez la signature localement — aucun appel API requis.

<?php
function verifyAttestation(string $attestation): ?array {
    // 1. Récupérer la clé publique ZYKAY (mettre en cache 1 heure)
    $jwks = json_decode(file_get_contents('https://app.zykay.com/api/billing/attestation-keys'), true);
    $key  = $jwks['keys'][0];
    $publicKey = sodium_base642bin($key['x'], SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING);
 
    // 2. Découper le JWS : header.payload.signature
    $parts = explode('.', $attestation);
    if (count($parts) !== 3) return null;
 
    // 3. Vérifier la signature Ed25519
    $message   = $parts[0] . '.' . $parts[1];
    $signature = sodium_base642bin($parts[2], SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING);
 
    if (!sodium_crypto_sign_verify_detached($signature, $message, $publicKey)) {
        return null; // signature invalide
    }
 
    // 4. Décoder le payload
    $payload = json_decode(base64_decode(strtr($parts[1], '-_', '+/')), true);
 
    // 5. Vérifier l'expiration
    if ($payload['exp'] < time()) return null;
 
    // 6. Vérifier le scope (bit 0 = isAdult)
    $isAdult = ($payload['scope_mask'] & 1) === 1;
 
    return [
        'is_adult'   => $isAdult,
        'scope_mask' => $payload['scope_mask'],
        'nullifier'  => $payload['nullifier'],  // identifiant unique (respecte la vie privée)
    ];
}
 
// Utilisation dans votre endpoint /api/check-age :
$result = verifyAttestation($_POST['attestation']);
if ($result && $result['is_adult']) {
    $_SESSION['age_verified'] = true;
    echo json_encode(['verified' => true]);
} else {
    echo json_encode(['verified' => false]);
}

La vérification de l'attestation est hors ligne — aucun appel API vers ZYKAY n'est nécessaire. Seule la récupération de la clé publique JWKS doit être effectuée (et mise en cache).


Résumé

Pas de proxies. Pas de configuration CORS. Pas de déploiement de widget. Pas d'endpoints d'échange.

ÉtapeCe que vous écrivez
1. TokengetZykayToken() — ~20 lignes (serveur)
2. WidgetBalise <script> — 5 lignes (HTML)
3. ExtractionLecture du fragment URL — ~10 lignes (JavaScript)
4. VérificationverifyAttestation() — ~20 lignes (serveur)

Ce que ZYKAY gère pour vous :

  • QR codes, deep links, polling
  • Vérification Face ID
  • Signature de l'attestation
  • CORS, WebSocket, tous les transports

FAQ

Dois-je configurer CORS ? Non. Le loader widget parle à widget-app.zykay.com qui a CORS configuré pour toutes les origines.

Dois-je configurer un webhook ? Non. Le résultat arrive via redirection de fragment URL, pas par webhook.

Que faire si le token expire (5 min) ? Générez-en un nouveau à chaque chargement de page. Chaque chargement doit appeler getZykayToken() fraîchement.

Puis-je mettre la clé publique en cache ? Oui, mettez la réponse JWKS en cache jusqu'à 1 heure. La clé tourne rarement (tous les quelques mois).

Qu'est-ce que le nullifier ? Un identifiant unique respectant la vie privée. Le même utilisateur produit toujours le même nullifier sur votre site, mais un nullifier différent sur d'autres sites. Utilisez-le pour la déduplication (empêcher une personne de se vérifier deux fois) sans tracer les utilisateurs entre sites.

Combien de temps dure la vérification ?

  • Première fois : ~15 secondes (portefeuille EUDI + Face ID)
  • Utilisateur de retour : ~2 secondes (Face ID uniquement — connexion instantanée)

Références