Documentation PhoneVerify

Envoyez et vérifiez des codes OTP via WhatsApp, Telegram et Signal — depuis VOS propres numéros/bots émetteurs, sans API officielle payante.

Parcours en 4 étapes

  1. Compte → inscrivez-vous (auth.html) — email ou Google.
  2. Clé API → dashboard → « Clés API & intégration » → générez une clé pvk_… (recopiable à tout moment).
  3. Émetteur → « Numéro émetteur » → liez WhatsApp (QR) ou un compte/bot Telegram (voir § Émetteur).
  4. Envoi → POST /v1/otp/request avec le numéro destinataire → le code part de votre émetteur → POST /v1/otp/verify.

Intégration de A à Z dans votre application

Tout ce dont une application externe a besoin : une seule clé API (X-API-Key). L'émetteur lié à la clé est utilisé automatiquement.

1 — Demander un code

cURL
curl -X POST https://votre-domaine.fr/v1/otp/request \
  -H "X-API-Key: pvk_…" -H "Content-Type: application/json" \
  -d '{"phone":"+33612345678","channel":"whatsapp","fallback_channels":["telegram"]}'
ChampTypeDescription
phone requisstringNuméro destinataire au format E.164 (+2376…).
channelstringtelegram | whatsapp | signal — canal préféré.
fallback_channelsstring[]Canaux de repli tentés dans l'ordre en cas d'échec.

Réponse 200 : {"request_id","channel_used","attempted_channels","expires_in"}. Stockez request_id côté serveur, associé à la session utilisateur.

2 — Vérifier le code

cURL
curl -X POST https://votre-domaine.fr/v1/otp/verify \
  -H "X-API-Key: pvk_…" -H "Content-Type: application/json" \
  -d '{"request_id":"3f4530…","code":"428193"}'
verifiedinvalid_codeexpiredlocked

3 — Exemple complet Node.js

node
const API = "https://votre-domaine.fr", KEY = process.env.OTP_KEY;

async function requestOtp(phone) {
  const r = await fetch(`${API}/v1/otp/request`, {
    method: "POST",
    headers: { "X-API-Key": KEY, "Content-Type": "application/json" },
    body: JSON.stringify({ phone, channel: "telegram",
                           fallback_channels: ["whatsapp"] }),
  });
  const d = await r.json();
  if (!r.ok) {
    // Erreur : voir d.channel_errors (lien Telegram, whatsapp_not_linked…)
    throw new Error(JSON.stringify(d));
  }
  return d.request_id;              // stockez-le côté serveur
}

async function verifyOtp(request_id, code) {
  const r = await fetch(`${API}/v1/otp/verify`, {
    method: "POST",
    headers: { "X-API-Key": KEY, "Content-Type": "application/json" },
    body: JSON.stringify({ request_id, code }),
  });
  return (await r.json()).status;   // "verified" | "invalid_code" | …
}

4 — Exemple complet Python

python
import httpx, os

API, KEY = "https://votre-domaine.fr", os.environ["OTP_KEY"]
H = {"X-API-Key": KEY}

def request_otp(phone: str) -> str:
    r = httpx.post(f"{API}/v1/otp/request", headers=H, json={
        "phone": phone, "channel": "whatsapp",
        "fallback_channels": ["telegram"],
    }, timeout=15)
    r.raise_for_status()
    return r.json()["request_id"]       # à persister côté serveur

def verify_otp(request_id: str, code: str) -> bool:
    r = httpx.post(f"{API}/v1/otp/verify", headers=H, json={
        "request_id": request_id, "code": code,
    }, timeout=15)
    return r.json().get("status") == "verified"

5 — Points d'attention d'intégration

  • Gardez la clé côté serveur — jamais dans du code client public.
  • request_id : associez-le à votre session, ne le régénérez pas (usage unique).
  • Un code expiré/verrouillé nécessite un nouveau request (cooldown entre deux envois).
  • Sur 502 : lisez channel_errors — il contient la cause exacte et, pour Telegram, un lien de livraison à transmettre au destinataire.

Numéro émetteur (qui ENVOIE les codes)

Chaque clé API possède sa propre session émettrice — vos OTP partent de VOS numéros, pas d'un compte partagé de la plateforme.

CanalÉmetteurConfiguration (dashboard → Numéro émetteur)
WhatsAppVotre numéro WhatsApp« Afficher le QR » → scannez avec le numéro émetteur (Appareils connectés). « Changer de numéro » réinitialise. Une session par clé.
Telegram (bot)Votre botCréez un bot @BotFather → collez le token → validé via getMe. Sans bot personnel : bot par défaut de la plateforme.
Telegram universelVotre compte TelegramNuméro → code de connexion → connecté. Envoie à tout numéro sans action du destinataire (expérimental).
SignalVotre numéro SignalEnregistrement signal-cli (captcha + SMS) — voir docs/DEPLOYMENT.md.

Changer d'émetteur : WhatsApp → « Changer de numéro » (nouveau QR) ; Telegram → « Retirer » ou nouveau token ; Universel → reconnecter un autre compte.

Côté destinataire — ce qui est requis

CanalPrérequis destinataire
WhatsAppUn compte WhatsApp actif sur le numéro. Rien d'autre — le message arrive directement.
Telegram (bot)Au choix : (a) /start + « Partager mon contact » une fois → lié à vie ; (b) cliquer le lien de livraison t.me/bot?start=otp_… renvoyé dans channel_errors → le bot lui envoie le code (aucune liaison préalable).
Telegram universelRien — le message arrive directement sur tout compte Telegram existant.
SignalUn compte Signal actif sur le numéro.

Numéros de tous pays acceptés (E.164). Un numéro sans compte sur le canal demandé → 404 user_not_found : proposez un canal de repli.

Référence API

Base : https://votre-domaine.fr/v1 — Auth : X-API-Key: pvk_…

POST/v1/otp/request

Génère + envoie un code. Corps : phone, channel, fallback_channels[]. → request_id, expires_in.

POST/v1/otp/verify

Vérifie le code. Corps : request_id, code. → verified|invalid_code|expired|locked + attempts_remaining.

GET/v1/channels

État des canaux pour votre clé : enabled + healthy (votre session émettrice, pas celle d'un autre).

Codes de statut de vérification

verifiedCode correct — la demande est consommée (usage unique).
invalid_codeMauvais code, attempts_remaining indique les essais restants.
expiredTTL dépassé ou request_id inconnu (même réponse — anti-oracle).
lockedTrop de tentatives — nouvelle demande requise.

Endpoints émetteur

GET/v1/wa-qr

QR de liaison WhatsApp de VOTRE session : {"connected","qr","phone"}. QR en ASCII — affichable tel quel.

POST/v1/wa-reset

Réinitialise votre session émettrice (nouveau numéro possible).

POST/v1/tg-auth/start

Envoie le code de connexion du compte universel : {"phone":"+…"}.

POST/v1/tg-auth/confirm

Valide la session : {"phone","code","password?"}. → compte émetteur actif pour toutes vos clés.

Messages personnalisés

Le texte des OTP est brandé par compte : nom d'entreprise saisi dans « Mon entreprise » + template optionnel.

Placeholders : {company} {code} {minutes}. Défaut : {company} : votre code {code} — valable {minutes} min. Ne le partagez jamais.

Erreurs & statuts

HTTPstatusSignification / action
200sent / verifiedOK.
200invalid_code / expired / lockedVérification échouée — voir table ci-dessus.
401unauthorizedClé absente, invalide ou révoquée.
422invalid_phoneNuméro non E.164 ou invalide.
429rate_limited / resend_cooldownQuota dépassé — champ retry_after (secondes).
502send_failedTous les canaux ont échoué → channel_errors + fallback_channels.

Détails dans channel_errors

  • whatsapp_not_linked → session émettrice non liée : scannez le QR (Numéro émetteur).
  • telegram_chat_not_linked → contient le lien de livraison t.me/… à transmettre au destinataire.
  • tg_user_bridge:404:telegram_user_not_found → le numéro n'a pas de compte Telegram.
  • canal inconnu ou desactive → canal non configuré côté serveur.

Limites & quotas

  • Validité du code : OTP_TTL_SECONDS (défaut 300 s).
  • Tentatives : OTP_MAX_ATTEMPTS (5) puis verrouillage de la demande.
  • Cooldown renvoi : OTP_RESEND_COOLDOWN_SECONDS entre deux codes sur un même numéro.
  • Rate-limit : OTP_RATE_LIMIT_PER_PHONE/heure + GATEWAY_IP_RATE_LIMIT/min par IP.

Sur 429, respectez retry_after avant de réessayer.

Sécurité & risques

  • Codes jamais stockés en clair (HMAC-SHA256 sel + poivre), comparaison temps constant, TTL strict.
  • Logs : aucun code ni numéro en clair — empreintes tronquées uniquement.
  • Clés API : gardez-les côté serveur ; révoquez toute clé compromise (dashboard).
  • Risques connecteurs : WhatsApp universel/Telegram universel = comptes automatisés non officiels — risque de bannissement du numéro émetteur. Numéros dédiés + quotas bas. Détails : docs/RISKS.md.

FAQ

Pourquoi mon destinataire doit-il cliquer un lien Telegram ? Un bot ne peut écrire qu'à un utilisateur l'ayant démarré — c'est le protocole. Le lien t.me/bot?start=otp_… livre le code en un clic sans liaison préalable. L'émetteur « universel » (compte utilisateur) supprime cette friction.

« La session WhatsApp a expiré » — refaites le QR dans « Numéro émetteur » ; la session persiste dans le volume Docker ensuite.

Puis-je avoir plusieurs émetteurs ? Oui — une session WhatsApp par clé API. Créez autant de clés que d'émetteurs voulus.

Comment changer de plan ? Dashboard → « Plans & abonnement ».

Où sont les stats ? « Vue d'ensemble » (compteurs + graphique 14 j) et « Vérifications » (50 derniers événements, numéros masqués).

Console de test

Testez l'API en direct — identique à la console du dashboard.