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 STANDARD | Rail ADULT_BLIND | |
|---|---|---|
| Auth widget | data-partner-id | data-session-token |
| Résultat | #grant_code=... → échange API | #attestation=... → vérification locale |
| Appel retour | POST /v1/exchange (HMAC) | Aucun appel retour — vérification hors ligne |
| Anonymat | ZYKAY connaît le partenaire | ZYKAY 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 :
| Attribut | Requis | Description |
|---|---|---|
data-session-token | Oui | Le token de l'étape 1 |
data-success-path | Oui | Où rediriger après vérification (chemin relatif) |
data-locale | Non | fr 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.
| Étape | Ce que vous écrivez |
|---|---|
| 1. Token | getZykayToken() — ~20 lignes (serveur) |
| 2. Widget | Balise <script> — 5 lignes (HTML) |
| 3. Extraction | Lecture du fragment URL — ~10 lignes (JavaScript) |
| 4. Vérification | verifyAttestation() — ~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
- API Billing Session — documentation complète de l'endpoint
POST /api/billing/session - Codes d'erreur — erreurs spécifiques au rail ADULT_BLIND
- Politique de Rate-Limit — limites de l'endpoint billing session