Gratuit, sans compte, vérifiable hors ligne : vos utilisateurs prouvent qu'ils ont un compte Qevik, et vous vérifiez la signature sur votre serveur.
Vous vendez des articles en ligne ? Voir l'API marchands
Déclarer le domaine
Vous déclarez votre domaine et publiez un fichier de vérification. Aucun compte n'est nécessaire.
L'utilisateur prouve son compte
Depuis votre site, l'utilisateur autorise sur qevik.com l'envoi d'une attestation signée.
Vous vérifiez la signature
Votre serveur vérifie l'attestation avec les clés publiques de Qevik, sans appel à chaque affichage.
Choisissez un libellé, une langue et un fond. Les fichiers s'utilisent tels quels, sans traduction ni retouche (conditions, art. 5).
Aperçu, tel qu'il apparaît sur une annonce
Le badge se place à côté du pseudonyme (conditions, art. 5 b).
Extrait HTML
<a href="https://qevik.com/verify/fr#ATTESTATION" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer">
<img src="/badges/qevik-badge-membre-fr-clair.svg" alt="Membre Qevik" width="95" height="28"></a>La Plateforme n'affiche un badge Qevik :
La Plateforme n'associe au badge aucune mention laissant entendre que Qevik vérifie l'identité de l'utilisateur, se porte garante de lui ou de ses prestations, assure des objets, recommande la Plateforme ou est son partenaire.
Votre plateforme ouvre /authorize sur qevik.com.
L'utilisateur se connecte à Qevik et lit l'écran de consentement.
S'il autorise, qevik.com le renvoie vers votre adresse de retour avec code, state et iss.
Votre serveur vérifie state et iss, puis échange le code sur /token, de serveur à serveur, avec le pseudonyme public de l'utilisateur (platform_handle).
Vous recevez l'attestation signée et la vérifiez sur votre serveur.
Vous demandez à l'utilisateur s'il veut afficher le badge sur ses annonces.
Le choix revient à l'utilisateur
Les conditions l'exigent (art. 5 a) : le badge ne s'affiche que si l'utilisateur l'a expressément choisi, et disparaît dès qu'il le retire.
Avant l'expiration, vous renouvelez l'attestation en joignant de nouveau le pseudonyme public de l'utilisateur ; si l'utilisateur a retiré l'accès, le renouvellement est refusé.
Un code QR Qevik se télécharge uniquement sur qevik.com. Votre bouton « Télécharger » ouvre la page qevik.com dans un nouvel onglet ou une nouvelle fenêtre, avec rel="noopener noreferrer". Votre plateforme ne reçoit jamais le code QR, ne peut pas l'obtenir par l'API et n'intègre pas cette page dans un cadre (iframe).
GET /api/v1/public/oauth/authorize
GET https://qevik.com/api/v1/public/oauth/authorize
?client_id=plt_YOUR_CLIENT_ID
&redirect_uri=https%3A%2F%2Fyour-site.example%2Fqevik%2Fback # exact registered return URL
&response_type=code
&scope=attest%3Aaccount%20attest%3Atagged # or attest:account alone
&state=RANDOM_BOUND_TO_THE_USER_SESSION # mandatory, <= 512 chars
&code_challenge=BASE64URL_SHA256_OF_CODE_VERIFIER
&code_challenge_method=S256 # S256 only
&ui_locales=fr # optional: fr, de or en
# No platform_handle here: it goes to POST /token only.
# Refusals are never redirects: the user sees a Qevik page.redirect_uri ?state&iss&code
# Approved
https://your-site.example/qevik/back?state=...&iss=https://qevik.com&code=... # code: single use, 60 s
# Refused by the user
https://your-site.example/qevik/back?state=...&iss=https://qevik.com&error=access_denied
# Check state against the user's session and iss against https://qevik.com before anything else.POST /api/v1/public/oauth/token (authorization_code)
POST https://qevik.com/api/v1/public/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=CODE_FROM_THE_CALLBACK
&code_verifier=ORIGINAL_CODE_VERIFIER
&client_id=plt_YOUR_CLIENT_ID
&redirect_uri=https%3A%2F%2Fyour-site.example%2Fqevik%2Fback
&platform_handle=jean_d # the user's public name on your site, on every call
# Server to server only: browsers are refused by CORS. No client secret (public client, PKCE).POST /api/v1/public/oauth/token (refresh_token)
POST https://qevik.com/api/v1/public/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=LAST_REFRESH_TOKEN_RECEIVED
&client_id=plt_YOUR_CLIENT_ID
&platform_handle=jean_d # sent again: Qevik never stores it
# Refresh tokens rotate: keep the new one, the one you sent is dead.
# Sending a used refresh token (or a code twice) revokes the link: the user must consent again.200 /token
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"attestation": "eyJhbGciOiJFUzI1NiIsImtpZCI6Ii4uLiIsInR5cCI6InFldmlrLWF0dGVzdGF0aW9uK2p3dCJ9...",
"token_type": "qevik-attestation",
"expires_in": 2592000,
"refresh_token": "...",
"scope": "attest:account attest:tagged"
}OAuth errors /token
HTTP/1.1 400 Bad Request
{ "error": "invalid_request", "error_description": "platform_handle is invalid (PLT_014)." }
400 invalid_request missing or malformed parameter, invalid platform_handle
400 invalid_grant code or refresh token unknown, expired, used or revoked; PKCE mismatch
400 access_denied platform closed, or the user can no longer be attested (link revoked)
400 unsupported_grant_type anything but authorization_code or refresh_token
400 temporarily_unavailable the offer is closed
429 temporarily_unavailable 5 000 attestations per platform per UTC day; honour Retry-After
# platform_handle: NFC, 1 to 64 characters, letters and digits of any script, . _ - and single inner spaces.Attestation claims
header { "alg": "ES256", "kid": "...", "typ": "qevik-attestation+jwt" }
payload {
"iss": "https://qevik.com",
"aud": "plt_YOUR_CLIENT_ID", // a single string
"sub": "...", // pairwise: differs on every platform
"iat": 1761120000, "nbf": 1761120000, "exp": 1763712000, // 30 days
"jti": "...",
"att": "tagged", // "account" or "tagged"
"member": true, // always true
"tagged": true, // present only when att = "tagged"
"since": 2025, // account creation year
"dom": "your-site.example",
"platformHandle": "jean_d" // must equal the listing owner's name
}L'attestation est un JWT signé par Qevik. Vérifiez-la avec les clés publiques de Qevik et une bibliothèque éprouvée.
Bibliothèque : jose, version 5.2 ou ultérieure.
import { createRemoteJWKSet, jwtVerify } from "jose";
const ISSUER = "https://qevik.com";
const AUDIENCE = "plt_YOUR_CLIENT_ID";
// JWKS cached for 1 h; an unknown kid reloads it at most once every 5 minutes.
const JWKS = createRemoteJWKSet(new URL("https://qevik.com/.well-known/qevik-platform-jwks.json"), {
cacheMaxAge: 3_600_000,
cooldownDuration: 300_000,
});
/** Returns the claims, or null: a null hides the badge, it never shows an error. */
export async function verifyQevikAttestation(token, expectedHandle) {
if (typeof token !== "string" || token.length > 2048 || token.split(".").length !== 3) return null;
try {
const { payload } = await jwtVerify(token, JWKS, {
algorithms: ["ES256"],
issuer: ISSUER,
audience: AUDIENCE,
typ: "qevik-attestation+jwt",
clockTolerance: 60,
requiredClaims: ["exp", "nbf", "iat", "sub", "att", "member", "platformHandle"],
});
if (payload.member !== true) return null;
if (payload.att !== "account" && payload.att !== "tagged") return null;
if (payload.platformHandle !== expectedHandle) return null;
return payload; // "Qevik label" badge only if payload.tagged === true
} catch {
return null;
}
}
Vérifiez sur votre serveur, jamais dans le navigateur de l'utilisateur.
Avant la mise en ligne, chaque cas doit donner le résultat attendu avec votre implémentation.
Ces vecteurs sont signés avec une clé de test publiée pour cet usage seulement. Ne l'acceptez jamais en production.
| Cas | Résultat attendu |
|---|---|
Attestation valide valid | Accepter |
Attestation expirée expired | Refuser |
Signature altérée badSignature | Refuser |
Audience d'une autre plateforme (aud) wrongAud | Refuser |
Algorithme « none » algNone | Refuser |
Algorithme HS256 signé avec la clé publique hs256 | Refuser |
Clé inconnue (kid) unknownKid | Refuser |
Pseudonyme différent de celui de l'annonce badHandle | Refuser |
{
"issuer": "https://test-vectors.qevik.invalid",
"audience": "plt_TESTVECTORS0001",
"verifyAt": 1792670400,
"listingHandle": "jean_d",
"jwks": {
"keys": [
{
"kty": "EC",
"crv": "P-256",
"x": "u_zfhmLEuPswg6JpT7grZpo4m_NkCFxmicl5x4FsUvY",
"y": "mzG_VCMoJIHUojBIijnhn6JmEUKPoXN05gmL31A3nUY",
"kid": "qevik-test-vectors-2026-10-22",
"use": "sig",
"alg": "ES256"
}
]
},
"vectors": [
{
"case": "valid",
"expected": "accept",
"token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InFldmlrLXRlc3QtdmVjdG9ycy0yMDI2LTEwLTIyIiwidHlwIjoicWV2aWstYXR0ZXN0YXRpb24rand0In0.eyJpc3MiOiJodHRwczovL3Rlc3QtdmVjdG9ycy5xZXZpay5pbnZhbGlkIiwiYXVkIjoicGx0X1RFU1RWRUNUT1JTMDAwMSIsInN1YiI6InRlc3QtdmVjdG9ycy1wYWlyd2lzZS1zdWIiLCJpYXQiOjE3OTI2NjY4MDAsIm5iZiI6MTc5MjY2NjgwMCwiZXhwIjoxNzkzMjcxNjAwLCJqdGkiOiJ0ZXN0LXZlY3RvcnMtanRpIiwiYXR0IjoiYWNjb3VudCIsIm1lbWJlciI6dHJ1ZSwic2luY2UiOjIwMjUsImRvbSI6ImV4YW1wbGUuY29tIiwicGxhdGZvcm1IYW5kbGUiOiJqZWFuX2QifQ.LWxRJPwTJUIZZZ1upll0EBR4UNubVjLtMgP8aNZQ2DiatLLz3tNqPc6RKvSi1cm14NVFYZukls7WCgy0nahOVQ"
},
{
"case": "expired",
"expected": "reject",
"token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InFldmlrLXRlc3QtdmVjdG9ycy0yMDI2LTEwLTIyIiwidHlwIjoicWV2aWstYXR0ZXN0YXRpb24rand0In0.eyJpc3MiOiJodHRwczovL3Rlc3QtdmVjdG9ycy5xZXZpay5pbnZhbGlkIiwiYXVkIjoicGx0X1RFU1RWRUNUT1JTMDAwMSIsInN1YiI6InRlc3QtdmVjdG9ycy1wYWlyd2lzZS1zdWIiLCJpYXQiOjE3OTE4MDY0MDAsIm5iZiI6MTc5MTgwNjQwMCwiZXhwIjoxNzkyNTg0MDAwLCJqdGkiOiJ0ZXN0LXZlY3RvcnMtanRpIiwiYXR0IjoiYWNjb3VudCIsIm1lbWJlciI6dHJ1ZSwic2luY2UiOjIwMjUsImRvbSI6ImV4YW1wbGUuY29tIiwicGxhdGZvcm1IYW5kbGUiOiJqZWFuX2QifQ.m73cnY6lVdJBaZmsfvjCKIVmQhD4wSIKFBaD8iyD9FRrpVxNiNiPfHTTQvEq095J0QKAAF5kb_kOIZLQDNwlVA"
},
{
"case": "badSignature",
"expected": "reject",
"token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InFldmlrLXRlc3QtdmVjdG9ycy0yMDI2LTEwLTIyIiwidHlwIjoicWV2aWstYXR0ZXN0YXRpb24rand0In0.eyJpc3MiOiJodHRwczovL3Rlc3QtdmVjdG9ycy5xZXZpay5pbnZhbGlkIiwiYXVkIjoicGx0X1RFU1RWRUNUT1JTMDAwMSIsInN1YiI6InRlc3QtdmVjdG9ycy1wYWlyd2lzZS1zdWIiLCJpYXQiOjE3OTI2NjY4MDAsIm5iZiI6MTc5MjY2NjgwMCwiZXhwIjoxNzkzMjcxNjAwLCJqdGkiOiJ0ZXN0LXZlY3RvcnMtanRpIiwiYXR0IjoiYWNjb3VudCIsIm1lbWJlciI6dHJ1ZSwic2luY2UiOjIwMjUsImRvbSI6ImV4YW1wbGUuY29tIiwicGxhdGZvcm1IYW5kbGUiOiJqZWFuX2QifQ.AWxRJPwTJUIZZZ1upll0EBR4UNubVjLtMgP8aNZQ2DiatLLz3tNqPc6RKvSi1cm14NVFYZukls7WCgy0nahOVQ"
},
{
"case": "wrongAud",
"expected": "reject",
"token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InFldmlrLXRlc3QtdmVjdG9ycy0yMDI2LTEwLTIyIiwidHlwIjoicWV2aWstYXR0ZXN0YXRpb24rand0In0.eyJpc3MiOiJodHRwczovL3Rlc3QtdmVjdG9ycy5xZXZpay5pbnZhbGlkIiwiYXVkIjoicGx0X1RFU1RWRUNUT1JTQ0xPU0VEIiwic3ViIjoidGVzdC12ZWN0b3JzLXBhaXJ3aXNlLXN1YiIsImlhdCI6MTc5MjY2NjgwMCwibmJmIjoxNzkyNjY2ODAwLCJleHAiOjE3OTMyNzE2MDAsImp0aSI6InRlc3QtdmVjdG9ycy1qdGkiLCJhdHQiOiJhY2NvdW50IiwibWVtYmVyIjp0cnVlLCJzaW5jZSI6MjAyNSwiZG9tIjoiZXhhbXBsZS5jb20iLCJwbGF0Zm9ybUhhbmRsZSI6ImplYW5fZCJ9.Ci2dObZufGWY3v-DyWWrrYFaDrqdX_C1elzyrkOm-NslEY5zHWyXhFF9-W-T8n9PO55_eJD0BeRzAgg_9qHHiQ"
},
{
"case": "algNone",
"expected": "reject",
"token": "eyJhbGciOiJub25lIiwia2lkIjoicWV2aWstdGVzdC12ZWN0b3JzLTIwMjYtMTAtMjIiLCJ0eXAiOiJxZXZpay1hdHRlc3RhdGlvbitqd3QifQ.eyJpc3MiOiJodHRwczovL3Rlc3QtdmVjdG9ycy5xZXZpay5pbnZhbGlkIiwiYXVkIjoicGx0X1RFU1RWRUNUT1JTMDAwMSIsInN1YiI6InRlc3QtdmVjdG9ycy1wYWlyd2lzZS1zdWIiLCJpYXQiOjE3OTI2NjY4MDAsIm5iZiI6MTc5MjY2NjgwMCwiZXhwIjoxNzkzMjcxNjAwLCJqdGkiOiJ0ZXN0LXZlY3RvcnMtanRpIiwiYXR0IjoiYWNjb3VudCIsIm1lbWJlciI6dHJ1ZSwic2luY2UiOjIwMjUsImRvbSI6ImV4YW1wbGUuY29tIiwicGxhdGZvcm1IYW5kbGUiOiJqZWFuX2QifQ."
},
{
"case": "hs256",
"expected": "reject",
"token": "eyJhbGciOiJIUzI1NiIsImtpZCI6InFldmlrLXRlc3QtdmVjdG9ycy0yMDI2LTEwLTIyIiwidHlwIjoicWV2aWstYXR0ZXN0YXRpb24rand0In0.eyJpc3MiOiJodHRwczovL3Rlc3QtdmVjdG9ycy5xZXZpay5pbnZhbGlkIiwiYXVkIjoicGx0X1RFU1RWRUNUT1JTMDAwMSIsInN1YiI6InRlc3QtdmVjdG9ycy1wYWlyd2lzZS1zdWIiLCJpYXQiOjE3OTI2NjY4MDAsIm5iZiI6MTc5MjY2NjgwMCwiZXhwIjoxNzkzMjcxNjAwLCJqdGkiOiJ0ZXN0LXZlY3RvcnMtanRpIiwiYXR0IjoiYWNjb3VudCIsIm1lbWJlciI6dHJ1ZSwic2luY2UiOjIwMjUsImRvbSI6ImV4YW1wbGUuY29tIiwicGxhdGZvcm1IYW5kbGUiOiJqZWFuX2QifQ.dfPx7WKEdyHAkjyATNTFF3M1D1URSC56PfrrnHFYw8c"
},
{
"case": "unknownKid",
"expected": "reject",
"token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InFldmlrLXRlc3QtdmVjdG9ycy11bmtub3duIiwidHlwIjoicWV2aWstYXR0ZXN0YXRpb24rand0In0.eyJpc3MiOiJodHRwczovL3Rlc3QtdmVjdG9ycy5xZXZpay5pbnZhbGlkIiwiYXVkIjoicGx0X1RFU1RWRUNUT1JTMDAwMSIsInN1YiI6InRlc3QtdmVjdG9ycy1wYWlyd2lzZS1zdWIiLCJpYXQiOjE3OTI2NjY4MDAsIm5iZiI6MTc5MjY2NjgwMCwiZXhwIjoxNzkzMjcxNjAwLCJqdGkiOiJ0ZXN0LXZlY3RvcnMtanRpIiwiYXR0IjoiYWNjb3VudCIsIm1lbWJlciI6dHJ1ZSwic2luY2UiOjIwMjUsImRvbSI6ImV4YW1wbGUuY29tIiwicGxhdGZvcm1IYW5kbGUiOiJqZWFuX2QifQ.7BmSiWeOXySvC4AkJfRaRNXyOURPsgmoK1IHKEyzUydmg2cHK5o0JH9gAd01SaYBecdMKbzJ4Hhy4Vpnt4w69g"
},
{
"case": "badHandle",
"expected": "reject",
"token": "eyJhbGciOiJFUzI1NiIsImtpZCI6InFldmlrLXRlc3QtdmVjdG9ycy0yMDI2LTEwLTIyIiwidHlwIjoicWV2aWstYXR0ZXN0YXRpb24rand0In0.eyJpc3MiOiJodHRwczovL3Rlc3QtdmVjdG9ycy5xZXZpay5pbnZhbGlkIiwiYXVkIjoicGx0X1RFU1RWRUNUT1JTMDAwMSIsInN1YiI6InRlc3QtdmVjdG9ycy1wYWlyd2lzZS1zdWIiLCJpYXQiOjE3OTI2NjY4MDAsIm5iZiI6MTc5MjY2NjgwMCwiZXhwIjoxNzkzMjcxNjAwLCJqdGkiOiJ0ZXN0LXZlY3RvcnMtanRpIiwiYXR0IjoiYWNjb3VudCIsIm1lbWJlciI6dHJ1ZSwic2luY2UiOjIwMjUsImRvbSI6ImV4YW1wbGUuY29tIiwicGxhdGZvcm1IYW5kbGUiOiJtYXJpZV9rIn0.qVAM8tm0AMoL_npDpHe5JmTXOFzutksNPWwbDpbm0ySHCIQ0EUrLD_otABxiNTRaDFndF7XjhPaQ24NLO5U_eg"
}
]
}Ce que vos visiteurs liront sur la page de vérification de Qevik :
Ce que ce badge ne prouve pas
Qevik ne vérifie pas l'identité des personnes. Ce badge ne garantit ni l'honnêteté de son titulaire, ni ses annonces, ni les objets ou colis qu'il transporte, et Qevik n'assure rien. Il ne promet pas qu'un objet perdu sera retrouvé. Qevik n'est pas partie aux échanges conclus sur le site qui affiche le badge.
Conditions d'utilisation de l'API (version 1.0)(nouvel onglet)
L'API est gratuite et fournie sans niveau de service. Aucun support individuel n'est promis.