Documentation
Un script dans le navigateur, un appel API depuis votre serveur. Tout ce qu'il faut pour passer en production, sur une seule page.
Démarrage rapide
Il vous faut votre clé publique (pk_...) pour le navigateur et votre clé secrète (sk_...) pour votre serveur. Les deux se trouvent dans le tableau de bord, rubrique Sites et clés.
1. Ajoutez le script
Placez cette balise sur les pages où les utilisateurs s'inscrivent, se connectent, cliquent sur des offres ou demandent un retrait. Il s'initialise automatiquement à partir de data-key
<script src="https://cashoutguard.com/v1/agent.js" data-key="pk_live_YOUR_PUBLIC_KEY" async></script>
2. Collectez et envoyez le request_id à votre serveur
Appelez collect() au moment qui compte, avec le nom de l'événement. La promesse se résout en { request_id, visitor_id }
const { request_id } = await CashoutGuard.collect({ event: 'cashout' });
collect() n'est jamais rejetée. En cas d'échec (blocage, hors ligne, délai dépassé), elle se résout avec request_id: null et une chaîne d'erreur. Envoyez quand même le formulaire et laissez votre serveur décider.
3. Demandez une décision depuis votre serveur
Envoyez l'événement en POST à /v1/evaluate avec votre clé secrète dans l'en-tête Authorization: Bearer sk_live_...
Ne mettez jamais la clé secrète dans du code navigateur ou une application mobile. Seul votre serveur doit appeler /v1/evaluate.
Requête evaluate
POST https://cashoutguard.com/v1/evaluate score un événement pour un compte, le relie à son appareil, son réseau et son adresse de paiement, et renvoie une décision. Envoyez du JSON.
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
event | string | Obligatoire | signup, login, offer_click, conversion, cashout ou custom. |
account_id | string | Obligatoire | Votre ID utilisateur, 191 caractères max. Utilisez un ID opaque, pas un e-mail. |
request_id | string | Facultatif | Fourni par collect() dans le navigateur. Valable 1 heure. Sans lui, nous n'avons que les données réseau. |
ip | string | Facultatif | L'IP de l'utilisateur. Nécessaire uniquement si vous n'avez pas de request_id, par exemple pour les postbacks. |
email | string | Facultatif | Stocké sous forme de hash. Détecte les domaines jetables et les e-mails partagés entre comptes. |
payout_address | string | Facultatif | E-mail PayPal, wallet ou compte qui reçoit l'argent. Stocké sous forme de hash. |
payout_method | string | Facultatif | Texte libre, par exemple paypal, usdt ou giftcard. |
amount | number | Facultatif | Montant du retrait ou de la conversion. |
currency | string | Facultatif | Code de devise, par exemple USD. USD par défaut pour les retraits. |
offer_id | string | Facultatif | ID de l'offre, pour offer_click et conversion. Sert à mesurer la vitesse des offres. |
offer_country | string | Facultatif | Pays pour lequel l'offre paie (code ISO, ex. US), sur les conversions. Un utilisateur dans un autre pays est signalé. |
geo_country | string | Facultatif | Votre propre géolocalisation de l'IP de l'utilisateur (code ISO). Si elle diffère de la nôtre, l'IP est probablement une sortie de proxy. |
transaction_id | string | Facultatif | ID de transaction réseau d'une conversion. Sert à détecter les doublons. |
offer_name | string | Facultatif | Titre de l'offre tel que vos utilisateurs le voient. Affiché partout dans le tableau de bord à la place de l'id. |
click_id | string | Facultatif | Votre id de clic. Envoyez la même valeur sur offer_click et sur la conversion pour les relier. |
source | string | Facultatif | Origine de l'événement, par exemple le nom de l'offerwall ou du réseau. |
meta | object | Facultatif | Jusqu'à 20 champs texte supplémentaires enregistrés avec l'événement (par exemple des sub ids). |
Réponse
La réponse a toujours la même structure. Les champs non applicables valent null.
scoreva de 0 à 100. Les raisons se combinent : plusieurs signaux moyens s'additionnent sans jamais dépasser 100.reasonsliste chaque signal déclenché, du plus fort au plus faible, avec son poids, ses détails et un libellé en anglais clair à afficher tel quel.device.confidenceindique à quel point l'appareil semble unique. Les empreintes peuvent être falsifiées, nous ne prétendons donc jamais à la certitude.linked_accountsliste les autres comptes qui partagent un appareil, une adresse de paiement ou un e-mail avec celui-ci.cashoutn'est renseigné que pour les événements cashout et indique le statut dans votre file de validation.
Décisions et actions
Basez-vous sur le champ action, pas sur decision. Les seuils ci-dessous sont les valeurs par défaut et peuvent être modifiés par site dans le tableau de bord.
| Décision | Score (par défaut) | action en shadow | action en enforce | Que faire |
|---|---|---|---|---|
allow | 0-49 | allow | allow | Continuez normalement. |
review | 50-79 | allow | review | Suspendez le retrait ou demandez une vérification supplémentaire. Il apparaît dans votre file de validation. |
block | 80-100 | allow | block | Stoppez l'action. Gelez le solde ou refusez l'inscription. |
En mode shadow, action vaut toujours allow : le même code peut donc être déployé sans risque avant de passer en enforce. Au moment du passage en enforce, rien ne change dans votre code.
Types d'événements
Envoyez les événements dont vous disposez. Signup et cashout sont les plus importants ; les conversions alimentent les contrôles de vitesse des offres.
| Événement | Quand l'envoyer | Ce qu'il détecte |
|---|---|---|
signup | Compte créé | Multi-comptes, bots, inscriptions via VPN, e-mails jetables |
login | Connexion réussie | Partage de compte, changements de pays, nouveaux appareils |
offer_click | L'utilisateur ouvre une offre | Lance le chrono pour la vitesse des offres |
conversion | Postback de l'offerwall ou du réseau | Offres terminées trop vite, transactions en double, rafales de conversions |
cashout | L'utilisateur demande un retrait | Adresses de paiement partagées, fréquence des retraits, retraits depuis des comptes récents |
custom | Tout autre événement à scorer | Signaux appareil et réseau |
Offerwalls et postbacks
Les conversions d'offerwall arrivent par postback, de serveur à serveur, sans navigateur. CashoutGuard relie chaque conversion au clic qui l'a déclenchée : elle est notée avec l'appareil et le réseau de ce clic.
1. Quand l'utilisateur ouvre une offre
Collectez dans le navigateur et envoyez un offer_click avec l'offre et votre id de clic.
2. Quand le postback arrive
Envoyez une conversion depuis votre gestionnaire de postback avec le même account_id et click_id. Pas besoin de request_id. Transmettez l'IP de l'utilisateur si l'offerwall la fournit.
Les conversions sont vérifiées : terminée trop vite pour l'offre, conversion sans clic, clic et conversion depuis des pays différents, gains bien au-dessus de la journée habituelle du compte et ids de transaction en double.
Réseaux offerwall
Si vous gérez un réseau avec de nombreux éditeurs, envoyez des ids de compte incluant l'éditeur et activez le « Mode réseau offerwall » dans Sites. Les comptes ne sont alors liés qu'au sein du même éditeur, car il est normal qu'une personne utilise plusieurs sites de vos éditeurs. Format : publisher_id:user_id
Catalogue d'offres
POST /v1/offers envoie les noms de vos offres (jusqu'à 1 000 par appel). Les événements passés et futurs affichent alors le vrai nom même s'ils ne portent que l'id.
Codes de raison
Ces codes apparaissent dans reasons[].code. Montrez le texte à vos modérateurs ; utilisez le code dans votre logique.
| Code | Signification | Groupe | Poids |
|---|---|---|---|
ip_tor | Nœud de sortie Tor | Réseau | 60 |
ip_vpn | Connexion VPN | Réseau | 45 |
ip_datacenter | IP de datacenter / hébergeur | Réseau | 40 |
timezone_mismatch | Le fuseau horaire du navigateur ne correspond pas à la localisation de l'IP | Réseau | 20 |
webrtc_ip_leak | Fuite de la vraie IP, située dans un autre pays | Réseau | 30 |
ip_multi_timezone | Même IP avec de nombreux fuseaux horaires (sortie de proxy) | Réseau | 50 |
proxy_farm_ip | Plusieurs comptes sur une IP, tous avec la même horloge étrangère (proxy partagé) | Réseau | 60 |
provider_rotation | Beaucoup de fournisseurs d'accès en quelques jours, avec une horloge étrangère (proxy résidentiel rotatif) | Réseau | 45 |
privacy_relay | iCloud Private Relay (fonction de confidentialité) | Réseau | 5 |
proxy_headers | Requête passée par un proxy | Réseau | 20 |
geo_sources_disagree | Les sources de géolocalisation divergent | Réseau | 45 |
ip_shared_with_accounts | Même IP utilisée par d'autres comptes aujourd'hui | Réseau | 25 |
country_changed | Changement de pays | Réseau | 15 |
impossible_travel | Voyage impossible : des IP de deux pays éloignés en quelques heures | Réseau | 50 |
locale_country_mismatch | Langue du navigateur d'un autre pays | Réseau | 30 |
phone_country_mismatch | Téléphone d'un autre pays que l'IP | Réseau | 20 |
ip_flagged_on_network | IP bloquée sur d'autres sites | Réseau | 25 |
device_shared_with_accounts | Appareil partagé avec d'autres comptes | Appareil | 55 |
automation_detected | Bot ou outil d'automatisation | Appareil | 70 |
auto_clicker | Auto-clicker sur le téléphone | Appareil | 55 |
scripted_behavior | Page utilisée par un script | Appareil | 45 |
emulator_or_vm | Émulateur ou machine virtuelle | Appareil | 45 |
browser_tampering | Navigateur modifié / usurpation | Appareil | 35 |
device_rooted | Téléphone rooté | Appareil | 25 |
incognito | Navigation privée | Appareil | 10 |
device_spoofed | Faux modèle de téléphone | Appareil | 55 |
emulator_profile | Profil par défaut d'émulateur | Appareil | 60 |
cloud_phone | Téléphone cloud | Appareil | 60 |
phone_farm_display | Écran de ferme de téléphones | Appareil | 45 |
device_market_mismatch | Téléphone chinois à l'étranger | Appareil | 25 |
storage_cleared_reappeared | Cookies effacés puis retour sur le site | Appareil | 15 |
no_device_data | Aucune donnée d'appareil reçue | Appareil | 15 |
request_reused | Jeton d'appareil réutilisé par un autre compte | Appareil | 40 |
device_farm_proxy | Ferme de téléphones sur proxys résidentiels | Appareil | 85 |
device_clones | Clones d'appareil : un téléphone qui réinitialise son identité pour se faire passer pour de nombreux nouveaux comptes sur un réseau | Appareil | 55 |
payout_address_shared | Adresse de paiement utilisée par d'autres comptes | Compte | 70 |
email_disposable | E-mail jetable | Compte | 25 |
email_invalid | Adresse e-mail qui ne peut recevoir aucun message (domaine de messagerie inexistant) | Compte | 35 |
email_shared | Même e-mail qu'un autre compte | Compte | 50 |
phone_shared | Téléphone partagé avec d'autres comptes | Compte | 50 |
phone_voip | Numéro VoIP ou virtuel | Compte | 40 |
phone_invalid | Numéro de téléphone invalide | Compte | 30 |
many_devices_per_account | Trop d'appareils sur un même compte | Compte | 20 |
flagged_on_network | Signalé sur d’autres sites protégés par CashoutGuard | Compte | 45 |
face_same_person | Même visage que d'autres comptes | Compte | 0 |
offer_completed_too_fast | Offre terminée anormalement vite | Comportement | 40 |
slow_after_click | Conversion longtemps après le clic | Comportement | 0 |
conversion_velocity | Trop de conversions par heure | Comportement | 30 |
conversion_without_click | Conversion sans clic depuis votre offerwall | Comportement | 35 |
click_conversion_country_mismatch | Clic dans un pays, conversion dans un autre | Comportement | 30 |
offer_country_mismatch | Le pays de l'offre diffère de celui de l'utilisateur | Comportement | 45 |
click_velocity | Trop de clics sur des offres en quelques minutes | Comportement | 35 |
earnings_spike | Gains bien au-dessus d'un utilisateur normal | Comportement | 30 |
source_high_reversals | Source avec beaucoup de rétrofacturations | Comportement | 45 |
offer_high_reversals | Offre avec beaucoup de rétrofacturations | Comportement | 35 |
duplicate_transaction | ID de transaction en double | Comportement | 60 |
offer_repeated_other_publisher | Même offre déjà faite via un autre éditeur | Comportement | 85 |
offer_repeated_other_account | Même offre déjà faite par un autre compte sur cet appareil | Comportement | 85 |
offer_farm_burst | Vague de ferme : beaucoup de nouveaux comptes avec des appareils de ferme sur cette offre en quelques heures | Comportement | 30 |
signup_burst | Rafale d'inscriptions : plusieurs comptes inscrits à quelques secondes d'intervalle depuis des IP différentes | Comportement | 25 |
face_check_required | Vérification faciale requise, pas encore faite | Comportement | 0 |
face_check_failed | Vérification faciale échouée | Comportement | 0 |
publisher_farm_wave | Vague de ferme de son éditeur : la plupart des nouveaux comptes envoyés sur cette offre venaient de téléphones cloud ou d’émulateurs | Comportement | 55 |
cashout_velocity | Trop de retraits par jour | Comportement | 30 |
new_account_cashout | Retrait depuis un compte tout juste créé | Comportement | 20 |
blocklisted | Sur votre liste noire | Vos listes | 100 |
Les poids sont combinés par un OU bruité (noisy-OR) : score = 100 x (1 - produit de (1 - poids/100)). Deux signaux de 40 donnent 64, et non 80. Une correspondance avec la liste noire donne toujours 100.
Webhooks
Définissez une URL de webhook dans le tableau de bord. Nous envoyons un POST pour chaque événement noté block (event.blocked), quand un compte passe à block (account.blocked) et quand votre équipe décide d’un retrait (cashout.decided). Ignorez les types que vous n’utilisez pas.
Chaque requête est signée avec votre secret de webhook. Le HMAC-SHA256 hexadécimal du corps brut figure dans l'en-tête X-CashoutGuard-Signature
Répondez avec n'importe quel statut 2xx. Les envois échoués sont retentés jusqu'à 5 fois : après 10 secondes, 1 minute, 5 minutes et 15 minutes.
Statut des comptes
GET /v1/accounts/{account_id} renvoie la vue de risque actuelle d'un compte sans créer d'événement.
POST /v1/accounts/{account_id}/status permet à vos propres outils de modération de modifier le statut d'un compte.
active: Scoring normal.allowlisted: Les scores sont plafonnés sous le seuil de vérification. Pour les utilisateurs de confiance et l'équipe.blocked: Chaque événement renvoie block avec un score de 100.
Annulations et précision
POST /v1/outcomes indique ce qu'il est advenu des conversions après notre score : annulées par l'offerwall ou l'annonceur (chargeback) ou confirmées valides. Nous rapprochons vos identifiants et mesurons le moteur sur votre propre trafic dans la page Précision.
Sans code : importez l'export mensuel des annulations de vos offerwalls (CSV ou Excel) dans la page Précision. Nous trouvons la colonne d'identifiants automatiquement.
PHP, Node.js, Python, Android
Aucun SDK nécessaire. Un simple appel HTTP avec un délai d'expiration court suffit. Exemple complet de retrait :
- cashoutguard.php: PHP 7.4+, un seul fichier, sans Composer. Un require et c'est tout.
- composer require cashoutguard/cashoutguard-php: le même client PHP avec son README, pour votre dossier vendor.
- npm install @cashoutguard/node: Node 18+, types TypeScript inclus.
- cashoutguard-wordpress.zip: extension WordPress : le script navigateur sur toutes les pages, inscriptions et connexions évaluées, et des hooks pour les retraits, les conversions d'offerwall et les annulations.
Erreurs et limites
| HTTP | Corps | Signification |
|---|---|---|
| 401 | missing_or_invalid_secret_key | Clé secrète manquante, incorrecte ou révoquée. La réponse donne la cause dans "reason" (missing, public_key, unknown_secret, placeholder, malformed) et la correction dans "hint". |
| 404 | not_found | Compte introuvable (endpoints de compte). |
| 422 | { "message", "errors" } | Corps invalide. errors liste chaque champ. |
| 429 | Too Many Requests | Limite de requêtes atteinte. L'API serveur autorise 3 000 requêtes par minute et par site. |
| 401 · agent.js | unknown_public_key | Le data-key n'est la clé publique d'aucun site. Recopiez pk_live_... depuis Installation. |
| 403 · agent.js | origin_not_allowed | Le domaine de la page n'est pas dans les origines autorisées du site. Ajoutez-le dans Sites ou laissez la liste vide. localhost fonctionne toujours. agent.js l'explique aussi dans la console du navigateur. Les WebViews d'applications envoient l'origine "null" : ajoutez null à la liste pour les accepter. Le SDK Android natif n'a besoin de rien. |
| 403 | account_suspended | Le compte est suspendu, en général pour une facture impayée. Vérifiez la facturation. |
| 503 | temporarily_unavailable | Courte panne de notre côté. Laissez passer l'action et réessayez plus tard. |
Fail open : si l'appel expire ou échoue, autorisez l'action et consignez-la. Ne laissez jamais une panne de notre côté bloquer vos utilisateurs.