CashoutGuard

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.

ChampTypeObligatoireDescription
eventstringObligatoiresignup, login, offer_click, conversion, cashout ou custom.
account_idstringObligatoireVotre ID utilisateur, 191 caractères max. Utilisez un ID opaque, pas un e-mail.
request_idstringFacultatifFourni par collect() dans le navigateur. Valable 1 heure. Sans lui, nous n'avons que les données réseau.
ipstringFacultatifL'IP de l'utilisateur. Nécessaire uniquement si vous n'avez pas de request_id, par exemple pour les postbacks.
emailstringFacultatifStocké sous forme de hash. Détecte les domaines jetables et les e-mails partagés entre comptes.
payout_addressstringFacultatifE-mail PayPal, wallet ou compte qui reçoit l'argent. Stocké sous forme de hash.
payout_methodstringFacultatifTexte libre, par exemple paypal, usdt ou giftcard.
amountnumberFacultatifMontant du retrait ou de la conversion.
currencystringFacultatifCode de devise, par exemple USD. USD par défaut pour les retraits.
offer_idstringFacultatifID de l'offre, pour offer_click et conversion. Sert à mesurer la vitesse des offres.
offer_countrystringFacultatifPays pour lequel l'offre paie (code ISO, ex. US), sur les conversions. Un utilisateur dans un autre pays est signalé.
geo_countrystringFacultatifVotre 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_idstringFacultatifID de transaction réseau d'une conversion. Sert à détecter les doublons.
offer_namestringFacultatifTitre de l'offre tel que vos utilisateurs le voient. Affiché partout dans le tableau de bord à la place de l'id.
click_idstringFacultatifVotre id de clic. Envoyez la même valeur sur offer_click et sur la conversion pour les relier.
sourcestringFacultatifOrigine de l'événement, par exemple le nom de l'offerwall ou du réseau.
metaobjectFacultatifJusqu'à 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.

  • score va de 0 à 100. Les raisons se combinent : plusieurs signaux moyens s'additionnent sans jamais dépasser 100.
  • reasons liste 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.confidence indique à quel point l'appareil semble unique. Les empreintes peuvent être falsifiées, nous ne prétendons donc jamais à la certitude.
  • linked_accounts liste les autres comptes qui partagent un appareil, une adresse de paiement ou un e-mail avec celui-ci.
  • cashout n'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écisionScore (par défaut)action en shadowaction en enforceQue faire
allow0-49allowallowContinuez normalement.
review50-79allowreviewSuspendez le retrait ou demandez une vérification supplémentaire. Il apparaît dans votre file de validation.
block80-100allowblockStoppez 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énementQuand l'envoyerCe qu'il détecte
signupCompte crééMulti-comptes, bots, inscriptions via VPN, e-mails jetables
loginConnexion réussiePartage de compte, changements de pays, nouveaux appareils
offer_clickL'utilisateur ouvre une offreLance le chrono pour la vitesse des offres
conversionPostback de l'offerwall ou du réseauOffres terminées trop vite, transactions en double, rafales de conversions
cashoutL'utilisateur demande un retraitAdresses de paiement partagées, fréquence des retraits, retraits depuis des comptes récents
customTout autre événement à scorerSignaux 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.

CodeSignificationGroupePoids
ip_torNœud de sortie TorRéseau60
ip_vpnConnexion VPNRéseau45
ip_datacenterIP de datacenter / hébergeurRéseau40
timezone_mismatchLe fuseau horaire du navigateur ne correspond pas à la localisation de l'IPRéseau20
webrtc_ip_leakFuite de la vraie IP, située dans un autre paysRéseau30
ip_multi_timezoneMême IP avec de nombreux fuseaux horaires (sortie de proxy)Réseau50
proxy_farm_ipPlusieurs comptes sur une IP, tous avec la même horloge étrangère (proxy partagé)Réseau60
provider_rotationBeaucoup de fournisseurs d'accès en quelques jours, avec une horloge étrangère (proxy résidentiel rotatif)Réseau45
privacy_relayiCloud Private Relay (fonction de confidentialité)Réseau5
proxy_headersRequête passée par un proxyRéseau20
geo_sources_disagreeLes sources de géolocalisation divergentRéseau45
ip_shared_with_accountsMême IP utilisée par d'autres comptes aujourd'huiRéseau25
country_changedChangement de paysRéseau15
impossible_travelVoyage impossible : des IP de deux pays éloignés en quelques heuresRéseau50
locale_country_mismatchLangue du navigateur d'un autre paysRéseau30
phone_country_mismatchTéléphone d'un autre pays que l'IPRéseau20
ip_flagged_on_networkIP bloquée sur d'autres sitesRéseau25
device_shared_with_accountsAppareil partagé avec d'autres comptesAppareil55
automation_detectedBot ou outil d'automatisationAppareil70
auto_clickerAuto-clicker sur le téléphoneAppareil55
scripted_behaviorPage utilisée par un scriptAppareil45
emulator_or_vmÉmulateur ou machine virtuelleAppareil45
browser_tamperingNavigateur modifié / usurpationAppareil35
device_rootedTéléphone rootéAppareil25
incognitoNavigation privéeAppareil10
device_spoofedFaux modèle de téléphoneAppareil55
emulator_profileProfil par défaut d'émulateurAppareil60
cloud_phoneTéléphone cloudAppareil60
phone_farm_displayÉcran de ferme de téléphonesAppareil45
device_market_mismatchTéléphone chinois à l'étrangerAppareil25
storage_cleared_reappearedCookies effacés puis retour sur le siteAppareil15
no_device_dataAucune donnée d'appareil reçueAppareil15
request_reusedJeton d'appareil réutilisé par un autre compteAppareil40
device_farm_proxyFerme de téléphones sur proxys résidentielsAppareil85
device_clonesClones d'appareil : un téléphone qui réinitialise son identité pour se faire passer pour de nombreux nouveaux comptes sur un réseauAppareil55
payout_address_sharedAdresse de paiement utilisée par d'autres comptesCompte70
email_disposableE-mail jetableCompte25
email_invalidAdresse e-mail qui ne peut recevoir aucun message (domaine de messagerie inexistant)Compte35
email_sharedMême e-mail qu'un autre compteCompte50
phone_sharedTéléphone partagé avec d'autres comptesCompte50
phone_voipNuméro VoIP ou virtuelCompte40
phone_invalidNuméro de téléphone invalideCompte30
many_devices_per_accountTrop d'appareils sur un même compteCompte20
flagged_on_networkSignalé sur d’autres sites protégés par CashoutGuardCompte45
face_same_personMême visage que d'autres comptesCompte0
offer_completed_too_fastOffre terminée anormalement viteComportement40
slow_after_clickConversion longtemps après le clicComportement0
conversion_velocityTrop de conversions par heureComportement30
conversion_without_clickConversion sans clic depuis votre offerwallComportement35
click_conversion_country_mismatchClic dans un pays, conversion dans un autreComportement30
offer_country_mismatchLe pays de l'offre diffère de celui de l'utilisateurComportement45
click_velocityTrop de clics sur des offres en quelques minutesComportement35
earnings_spikeGains bien au-dessus d'un utilisateur normalComportement30
source_high_reversalsSource avec beaucoup de rétrofacturationsComportement45
offer_high_reversalsOffre avec beaucoup de rétrofacturationsComportement35
duplicate_transactionID de transaction en doubleComportement60
offer_repeated_other_publisherMême offre déjà faite via un autre éditeurComportement85
offer_repeated_other_accountMême offre déjà faite par un autre compte sur cet appareilComportement85
offer_farm_burstVague de ferme : beaucoup de nouveaux comptes avec des appareils de ferme sur cette offre en quelques heuresComportement30
signup_burstRafale d'inscriptions : plusieurs comptes inscrits à quelques secondes d'intervalle depuis des IP différentesComportement25
face_check_requiredVérification faciale requise, pas encore faiteComportement0
face_check_failedVérification faciale échouéeComportement0
publisher_farm_waveVague de ferme de son éditeur : la plupart des nouveaux comptes envoyés sur cette offre venaient de téléphones cloud ou d’émulateursComportement55
cashout_velocityTrop de retraits par jourComportement30
new_account_cashoutRetrait depuis un compte tout juste crééComportement20
blocklistedSur votre liste noireVos listes100

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 :

Erreurs et limites

HTTPCorpsSignification
401missing_or_invalid_secret_keyClé 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".
404not_foundCompte introuvable (endpoints de compte).
422{ "message", "errors" }Corps invalide. errors liste chaque champ.
429Too Many RequestsLimite de requêtes atteinte. L'API serveur autorise 3 000 requêtes par minute et par site.
401 · agent.jsunknown_public_keyLe data-key n'est la clé publique d'aucun site. Recopiez pk_live_... depuis Installation.
403 · agent.jsorigin_not_allowedLe 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.
403account_suspendedLe compte est suspendu, en général pour une facture impayée. Vérifiez la facturation.
503temporarily_unavailableCourte 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.