Référence de l'API

L'API RevRake.

Tout ce que la plateforme d'un opérateur envoie à RevRake, ce qui revient, et ce que RevRake envoie aux trackers des affiliés et aux propres systèmes de l'opérateur. Pour l'ordre dans lequel procéder, lisez le guide d'intégration.

URL de base

L'API se compose de quatre endpoints signés pour la plateforme de l'opérateur, deux pour écrire et deux pour lire, et du lien de tracking qu'ouvrent les visiteurs. Les endpoints signés sont aussi publiés sous forme de fichier OpenAPI 3.1.

EndpointSignification
POST https://app.revrake.com/v1/postbackDéclare un événement, ou un lot d'événements. Signé.
POST https://app.revrake.com/v1/filesEnvoie un fichier quotidien REG ou SALES. Signé.
GET https://app.revrake.com/v1/reportsLit un rapport, en JSON ou en CSV. Signé, avec une clé opérateur ou une clé affilié.
GET https://app.revrake.com/v1/commissionsLit le grand livre des commissions, page par page. Signé, avec une clé opérateur ou une clé affilié.
GET https://app.revrake.com/v1/affiliatesListe les affiliés de la marque. Signé, avec la clé de l'opérateur.
GET https://app.revrake.com/v1/linksListe les liens de suivi. Signé, avec une clé d'opérateur ou d'affilié.
GET https://app.revrake.com/v1/dealsListe les accords avec les conditions en vigueur. Signé, avec la clé de l'opérateur.
GET https://track.revrake.com/c/{code}Le lien de tracking : enregistre le clic et redirige. Non signé.

Les portails sur app.revrake.com utilisent leurs propres endpoints derrière une session connectée. Ce n'est pas une API publique, et ils peuvent changer sans préavis. Les rapports, le grand livre et les lignes de paiement peuvent aussi y être téléchargés en CSV.

Clés

Il existe deux sortes de clé. Chacune a un identifiant de clé et un secret, et chacune signe les requêtes de la même façon. Les clés ne se partagent jamais : chaque partie a la sienne.

L'identifiant de clé commence parÀ qui elle appartientCe qu'elle peut faire
pb_L'opérateur. Une clé active par marque, sur la page Intégration du portail opérateur. Un admin de l'espace de travail peut voir le secret et renouveler la clé.Envoie des événements et des fichiers. Lit les rapports et le grand livre de tout le programme, ou d'un seul affilié avec affiliate=<number>.
ak_Un affilié. Chaque affilié crée la sienne dans l'onglet API de son portail affilié. Le secret est affiché une seule fois, à la création de la clé.Lit les rapports et les commissions de cet affilié, et rien d'autre. Elle ne peut pas envoyer d'événements : /v1/postback et /v1/files répondent 401 unknown_key.

Ne donnez jamais la clé opérateur à un affilié. Elle lit les chiffres de tous les affiliés et peut écrire des événements. Un affilié qui veut un accès par API crée sa propre clé.

  • Une nouvelle clé révoque l'ancienne immédiatement, pour les deux sortes. Un affilié peut aussi révoquer sa clé et n'en avoir aucune.
  • Une clé affilié ne fonctionne que tant que l'affilié est approuvé dans le programme. Sinon, une lecture répond 403 affiliate_inactive.
  • Avec une clé affilié, le paramètre affiliate est ignoré et by=affiliate est traité par jour.
  • La limite de débit des lectures est comptée par clé.

Authentification

Chaque requête /v1 est signée avec une clé. Chaque requête porte trois en-têtes :

En-têteValeur
X-RevRake-KeyVotre identifiant de clé. Il commence par pb_ (opérateur) ou ak_ (affilié).
X-RevRake-TimestampL'heure Unix actuelle en secondes. Une requête est acceptée à cinq minutes près, avant ou après.
X-RevRake-SignatureLe HMAC-SHA256 en hexadécimal minuscule, avec votre secret, de <timestamp>.<body> pour un POST et de <timestamp>.<path>?<query> pour un GET.

Pour signer une requête :

  1. Construisez le corps et gardez les octets exacts que vous allez envoyer.
  2. Prenez l'heure Unix actuelle en secondes, en texte décimal.
  3. Concaténez le timestamp, un point (.) et le corps.
  4. Calculez le HMAC-SHA256 de ce texte avec votre secret et écrivez-le en hexadécimal minuscule.
  5. Envoyez le corps inchangé, avec les trois en-têtes.

Signer une lecture

Un GET n'a pas de corps : la signature porte donc sur la cible de la requête, c'est-à-dire le chemin et la query exactement tels qu'ils sont envoyés, par exemple 1790000000./v1/reports?by=link&days=7. Sans query, c'est le timestamp, un point et le chemin. Construisez l'adresse une fois, signez ce texte et envoyez ce texte : un client qui réordonne ou réencode la query après la signature casse la signature.

  • La clé est vérifiée d'abord, puis le timestamp, puis la signature.
  • L'en-tête Content-Type n'est pas obligatoire. Envoyez application/json pour les événements et text/csv pour les fichiers.
  • Une clé renouvelée cesse de fonctionner immédiatement.

Exemples de signature

Chaque exemple a une seule fonction de signature, envoie une inscription puis lit un rapport. Remplacez l'identifiant de clé et le secret par les vôtres.

curl
KEY="pb_your_key_id"
SECRET="your_secret"
BASE="https://app.revrake.com"

# sign <payload>: hex HMAC-SHA256 of "<timestamp>.<payload>" under the secret
sign() { printf '%s.%s' "$TS" "$1" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //'; }

# Write: the payload is the body.
TS=$(date +%s)
BODY='{"type":"registration","player_id":"PLR-1001","click_id":"CID_FROM_LANDING_URL"}'
curl -X POST "$BASE/v1/postback" \
  -H "Content-Type: application/json" \
  -H "X-RevRake-Key: $KEY" \
  -H "X-RevRake-Timestamp: $TS" \
  -H "X-RevRake-Signature: $(sign "$BODY")" \
  -d "$BODY"

# Read: the payload is the path and the query, exactly as sent.
TS=$(date +%s)
TARGET='/v1/reports?by=link&days=7'
curl "$BASE$TARGET" \
  -H "X-RevRake-Key: $KEY" \
  -H "X-RevRake-Timestamp: $TS" \
  -H "X-RevRake-Signature: $(sign "$TARGET")"
Node.js
import { createHmac } from 'node:crypto';

const KEY = 'pb_your_key_id';
const SECRET = 'your_secret';
const BASE = 'https://app.revrake.com';

// hex HMAC-SHA256 of "<timestamp>.<payload>" under the secret
function sign(secret, timestamp, payload) {
  return createHmac('sha256', secret).update(`${timestamp}.${payload}`).digest('hex');
}

function headers(payload) {
  const ts = Math.floor(Date.now() / 1000).toString();
  return { 'X-RevRake-Key': KEY, 'X-RevRake-Timestamp': ts, 'X-RevRake-Signature': sign(SECRET, ts, payload) };
}

// Write: the payload is the body. Send the same string you signed.
const body = JSON.stringify({ type: 'registration', player_id: 'PLR-1001', click_id: 'CID_FROM_LANDING_URL' });
const write = await fetch(`${BASE}/v1/postback`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', ...headers(body) },
  body,
});
console.log(write.status, await write.json());

// Read: the payload is the path and the query, exactly as sent.
const target = '/v1/reports?by=link&days=7';
const read = await fetch(BASE + target, { headers: headers(target) });
console.log(read.status, await read.json());
Python
import hashlib
import hmac
import json
import time
import urllib.error
import urllib.request

KEY = "pb_your_key_id"
SECRET = "your_secret"
BASE = "https://app.revrake.com"


def sign(secret: str, timestamp: str, payload: bytes) -> str:
    """hex HMAC-SHA256 of "<timestamp>.<payload>" under the secret"""
    return hmac.new(secret.encode(), timestamp.encode() + b"." + payload, hashlib.sha256).hexdigest()


def call(method: str, target: str, body: bytes | None = None):
    ts = str(int(time.time()))
    # Write: the payload is the body. Read: the path and the query, as sent.
    payload = body if body is not None else target.encode()
    headers = {"X-RevRake-Key": KEY, "X-RevRake-Timestamp": ts, "X-RevRake-Signature": sign(SECRET, ts, payload)}
    if body is not None:
        headers["Content-Type"] = "application/json"
    req = urllib.request.Request(BASE + target, data=body, method=method, headers=headers)
    try:
        with urllib.request.urlopen(req, timeout=10) as res:
            return res.status, json.load(res)
    except urllib.error.HTTPError as e:
        return e.code, json.load(e)


event = {"type": "registration", "player_id": "PLR-1001", "click_id": "CID_FROM_LANDING_URL"}
print(*call("POST", "/v1/postback", json.dumps(event).encode()))
print(*call("GET", "/v1/reports?by=link&days=7"))
PHP
<?php
const KEY = 'pb_your_key_id';
const SECRET = 'your_secret';
const BASE = 'https://app.revrake.com';

// hex HMAC-SHA256 of "<timestamp>.<payload>" under the secret
function sign(string $secret, string $timestamp, string $payload): string
{
    return hash_hmac('sha256', $timestamp . '.' . $payload, $secret);
}

function call(string $method, string $target, ?string $body = null): array
{
    $ts = (string) time();
    // Write: the payload is the body. Read: the path and the query, as sent.
    $payload = $body ?? $target;
    $headers = [
        'X-RevRake-Key: ' . KEY,
        'X-RevRake-Timestamp: ' . $ts,
        'X-RevRake-Signature: ' . sign(SECRET, $ts, $payload),
    ];
    $ch = curl_init(BASE . $target);
    curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10]);
    if ($body !== null) {
        $headers[] = 'Content-Type: application/json';
        curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
    }
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    $answer = curl_exec($ch);
    return [curl_getinfo($ch, CURLINFO_RESPONSE_CODE), json_decode($answer, true)];
}

$event = ['type' => 'registration', 'player_id' => 'PLR-1001', 'click_id' => 'CID_FROM_LANDING_URL'];
print_r(call('POST', '/v1/postback', json_encode($event)));
print_r(call('GET', '/v1/reports?by=link&days=7'));
Go
package main

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"time"
)

const (
	key    = "pb_your_key_id"
	secret = "your_secret"
	base   = "https://app.revrake.com"
)

// sign is the hex HMAC-SHA256 of "<timestamp>.<payload>" under the secret.
func sign(secret, timestamp string, payload []byte) string {
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(timestamp + "."))
	mac.Write(payload)
	return hex.EncodeToString(mac.Sum(nil))
}

func call(method, target string, body []byte) (int, string, error) {
	ts := strconv.FormatInt(time.Now().Unix(), 10)
	// Write: the payload is the body. Read: the path and the query, as sent.
	payload := body
	if body == nil {
		payload = []byte(target)
	}
	req, err := http.NewRequest(method, base+target, bytes.NewReader(body))
	if err != nil {
		return 0, "", err
	}
	req.Header.Set("X-RevRake-Key", key)
	req.Header.Set("X-RevRake-Timestamp", ts)
	req.Header.Set("X-RevRake-Signature", sign(secret, ts, payload))
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	res, err := (&http.Client{Timeout: 10 * time.Second}).Do(req)
	if err != nil {
		return 0, "", err
	}
	defer res.Body.Close()
	answer, err := io.ReadAll(res.Body)
	return res.StatusCode, string(answer), err
}

func main() {
	event := []byte(`{"type":"registration","player_id":"PLR-1001","click_id":"CID_FROM_LANDING_URL"}`)
	fmt.Println(call("POST", "/v1/postback", event))
	fmt.Println(call("GET", "/v1/reports?by=link&days=7", nil))
}

Vecteur de test

Avec ces entrées, votre code doit produire exactement ces signatures :

Secret
test_secret
Timestamp
1791072000
Corps
{"type":"registration","player_id":"PLR-1001","btag":"1001_1"}
Signature
115d8c73c6069d8953a36ca3a68c114da6232e4ba31889fd519d41ab1350a603
Cible de la requête d'une lecture
/v1/reports?by=link&days=7
Signature de la lecture
ed1ed72dd7ddef22c762f63b8919011e7c36c7214b55b72693c5927fb4b911d0

Limites

Voici toutes les limites que le produit applique aujourd'hui aux intégrations, au tracking et aux affiliés, avec leurs valeurs. La connexion, la réinitialisation du mot de passe et les candidatures ont aussi des limites contre les abus.

LimiteSignification
120Lectures par clé et par minute : GET /v1/reports et GET /v1/commissions ensemble
30 000Requêtes d'événements par clé et par minute : POST /v1/postback, jusqu'à 1 000 d'un coup
60Fichiers par clé et par minute : POST /v1/files, jusqu'à 10 d'un coup
4 MBCorps de POST /v1/postback, un événement ou un lot
32 MBCorps de POST /v1/files
1 – 1000Événements dans un lot
200 000Lignes de données dans un fichier
300 sÉcart entre X-RevRake-Timestamp et l'heure réelle, dans un sens ou dans l'autre
24 hJusqu'où ts et period peuvent être dans le futur
100Longueur de player_id, en caractères
200Longueur de txn_id, en caractères
0 / 2Décimales d'un montant : celles de la devise (deux pour la plupart, aucune pour des devises comme XOF, UGX ou JPY)
1 – 365days d'un rapport
10, 25, 50, 100, 500, 1000per_page du grand livre : l'une de ces valeurs
1 – 100 000page du grand livre
100Longueur de q, en caractères ; un texte plus long est tronqué
200Longueur de sub1 à sub5, de chaque tag UTM et de chaque click ID de régie publicitaire sur un lien de tracking, en caractères ; les valeurs plus longues sont tronquées
10Destinations de postback par affilié
2000Longueur d'un modèle d'adresse de postback, en caractères
5 sTemps dont dispose un tracker pour répondre à un postback
50Journal des entrées affiché dans le portail : dernières entrées des 7 derniers jours
2000Longueur de l'adresse d'un endpoint de webhook, en caractères
5 sTemps dont dispose un endpoint de webhook pour répondre
20Tentatives de webhook échouées d'affilée avant que l'endpoint soit désactivé
100Envois par endpoint de webhook affichés dans le journal des envois
2 – 40Longueur d'un code promo : lettres, chiffres, traits d'union et tirets bas
32 MBTaille d'un fichier que RevRake récupère à votre adresse
60 sTemps dont votre adresse dispose pour livrer un fichier récupéré
0 – 3600 sSecondes après le clic pour la règle de délai d'inscription (0 la désactive)
0 – 365Jours avant qu'une alerte de fraude ouverte se décide seule (0 signifie jamais)
1 – 200Alertes levées ou confirmées en une seule décision groupée
0 – 10 000 (20)Clics par minute sur un lien depuis une même adresse avant que l'alerte de rafale de clics soit levée (0 la désactive ; 20 si vous ne la changez pas)
60 + 1 / minLiens de tracking qu'un affilié peut créer : 60 d'un coup, puis un par minute. Au-delà, la réponse est 429 avec Retry-After
2 MBTaille d'une image de bannière téléversée dans le portail : PNG, JPEG, GIF ou WebP

Les deux endpoints d'écriture, POST /v1/postback et POST /v1/files, sont limités par clé (voir le tableau). Chaque réponse porte X-RateLimit-Limit et X-RateLimit-Remaining ; un refus est un 429 avec Retry-After. Envoyez au rythme de vos événements et utilisez des lots pour un rattrapage. Le lien de tracking n'a pas de limite de débit ; une rafale inhabituelle de clics sur un lien depuis une même adresse lève un signalement de fraude et est tout de même redirigée.

La limite de débit des lectures

  • Chaque clé peut faire 120 lectures par minute. Le quota est une réserve de 120 : une lecture en prend une, et une revient toutes les demi-secondes. Une clé restée inactive peut faire 120 lectures d'un coup ; ensuite, deux par seconde.
  • Chaque réponse à une lecture porte X-RateLimit-Limit (toujours 120) et X-RateLimit-Remaining (combien de lectures il reste à cet instant).
  • Au-delà de la limite, la réponse est 429 rate_limited avec un en-tête Retry-After : le nombre de secondes à attendre. Attendez, puis renvoyez la même requête, signée avec un timestamp frais.
  • La limite est comptée par clé : la clé de l'opérateur et la clé de chaque affilié ont chacune leur quota.
HTTP/1.1 200 OK
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
Retry-After: 1

{"error":"rate_limited","message":"Too many requests: at most 120 reads a minute per key. One more is allowed every half second; wait the seconds in Retry-After and try again."}

Gérer un 429

// read(target) signs one GET with a fresh timestamp and sends it,
// as in the signing samples.
async function readWithRetry(target, tries = 5) {
  for (let i = 0; i < tries; i++) {
    const res = await read(target);
    if (res.status !== 429) return res;
    const seconds = Number(res.headers.get('Retry-After')) || 1;
    await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
  }
  throw new Error('still rate limited after ' + tries + ' tries');
}

POST /v1/postback

Déclare un événement de la plateforme de l'opérateur. Le corps est un objet JSON avec un type et les champs de ce type, ou un tableau JSON de tels objets (un lot).

POST https://app.revrake.com/v1/postback
  • type vaut registration, deposit, chargeback ou ngr.
  • Un champ qui n'est pas listé pour le type est refusé avec un 422, de sorte qu'une faute de frappe n'est jamais ignorée en silence.
  • ts est l'heure de l'événement au format RFC 3339 (2026-10-04T08:15:00Z). S'il est omis, l'heure de la requête est utilisée. Il doit porter un décalage UTC (Z ou +03:00) ; une heure sans décalage est refusée. Il ne peut pas être à plus d'un jour dans le futur.
  • Un montant est un décimal, en texte JSON ou en nombre JSON : "25.00" ou 25.5. Pas de séparateur de milliers, pas d'exposant, et pas plus de décimales que la devise n'en a.
  • currency est un code ISO 4217 à trois lettres. Les minuscules sont acceptées. S'il est omis, la devise de reporting de l'espace de travail est retenue.
  • Les espaces en début et en fin des valeurs de texte sont retirées, sauf pour click_id et btag : envoyez-les exactement tels qu'ils sont arrivés sur la landing page.

registration

Un nouveau joueur. Envoyez click_id, btag, promo_code, ou plusieurs d'entre eux ; sans aucun, le joueur est enregistré comme non attribué. Le joueur est attribué une seule fois, ici. Réponse : attribution.

{"type":"registration","player_id":"PLR-1001","click_id":"01K6QF3M8Z5T7W9XB2C4D6E8GH","btag":"1001_1"}
ChampTypeObligatoireExempleSignification
player_idstringoui"PLR-1001"Votre identifiant du joueur. Jusqu'à 100 caractères. La clé de l'événement.
click_idstringnon"01K6QF3M8Z5T7W9XB2C4D6E8GH"Le cid avec lequel le joueur est arrivé : 26 caractères, lettres et chiffres ; majuscules ou minuscules.
btagstringnon"1001_1"Le btag que votre plateforme a stocké sur le joueur : <numéro d'affilié>_<numéro de lien>, uniquement des chiffres.
promo_codestringnon"GARAGE50"Le code promo que le joueur a saisi à l'inscription. Insensible à la casse. Il n'attribue le joueur que si click_id et btag ne le font pas.
email_hashstringnon"9f86d081…0f00a08"SHA-256 de l'e-mail en minuscules, en hexadécimal. Utilisé uniquement par le contrôle d'auto-parrainage.
email_domainstringnon"example.com"Le domaine de l'e-mail du joueur, la partie après le @ (example.com). Utilisé uniquement par la règle e-mail jetable. N'envoyez que le domaine : si une adresse complète arrive, seul le domaine est gardé sur le joueur, mais le journal des entrées montre toujours la requête telle qu'elle est arrivée.
ipstringnon"203.0.113.7"L'adresse IP à l'inscription. Elle est hachée, et utilisée uniquement par le contrôle d'auto-parrainage et pour trouver le clic correspondant à un btag.
countrystringnon"KE"Le pays du joueur : deux lettres, ISO 3166-1 alpha-2 (GB, KE). Les minuscules sont acceptées. Une valeur qui n'a pas deux lettres laisse le joueur sans pays. Sans ce champ, le pays du clic qui a amené le joueur est utilisé. Un deal peut lister des pays pour lesquels il ne paie pas.
tsstringnon"2026-10-04T08:15:00Z"Quand le joueur s'est inscrit. La fenêtre d'attribution est mesurée à partir de là.
{
  "status": "accepted",
  "event_type": "registration",
  "event_id": "0199b2c4-6f1e-7a53-9d0c-2b8f4e1a7c33",
  "attribution": {
    "attributed": true,
    "reason": "click",
    "affiliate_number": 1001,
    "link_number": 1,
    "click_id": "01K6QF3M8Z5T7W9XB2C4D6E8GH",
    "policy_version": 1,
    "window_days": 30,
    "model": "last_click"
  }
}

deposit

Envoyez un événement deposit par dépôt, avec votre identifiant de transaction dans txn_id. Le deal compare son minimum CPA au premier dépôt ou aux dépôts cumulés, déduction faite des chargebacks. La qualification crée une commission CPA par joueur, en rétention.

{"type":"deposit","player_id":"PLR-1001","txn_id":"TX-88213","amount":"25.00","currency":"KES","first":true}
ChampTypeObligatoireExempleSignification
player_idstringoui"PLR-1001"Votre identifiant du joueur. Jusqu'à 100 caractères.
txn_idstringoui"TX-88213"Votre identifiant de transaction. Jusqu'à 200 caractères. La clé de l'événement.
amountstring | numberoui"25.00"Le dépôt, supérieur à zéro.
currencystringnon"KES"La devise du dépôt. Toute devise est acceptée.
tsstringnon"2026-10-04T08:20:00Z"Quand le dépôt a été fait. Détermine quelle version du deal et quel taux de change du jour s'appliquent.
firstbooleannontruetrue s'il s'agit du premier dépôt du joueur. S'il est omis, le dépôt compte comme premier si RevRake n'en connaît aucun antérieur pour ce joueur.
{
  "status": "accepted",
  "event_type": "deposit",
  "event_id": "0199b2c9-03aa-7e10-b6d2-91c07f5e2a18",
  "commission": {
    "effect": "created",
    "type": "cpa",
    "amount_minor": 4000,
    "status": "hold"
  }
}

chargeback

Envoyez un événement chargeback avec son propre txn_id et le txn_id du dépôt initial dans ref_txn_id. Il annule ce dépôt en totalité. Pour le CPA cumulé, la commission est reprise seulement si le total admissible restant passe sous le minimum : une commission en rétention est annulée ; une commission approuvée ou payée reçoit une ligne d’annulation.

{"type":"chargeback","txn_id":"CB-5001","ref_txn_id":"TX-88213"}
ChampTypeObligatoireExempleSignification
txn_idstringoui"CB-5001"Votre identifiant du chargeback lui-même. Jusqu'à 200 caractères. La clé de l'événement.
ref_txn_idstringoui"TX-88213"Le txn_id du dépôt concerné par le chargeback.
player_idstringnon"PLR-1001"Facultatif. Si fourni, doit correspondre au joueur du dépôt référencé.
tsstringnon"2026-10-09T11:00:00Z"Quand le chargeback a eu lieu.
{
  "status": "accepted",
  "event_type": "chargeback",
  "event_id": "0199cc10-7b2d-7c41-a3f0-5d2e8b9c1f47",
  "commission": {
    "effect": "cancelled",
    "type": "cpa",
    "amount_minor": 4000,
    "status": "rejected"
  }
}

ngr

Le revenu net de jeu (NGR) d'un joueur pour un jour ou un mois. Il rapporte le RevShare de la version du deal en vigueur à la fin de la période, en rétention. Le joueur doit être inscrit. Réponse : commission.

{"type":"ngr","player_id":"PLR-1001","period":"2026-10-03","amount":"118.40","currency":"KES"}
ChampTypeObligatoireExempleSignification
player_idstringoui"PLR-1001"Votre identifiant du joueur. Jusqu'à 100 caractères.
periodstringoui"2026-10-03"Le jour (YYYY-MM-DD) ou le mois (YYYY-MM) que couvre le montant. Un mois et son premier jour sont deux périodes différentes. Pas à plus d'un jour dans le futur.
amountstring | numberoui"118.40"Le revenu net de jeu (NGR) de la période. Négatif quand le joueur a gagné.
currencystringnon"KES"La devise du montant.
ggrstring | numbernon"240.00"Facultatif. Le revenu brut de jeu de la période, dans la même devise. Montré à l'affilié ; il ne change pas la commission.
bonusesstring | numbernon"80.00"Facultatif. Les bonus déduits du revenu brut. Affiché seulement.
feesstring | numbernon"21.60"Facultatif. Les frais déduits du revenu brut, comme les frais de paiement ou de plateforme. Affiché seulement.
taxesstring | numbernon"20.00"Facultatif. Les taxes déduites du revenu brut. Affiché seulement.
tsstringnon"2026-10-04T02:00:00Z"Quand le chiffre a été déclaré.
{
  "status": "accepted",
  "event_type": "ngr",
  "event_id": "0199b2d1-5c0e-7b42-8a17-3e9d6f0c4b21",
  "commission": {
    "effect": "created",
    "type": "revshare",
    "amount_minor": 2960,
    "status": "hold"
  }
}

Un click_id, un btag, un promo_code ou un player_id qui contient { ou } est refusé : cela signifie que votre plateforme a envoyé un placeholder sans le renseigner.

La réponse

Un événement qui a été enregistré, ou qui l'était déjà, reçoit un 200 et un objet JSON. Les champs qui ne s'appliquent pas sont omis.

ChampTypeSignification
statusstringaccepted, ou duplicate quand l'événement était déjà enregistré.
event_typestringLe type que vous avez envoyé.
event_idstringL'identifiant RevRake de l'événement enregistré, un UUID. Le même à chaque renvoi.
attributionobjectPour une inscription : la décision.
attribution.attributedbooleanSi le joueur appartient à un affilié.
attribution.reasonstringPourquoi. Voir le tableau ci-dessous.
attribution.affiliate_numberintegerL'affilié. Seulement quand le joueur est attribué.
attribution.link_numberintegerLe lien de l'affilié. Seulement quand le joueur est attribué.
attribution.click_idstringLe clic retenu, quand il y en a un.
attribution.policy_versionintegerLa version de la politique d'attribution de la marque qui a décidé.
attribution.window_daysintegerLa fenêtre d'attribution de cette version, en jours.
attribution.modelstringlast_click ou first_click : le modèle de cette version.
commissionobjectPour un dépôt, un chargeback et ngr : ce que l'événement a fait au grand livre.
commission.effectstringcreated, cancelled (une commission en rétention a été rejetée), reversed (une ligne d'annulation a été ajoutée) ou none.
commission.typestringcpa, revshare, adjustment ou reversal.
commission.amount_minorintegerLe montant en unités mineures de la devise du deal (4000 vaut 40.00). Négatif pour une annulation et pour un ajustement à la baisse.
commission.statusstringhold, approved ou rejected.
commission.reasonstringPourquoi rien n'a été créé, quand effect vaut none. Voir le tableau ci-dessous.
waiting_for_playerbooleantrue pour un dépôt dont le joueur n'est pas encore inscrit. Il est conservé et valorisé quand l'inscription arrive.
duplicate_ofstringPour une ancienne correspondance de premier dépôt entre événements en direct et anciens fichiers quotidiens : le event_id précédent. Les récapitulatifs SALES actuels utilisent une couverture quotidienne et conservent chaque identifiant de transaction réelle.
detailstringAvec duplicate_of : une phrase disant quel dépôt antérieur celui-ci répète.
{
  "status": "accepted",
  "event_type": "deposit",
  "event_id": "0199b2c9-03aa-7e10-b6d2-91c07f5e2a18",
  "waiting_for_player": true
}
{
  "status": "duplicate",
  "event_type": "deposit",
  "event_id": "0199b31f-8c02-7a66-b4e1-2d7f9a3c6e15",
  "duplicate_of": "0199b2c9-03aa-7e10-b6d2-91c07f5e2a18",
  "detail": "The same deposit (TX-88213) was already reported through the API; this one adds nothing."
}
{
  "status": "duplicate",
  "event_type": "deposit",
  "event_id": "0199b2ca-11d4-7f09-8c55-0a6e2d4b7f90",
  "commission": { "effect": "none", "reason": "not_first" }
}

Une commission est créée avec le statut hold. Elle est approuvée automatiquement à la fin de la période de rétention du deal, sauf si quelque chose la retient encore : une alerte de fraude ouverte, un affilié qui n'est pas approuvé, ou un taux de change manquant.

Raisons d'attribution

Le click ID est essayé d'abord. S'il n'est pas l'un des vôtres, le btag est essayé. Quand la politique dit que le btag l'emporte et que le btag correspond à un lien, le btag est essayé d'abord. Un btag qui correspond à un lien attribue le joueur même quand aucun clic n'est trouvé. Un code promo est essayé en dernier, seulement si ni le click ID ni le btag n'ont attribué le joueur. La politique, sa fenêtre et lequel des deux l'emporte se règlent sur la page Intégration ; chaque changement est une nouvelle version. Quand la politique fait primer le code promo, un code qui correspond à un lien est essayé avant les deux, et un code qui ne correspond pas revient à eux. Un accord peut fixer sa propre fenêtre, qui remplace celle de la politique pour les clics sur les liens de ses affiliés.

reasonSignification
clickAttribuéLe click ID correspond à un clic dans la fenêtre.
first_clickAttribuéLa politique est au premier clic : le click ID correspond à un clic dans la fenêtre, et un clic compté plus ancien du même visiteur (même adresse et même appareil) dans la fenêtre a été crédité à sa place.
btag_clickAttribuéLe btag correspond à un lien, et un clic sur ce lien a été trouvé dans la fenêtre.
btag_onlyAttribuéLe btag correspond à un lien ; aucun clic n'a été trouvé dans la fenêtre.
promo_codeAttribuéNi le click ID ni le btag n'ont attribué le joueur, et le code promo correspond au code d'un affilié approuvé.
no_tagNon attribuéL'inscription ne portait ni click ID ni btag.
unknown_clickNon attribuéLe click ID n'est pas l'un des vôtres, et aucun btag n'a été envoyé.
unknown_btagNon attribuéLe btag est mal formé ou ne correspond à aucun lien.
outside_windowNon attribuéLe clic est plus ancien que la fenêtre d'attribution, ou postérieur à l'inscription.
affiliate_inactiveNon attribuéL'affilié n'est pas approuvé, ou ne l'était pas au moment du clic.
unknown_promo_codeNon attribuéSeul un code promo a été envoyé, et il ne correspond à aucun de vos codes.
promo_code_retiredNon attribuéSeul un code promo a été envoyé, et il a été retiré.

Pourquoi aucune commission n'a été créée

commission.reasonSignification
unattributedLe joueur n'appartient à aucun affilié.
not_firstCe n'est pas le premier dépôt du joueur.
already_earnedLe CPA du joueur a déjà été gagné. Un second « premier » dépôt ne paie rien et déclenche une alerte.
no_deal_versionL'affilié n'a pas de deal, ou le deal n'avait pas de version au moment de l'événement.
country_blockedLe joueur vient d'un pays pour lequel le deal ne paie pas. Il reste enregistré et attribué.
no_cpa_in_dealLe deal ne paie pas de CPA.
below_thresholdLe premier dépôt ou les dépôts cumulés nets restent sous le minimum du deal, selon sa base CPA.
no_commissionPour un chargeback : le dépôt n'avait rien rapporté.
no_revshare_in_dealPour ngr : le deal ne paie pas de RevShare.
fx_rate_missingPour ngr : il n'y a pas encore de taux de change pour la période. La déclaration est enregistrée et valorisée quand un taux existe.
unchangedPour ngr : un chiffre corrigé qui ne change rien à la commission.

Lots

Le corps peut être un tableau JSON de 1 à 1 000 événements, envoyé au même endpoint avec les mêmes en-têtes. La signature porte sur le tableau entier. Les événements sont traités dans l'ordre, chacun séparément, de sorte qu'un événement refusé n'arrête jamais les autres.

Un lot qui a pu être lu reçoit un 200, même quand tous ses événements ont été refusés. Regardez les compteurs.

[
  {"type":"deposit","player_id":"PLR-1001","txn_id":"TX-88213","amount":"25.00","first":true},
  {"type":"ngr","player_id":"PLR-1001","period":"2026-10-03","amount":"118.40"},
  {"type":"ngr","player_id":"PLR-1002","period":"October","amount":"-12.50"}
]
{
  "results": [
    {
      "index": 0,
      "status": "accepted",
      "event_type": "deposit",
      "event_id": "0199b2c9-03aa-7e10-b6d2-91c07f5e2a18",
      "commission": { "effect": "created", "type": "cpa", "amount_minor": 4000, "status": "hold" }
    },
    {
      "index": 1,
      "status": "duplicate",
      "event_type": "ngr",
      "event_id": "0199b2d1-5c0e-7b42-8a17-3e9d6f0c4b21",
      "commission": { "effect": "created", "type": "revshare", "amount_minor": 2960, "status": "hold" }
    },
    {
      "index": 2,
      "status": "rejected",
      "field": "period",
      "message": "period must be a day (2026-10-03) or a month (2026-10), not in the future."
    }
  ],
  "accepted": 1,
  "duplicate": 1,
  "rejected": 1
}
ChampTypeSignification
resultsarrayUne entrée par événement, dans l'ordre d'envoi.
results[].indexintegerLa position de l'événement dans le tableau, à partir de 0.
results[].statusstringaccepted, duplicate ou rejected.
results[].event_typestringComme dans une réponse simple. Absent pour un événement refusé.
results[].event_idstringComme dans une réponse simple. Absent pour un événement refusé.
results[].attributionobjectComme dans une réponse simple.
results[].commissionobjectComme dans une réponse simple.
results[].waiting_for_playerbooleanComme dans une réponse simple.
results[].fieldstringPour un événement refusé : le champ à corriger.
results[].messagestringPour un événement refusé : ce qu'il faut corriger.
acceptedintegerCombien d'événements ont été enregistrés.
duplicateintegerCombien étaient déjà enregistrés.
rejectedintegerCombien ont été refusés.

POST /v1/files

Envoie un fichier CSV quotidien, au lieu de l'importer sur la page Intégration ou de laisser RevRake le récupérer à une adresse où votre plateforme le publie. Le corps est le fichier lui-même ; les en-têtes et la signature sont ceux d'un événement, et la signature porte sur le timestamp et le fichier. Le nom du fichier va dans le paramètre de query filename ; il est affiché dans le portail et ne sert à rien d'autre.

POST https://app.revrake.com/v1/files?filename=sales-2026-10-03.csv

La ligne d'en-tête détermine de quel fichier il s'agit : un fichier avec une colonne registration_date est un fichier REG, un fichier avec une colonne activitydate est un fichier SALES. Les noms de colonnes sont comparés sans tenir compte de la casse. Les colonnes qui ne sont pas listées ci-dessous peuvent rester dans le fichier ; elles sont ignorées.

Formats acceptés : YYYY-MM-DD, YYYY-MM-DD HH:MM:SS, YYYY-MM-DDTHH:MM:SS, RFC 3339 ou YYYY/MM/DD. Sans décalage UTC, le fuseau des fichiers défini dans les Paramètres s’applique (UTC par défaut). Un horodatage avec décalage désigne un instant précis ; pour SALES, son jour civil dans le fuseau des fichiers est retenu. Le jour et le mois ne sont jamais devinés : 30/09/2026 est refusé.

Fichier REG : une ligne par joueur inscrit

ColonneObligatoireSignification
registration_dateouiQuand le joueur s'est inscrit. Devient ts.
player_idouiVotre identifiant du joueur.
btagouiLe btag stocké sur le joueur. Peut être vide : le joueur n'est alors pas attribué, sauf si un code promo l'attribue.
promo_codenonFacultatif. Le code promo que le joueur a saisi à l'inscription, utilisé comme promo_code d'une inscription.
player_countrynonFacultatif. Le code pays du joueur sur deux lettres (ISO 3166-1 alpha-2), utilisé comme country d'une inscription. Vide : le pays du clic.
brand_name,registration_date,btag,promo_code,player_id,player_country
Example,2026-10-03,1001_1,,PLR-1001,KE
Example,2026-10-03,,,PLR-1002,KE
Example,2026-10-03,,GARAGE50,PLR-1003,KE

Fichier SALES : une ligne par joueur et par jour

ColonneObligatoireSignification
activitydateouiLe jour d’activité, déterminé dans le fuseau des fichiers de l’espace de travail.
player_idouiVotre identifiant du joueur.
currencyouiLa devise des montants de la ligne. Vide : la devise de reporting de l'espace de travail.
depositsouiTotal brut des dépôts du jour, y compris le premier dépôt effectué ce jour-là. Ce n’est pas un cumul historique ; ne déduisez pas les chargebacks.
first_deposit_amountouiMontant du premier dépôt, uniquement le jour où il a eu lieu ; zéro les autres jours. Déjà compris dans deposits, jamais ajouté une seconde fois. Il ne peut dépasser le total brut des dépôts du jour.
chargebackouiTotal des chargebacks de ce jour d’activité, séparé des dépôts bruts. Ce n’est pas un cumul historique. Il réduit les dépôts cumulés pour le CPA, sans identifier une transaction précise.
*_net_revenuenonToute colonne nommée net_revenue ou dont le nom se termine par _net_revenue. Les cellules renseignées sont additionnées dans le revenu net du joueur pour ce jour, déclaré comme ngr. Une ligne où aucune n'est renseignée ne déclare aucun revenu.
*_gross_revenuenonFacultatif. Les colonnes nommées gross_revenue ou dont le nom se termine par _gross_revenue, additionnées dans le ggr du ngr du jour. Montré à l'affilié seulement.
*_bonusnonFacultatif. Les colonnes nommées bonus ou dont le nom se termine par _bonus, additionnées dans bonuses. Affiché seulement.
*_feesnonFacultatif. Les colonnes nommées fees ou dont le nom se termine par _fees, additionnées dans fees. Affiché seulement.
*_taxesnonFacultatif. Les colonnes nommées taxes ou dont le nom se termine par _taxes, additionnées dans taxes. Affiché seulement.
brand_name,activitydate,player_id,btag,currency,deposits,first_deposit_amount,chargeback,casino_net_revenue,sports_net_revenue
Example,2026-10-03,PLR-1001,1001_1,KES,3500.00,2500.00,0,118.40,0
Example,2026-10-03,PLR-1002,,KES,900.00,0,0,-12.50,
  • Chaque ligne SALES enregistre un récapitulatif monétaire quotidien et, si des cellules de revenu sont remplies, un événement NGR distinct. Ils sont traités indépendamment : refuser le récapitulatif monétaire n’annule pas le revenu accepté, ni l’inverse. Vérifiez tous les problèmes dans la réponse.
  • Un récapitulatif par marque, joueur et jour d’activité ; la première devise acceptée est définitive. Des montants identiques sont des replays, même après renommage ou réordonnancement du fichier. Modifier les montants, la devise ou les limites horaires d’un jour accepté crée un conflit, y compris pour des zéros. Les montants déjà enregistrés restent inchangés.
  • deposits, first_deposit_amount et chargeback sont obligatoires. Une cellule vide vaut zéro. Les montants doivent être des décimaux non négatifs respectant la précision de la devise de la ligne. Confirmez ces définitions avec votre plateforme avant de connecter son export.
  • Envoyez REG avant SALES. Les récapitulatifs monétaires reçus avant sont conservés et calculés à l’arrivée de l’inscription. Le NGR d’un joueur inconnu est refusé : après REG, renvoyez SALES ou NGR. Le récapitulatif monétaire conservé sera un replay.
  • Le récapitulatif couvre les postbacks individuels de son jour d’activité et de sa devise dans le fuseau des fichiers, sans additionner les montants deux fois. Les identifiants de transaction restent utilisables comme références. Des dépôts ou chargebacks connus dépassant le récapitulatif, ou des preuves contradictoires du premier dépôt, créent un conflit.
  • Pour le CPA cumulé, les dépôts moins les chargebacks doivent atteindre le minimum du deal. Chaque flux quotidien utilise le taux de change de sa date d’activité ; chaque postback utilise son propre jour UTC. Sans taux, le règlement reste en rétention sous fx_rate_missing. Un taux de chargeback manquant bloque le paiement sous chargeback_fx_missing jusqu’au rapprochement, même pour les commissions déjà incluses dans un paiement.
  • Pour le CPA sur premier dépôt, un chargeback agrégé peut ne pas identifier le dépôt annulé. Un récapitulatif monétaire ambigu d’un joueur déjà inscrit est refusé. S’il est arrivé avant REG, il est conservé et l’inscription réussit, mais le CPA créé reste en rétention sous chargeback_reference_missing. Pour vérifier la référence de transaction, envoyez un chargeback dont ref_txn_id contient l’identifiant de transaction du dépôt initial.
  • Le revenu est distinct des totaux monétaires immuables : modifier le NGR quotidien constitue une rectification, et la différence de commission devient un ajustement.

La réponse

Un fichier lisible renvoie 200. Les compteurs portent sur les événements, pas seulement les lignes : une ligne SALES peut enregistrer séparément un récapitulatif monétaire et du NGR, avec un succès partiel. Vérifiez toujours rejected et problems.

{
  "id": "0199b31e-2a40-7d18-9b6c-4f1e7a2c5d03",
  "filename": "sales-2026-10-03.csv",
  "kind": "sales",
  "uploaded_at": "2026-10-04T02:00:07Z",
  "rows_total": 2,
  "accepted": 3,
  "duplicates": 0,
  "rejected": 1,
  "skipped": 0,
  "problems": ["line 3: Player PLR-1002 is not registered. Send the registration first."]
}
ChampTypeSignification
idstringL'identifiant RevRake de cet import.
filenamestringLe nom que vous avez donné, ou reg.csv / sales.csv.
kindstringreg ou sales.
uploaded_atstringQuand il a été reçu.
rows_totalintegerLignes de données dans le fichier.
acceptedintegerÉvénements enregistrés.
duplicatesintegerÉvénements déjà enregistrés.
rejectedintegerLignes ou événements refusés. Chacun figure dans le journal des entrées, avec la ligne elle-même.
skippedintegerLignes sans rien à enregistrer.
problemsarray of stringLes 20 premières lignes refusées, sous la forme line N: reason. Omis quand il n'y en a pas.

GET /v1/reports

Renvoie le rapport de la page Rapports : clics, clics signalés, inscriptions, premiers dépôts et gains, groupés d'une seule façon, pour les jours jusqu'à aujourd'hui dans le fuseau horaire de l'espace de travail. Les chiffres du jour sont actualisés quand un rapport est demandé, au plus toutes les 30 secondes sur un programme chargé.

GET https://app.revrake.com/v1/reports?by=link&days=7
ParamètreTypeExempleSignification
bystringlinkComment les lignes sont groupées : day, link, sub1, country ou affiliate. Par défaut day. Quand affiliate est renseigné, by=affiliate est traité comme day.
daysinteger7Combien de jours, aujourd'hui compris : de 1 à 365. Par défaut 30.
fromstring2026-09-01Le premier jour d'une période quelconque, YYYY-MM-DD dans le fuseau horaire de l'espace de travail. Avec from ou to, days est ignoré. 366 jours au plus.
tostring2026-09-30Le dernier jour de la période, inclus. Par défaut aujourd'hui.
affiliateinteger1001Un numéro d'affilié. Restreint le rapport à cet affilié. Clé opérateur uniquement ; ignoré avec une clé affilié, qui lit toujours son propre affilié.
tagstringInfluencersLe nom d'un groupe, comme sur la page Groupes (répétez-le pour plusieurs : l'un d'eux). Clé de l'opérateur uniquement ; ignoré avec affiliate ou une clé d'affilié. Un nom inconnu donne 422 unknown_tag.
formatstringcsvcsv renvoie un fichier CSV au lieu de JSON.
{
  "by": "link",
  "from": "2026-09-28",
  "to": "2026-10-04",
  "currency": "KES",
  "rows": [
    {
      "key": "0199a7f0-41c2-7e55-b1d9-6c3a8e2f7b10",
      "label": "telegram-october",
      "clicks": 1840,
      "flagged_clicks": 212,
      "registrations": 96,
      "ftds": 31,
      "earnings_minor": 12400000
    }
  ],
  "total": {
    "key": "",
    "label": "",
    "clicks": 1840,
    "flagged_clicks": 212,
    "registrations": 96,
    "ftds": 31,
    "earnings_minor": 12400000
  }
}
ChampTypeSignification
bystringLe groupement qui a été utilisé.
fromstringLe premier jour du rapport, YYYY-MM-DD.
tostringLe dernier jour : aujourd'hui dans le fuseau horaire de l'espace de travail, ou le to que vous avez donné.
currencystringLa devise de reporting de l'espace de travail. Les gains sont dans cette devise.
rowsarrayUne entrée par groupe.
rows[].keystringCe qui identifie le groupe : le jour, l'identifiant RevRake du lien ou de l'affilié, la valeur de sub1, ou le pays en deux lettres.
rows[].labelstringLe texte que le portail affiche pour le groupe : le jour, le libellé de campagne du lien, la valeur de sub1, le pays, ou le nom et le numéro de l'affilié.
rows[].clicksintegerClics propres.
rows[].flagged_clicksintegerClics signalés comme bots, répétitions, rafales, ou d'un affilié qui n'est pas approuvé. Non comptés dans clicks.
rows[].registrationsintegerJoueurs attribués.
rows[].ftdsintegerPremiers dépôts.
rows[].earnings_minorintegerCommissions gagnées, en unités mineures de la devise de reporting, au taux du jour où chacune a été gagnée.
totalobjectLa somme des lignes, sous la forme d'une ligne.

En CSV

Avec format=csv, la réponse est un fichier. Sa première colonne porte le nom du groupement et contient le libellé ; viennent ensuite clicks, flagged_clicks, registrations, first_deposits et earnings_<currency> en décimal. La dernière ligne est le total.

link,clicks,flagged_clicks,registrations,first_deposits,earnings_KES
telegram-october,1840,212,96,31,124000.00
total,1840,212,96,31,124000.00

GET /v1/commissions

Renvoie une page du grand livre des commissions, les plus récentes d'abord sauf si vous triez autrement. Le montant d'une ligne n'est jamais modifié et une ligne n'est jamais supprimée, mais son status change après son écriture : hold, puis approved, puis paid, ou rejected. Une synchronisation doit relire une période et mettre à jour les lignes d'après leur id, pas seulement récupérer les nouvelles lignes.

GET https://app.revrake.com/v1/commissions?from=2026-09-01&to=2026-10-01&per_page=500
ParamètreTypeExempleSignification
statusstringapprovedhold, approved, paid ou rejected. Par défaut : tous.
fromstring2026-09-01Seulement les lignes survenues à cet instant ou après : RFC 3339, ou un jour YYYY-MM-DD en UTC.
tostring2026-10-01Seulement les lignes survenues avant cet instant. Mêmes formats.
affiliateinteger1001Un numéro d'affilié. Restreint les lignes, totals et owed à cet affilié. Clé opérateur uniquement ; ignoré avec une clé affilié.
tagstringInfluencersLe nom d'un groupe, comme sur la page Groupes (répétez-le pour plusieurs : l'un d'eux). Clé de l'opérateur uniquement ; ignoré avec affiliate ou une clé d'affilié. Un nom inconnu donne 422 unknown_tag.
qstringPLR-1001Les lignes dont l'identifiant du joueur ou le nom de l'affilié contient ce texte. Jusqu'à 100 caractères.
sortstringwhenwhen, affiliate, player, status, deposit ou commission. Par défaut when.
dirstringascasc ou desc. Par défaut desc.
pageinteger2La page, à partir de 1. Par défaut 1.
per_pageinteger50010, 25, 50, 100, 500 ou 1000. Par défaut 25.
{
  "currency": "KES",
  "totals": { "hold": 1240000, "approved": 3720000, "paid": 9800000 },
  "owed": { "KES": { "hold": 1240000, "approved": 3720000, "paid": 9800000 } },
  "period": true,
  "commissions": [
    {
      "id": "0199b2c9-0a11-7c3e-9f20-7d4b1e6a2c58",
      "occurred_at": "2026-09-14T08:20:00Z",
      "type": "cpa",
      "currency": "KES",
      "gross_currency": "KES",
      "reporting_minor": 400000,
      "affiliate_name": "Odds Garage",
      "affiliate_number": 1001,
      "player_ref": "PLR-1001",
      "deal_name": "CPA 4000",
      "deal_version": 2,
      "gross_minor": 250000,
      "rate_snapshot": 400000,
      "amount_minor": 400000,
      "status": "approved",
      "status_reason": "",
      "hold_until": "2026-10-14T08:20:00Z",
      "status_note": ""
    }
  ],
  "total": 1,
  "page": 1,
  "per_page": 500
}
ChampTypeSignification
currencystringLa devise de reporting de l'espace de travail.
totalsobjectLa somme par statut, en unités mineures de la devise de reporting : sur tout le grand livre, ou sur la période quand from ou to est donné. Non restreinte par les autres filtres, sauf affiliate. Les lignes qui attendent encore un taux de change n'y figurent pas.
owedobjectLa somme par devise et par statut, dans la devise propre à chaque commission : ce qui est dû. Sur les mêmes lignes que totals.
periodbooleantrue quand from ou to a été donné : totals et owed ne couvrent alors que cette période.
commissionsarrayLes lignes de cette page.
totalintegerCombien de lignes correspondent aux filtres.
pageintegerLa page qui a été renvoyée.
per_pageintegerLe nombre de lignes par page qui a été utilisé.

Une ligne de commission

ChampTypeSignification
idstringL'identifiant RevRake de la ligne, un UUID. Il ne change jamais : mettez à jour d'après lui.
occurred_atstringQuand la commission a été gagnée : l'heure du dépôt, la fin de la période de revenu, ou l'heure du chargeback.
typestringcpa, revshare, adjustment, reversal ou tier.
amount_minorintegerLa commission, en unités mineures de currency. Négative pour une annulation et pour un ajustement à la baisse.
currencystringLa devise de la commission : celle du deal.
reporting_minorinteger | nullLa commission dans la devise de reporting de l'espace de travail, au taux du jour où elle a été gagnée. null tant que ce taux manque.
gross_minorintegerLe dépôt ou le revenu net à l'origine de la commission, en unités mineures de gross_currency.
gross_currencystringLa devise du dépôt ou du revenu.
rate_snapshotintegerLes conditions qui ont été appliquées : le montant du CPA en unités mineures, ou le RevShare en centièmes de pour cent (2500 vaut 25 %).
statusstringhold, approved, paid ou rejected. Voir les statuts d'une commission.
status_reasonstringPourquoi une ligne est encore en rétention ou a été rejetée. Vide sinon. Toutes les valeurs figurent dans le tableau des raisons.
status_notestringLa note de l'opérateur sur une commission retirée, telle que l'affilié la lit. Vide sinon.
hold_untilstring | nullQuand la période de rétention se termine. null pour une annulation.
affiliate_numberintegerLe numéro de l'affilié.
affiliate_namestringLe nom de l'affilié.
player_refstringVotre identifiant du joueur.
deal_namestringLe deal.
deal_versionintegerLa version du deal qui a valorisé la ligne.

Listes du programme : affiliés, liens, accords

Votre backend peut lister le programme tel que le portail le montre, signé comme les autres lectures et compté dans la même limite. GET /v1/affiliates et GET /v1/deals demandent la clé de l'opérateur ; avec une clé d'affilié ils répondent 403 operator_key_required. GET /v1/links répond les liens de la marque, ou ceux d'un affilié avec affiliate=<numéro> ; avec une clé d'affilié, les siens.

Les listes sont paginées avec page (à partir de 1) et per_page (de 1 à 500, 100 par défaut) et portent total. GET /v1/affiliates accepte aussi status : pending, approved, rejected ou suspended. Créer des affiliés ou des liens par l'API n'est pas encore proposé : dites-nous à quoi cela vous servirait.

GET https://app.revrake.com/v1/links?affiliate=1001&per_page=100

Chaque affilié

ChampTypeSignification
numberintegerLe numéro d'affilié, la première partie du btag ; dans un lien, le numéro de lien, sa seconde partie.
namestringLe nom de l'affilié.
emailstringL'adresse e-mail de l'affilié.
statusstringpending, approved, rejected ou suspended.
dealstringLe nom de l'accord de l'affilié ; vide avant l'approbation.
created_atstringDate de création, RFC 3339.
decided_atstring | nullQuand la candidature a été approuvée ou refusée, ou le compte suspendu ; null avant.

Chaque lien

ChampTypeSignification
affiliate_numberintegerLe numéro de l'affilié auquel appartient le lien.
numberintegerLe numéro d'affilié, la première partie du btag ; dans un lien, le numéro de lien, sa seconde partie.
codestringLe code dans l'adresse du lien.
urlstringLe lien de suivi, sur le domaine de suivi vérifié de la marque ou le domaine partagé.
btagstring<affiliate_number>_<number>.
campaignstringLe libellé de campagne du lien.
landing_pathstring | nullLa page de votre site où mène le lien ; null pour celle de la marque par défaut.
subsstring[5]sub1 à sub5 fixés sur le lien ; chaînes vides s'ils ne le sont pas.
promo_codestring | nullLe code promo, sur un lien créé pour un code ; sinon null.
vanity_urlstring | nullL'adresse courte /go/<nom>, si l'affilié en a choisi une ; sinon null.
created_atstringDate de création, RFC 3339.

Chaque accord

ChampTypeSignification
namestringLe nom de l'affilié.
currencystringLa devise dans laquelle l'accord rémunère et paie.
listedbooleanSi la page publique d'inscription montre l'accord.
defaultbooleanSi les nouveaux affiliés reçoivent cet accord.
versionintegerLa version de l'accord en vigueur.
effective_atstringQuand cette version est entrée en vigueur, RFC 3339.
termsobjectLes conditions de cette version : montants en unités mineures, parts en points de base (2500 = 25 %), jours de retenue, règle des mois négatifs, fenêtre d'attribution propre à l'accord, paliers.

Export vers l’entrepôt de données

Réglé sur la page Intégration : chaque nuit, RevRake écrit la veille de chaque marque dans votre propre bucket, sur tout service compatible S3, à <dossier>/<marque>/<AAAA-MM-JJ>/, le jour étant compté dans le fuseau horaire de l’espace de travail. Quatre fichiers CSV compressés en gzip (UTF-8, une ligne d’en-tête, guillemets selon la RFC 4180), puis manifest.json. Le manifeste est écrit en dernier : chargez un jour une fois qu’il existe, et vérifiez rows, bytes et sha256 de chaque fichier.

Une ligne appartient au jour où elle est arrivée (un clic : quand il a eu lieu) : un fichier quotidien en retard est dans le jour où il est arrivé, jamais perdu. Les heures sont en RFC 3339, UTC avec millisecondes ; les montants sont des décimaux dans leur devise. Les statuts sont ceux du moment de l’export : celui d’une commission change ensuite, relisez donc GET /v1/commissions pour le statut actuel. Un jour en échec est réessayé toutes les heures et rattrapé jusqu’à sept jours en arrière ; réexporter un jour écrase ses fichiers. De nouvelles colonnes peuvent être ajoutées en fin de fichier ; schema_version du manifeste change quand un chargeur doit changer.

manifest.json : schema_version, workspace, brand, day, timezone, from et to (les bornes du jour en UTC), generated_at et files : pour chacun, name, rows, bytes, sha256 (du fichier compressé).

clicks.csv.gz : tous les clics sur les liens de la marque ce jour-là, signalés compris.

ChampTypeSignification
click_idstringL’identifiant de clic, tel que passé à votre page d’arrivée et dans les postbacks.
clicked_atstringQuand le clic a eu lieu.
affiliate_numberintegerLe numéro d’affilié.
link_numberintegerLe numéro de lien.
btagstring<affiliate_number>_<link_number>.
countrystringCode pays à deux lettres ; vide s’il est inconnu.
devicestringdesktop, mobile, tablet ou bot, d’après le navigateur.
osstringLe système d’exploitation, d’après le navigateur.
browserstringLe navigateur.
sub1 … sub5stringsub1 à sub5 : les sous-identifiants du clic.
utm_source … utm_contentstringutm_source à utm_content : les balises UTM du clic.
gclid, fbclid, ttclid, msclkid, wbraidstringgclid, fbclid, ttclid, msclkid, wbraid : identifiants de clic des régies publicitaires dans l’URL d’arrivée.
refererstringLa page d’où venait le visiteur.
flagsstringPourquoi le clic n’est pas compté, séparé par ; : bot, duplicate, burst, inactive. Vide pour un clic propre.

players.csv.gz : les joueurs dont l’inscription est arrivée ce jour-là.

ChampTypeSignification
player_idstringVotre identifiant de joueur.
registered_atstringL’heure d’inscription que vous avez envoyée.
arrived_atstringQuand RevRake l’a enregistrée.
countrystringCode pays à deux lettres ; vide s’il est inconnu.
attributedbooleantrue quand un affilié est crédité.
attribution_reasonstringPourquoi, comme dans la réponse du postback : click, btag_only, promo_code, no_tag…
affiliate_numberintegerLe numéro d’affilié.
link_numberintegerLe numéro de lien.
btagstring<affiliate_number>_<link_number>.
click_idstringLe clic qui a crédité le joueur ; vide s’il n’y en a pas.
policy_versionintegerLa version de la politique d’attribution qui a décidé.

events.csv.gz : tous les événements acceptés de la marque arrivés ce jour-là, y compris les récapitulatifs quotidiens SALES, avec leur contenu.

ChampTypeSignification
event_idstringL’identifiant de l’événement dans RevRake.
typestringregistration, deposit, chargeback, daily_sales ou ngr ; pour une commission, cpa, revshare, tier, adjustment ou reversal.
external_idstringLa clé de l’événement : l’identifiant du joueur, de la transaction, ou joueur et période.
player_idstringVotre identifiant de joueur.
occurred_atstringQuand c’est arrivé, selon votre envoi ; pour une commission, quand elle a été gagnée.
received_atstringQuand RevRake a accepté l’événement.
sourcestrings2s (l’API) ou file (un fichier quotidien).
payloadJSONL’événement tel que RevRake l’a lu, en JSON.
resultJSONCe que RevRake a répondu, en JSON.

commissions.csv.gz : les lignes de commission écrites ce jour-là.

ChampTypeSignification
commission_idstringL’identifiant de la ligne, l’id de GET /v1/commissions.
created_atstringQuand la ligne a été écrite.
occurred_atstringQuand c’est arrivé, selon votre envoi ; pour une commission, quand elle a été gagnée.
typestringregistration, deposit, chargeback, daily_sales ou ngr ; pour une commission, cpa, revshare, tier, adjustment ou reversal.
statusstringhold, approved, paid ou rejected, au moment de l’export.
status_reasonstringPourquoi elle est retenue ou refusée ; vide sinon.
affiliate_numberintegerLe numéro d’affilié.
player_idstringVotre identifiant de joueur.
dealstringLe nom de l’accord.
deal_versionintegerLa version de l’accord qui l’a valorisée.
grossdecimalLe dépôt ou le revenu net derrière elle.
gross_currencystringSa devise.
amountdecimalLa commission.
currencystringLa devise de l’accord.
reporting_amountdecimalLa commission dans la devise de reporting ; vide tant que le taux de change manque.
reporting_currencystringLa devise de reporting de l’espace de travail.
reverses_idstringPour une annulation, la ligne annulée.
payout_item_idstringLa ligne de paiement qui l’a reprise, une fois le mois clôturé.

Idempotence

Chaque événement possède une clé naturelle. Un nouvel envoi identique renvoie la réponse conservée avec "status":"duplicate". La réponse d’un dépôt en attente d’inscription est mise à jour à son arrivée. Des données monétaires modifiées peuvent créer un conflit ; les rectifications NGR utilisent des révisions. Après un délai dépassé, réessayez avec un nouvel horodatage de signature.

ÉvénementCléLa même clé à nouveau
registrationplayer_idLa première réponse. L'attribution n'est pas décidée à nouveau, même si les tags diffèrent.
deposittxn_idLa réponse conservée, mise à jour à l’arrivée de l’inscription attendue. Modifier le joueur, le montant ou la devise crée un conflit. Un récapitulatif SALES couvrant la transaction ne supprime pas son identifiant.
chargebacktxn_idLa première réponse. Un second chargeback du même dépôt sous un nouveau txn_id est refusé.
ngrplayer_id et periodMême montant et même devise : la première réponse. Un montant différent : une correction, enregistrée comme une nouvelle révision, avec la différence en adjustment.
CSVLigne REG : player_idComme une registration.
CSVRécapitulatif monétaire SALES : marque, joueur et jour d’activitéDes totaux identiques sont des replays. Modifier les dépôts, le premier dépôt, les chargebacks, la devise ou les limites du jour crée un conflit, même pour un récapitulatif initialement nul.
CSVLigne SALES, revenu : le joueur et le jourComme ngr : un chiffre modifié dans un réexport est une correction.

Statuts et erreurs

Toutes les réponses que l'API peut donner et tous les statuts qu'un enregistrement peut avoir, au même endroit. Une erreur reçoit un objet JSON : error (le code), message (une phrase en anglais) et, pour invalid, field.

{"error":"invalid","field":"amount","message":"amount must be a positive decimal in KES, for example 25.00."}

Statuts HTTP et codes d'erreur

StatuterrorEndpointQuandQue faire
200–/v1/*La requête a été traitée. Pour un événement : enregistré, ou déjà enregistré. Pour un lot ou un fichier : lu ; regardez les compteurs, certains éléments ont pu être refusés.
400bad_requestPOST /v1/postbackLe corps commence comme un tableau mais n'est pas un tableau JSON d'événements.Corrigez la requête. La même requête renvoyée reçoit la même réponse.
401unknown_key/v1/*La clé dans X-RevRake-Key est inconnue ou révoquée, ou c'est une clé affilié sur un endpoint d'écriture.Utilisez la clé actuelle. La renvoyer ne sert à rien.
401bad_timestamp/v1/*X-RevRake-Timestamp est absent, n'est pas un nombre entier de secondes, ou est décalé de plus de cinq minutes.Vérifiez l'horloge et le code de signature, puis renvoyez-la avec un timestamp frais.
401bad_signature/v1/*La signature ne correspond pas au timestamp et au corps (pour une lecture : au chemin et à la query).Vérifiez l'horloge et le code de signature, puis renvoyez-la avec un timestamp frais.
403affiliate_inactiveGET /v1/*Une lecture avec une clé affilié dont l'affilié n'est pas approuvé dans le programme : en attente, rejeté ou suspendu.Rien à corriger dans la requête : le compte doit être approuvé par l'opérateur.
404unknown_affiliateGET /v1/*Une lecture avec une clé opérateur : aucun affilié ne porte le numéro donné dans affiliate.Corrigez la requête. La même requête renvoyée reçoit la même réponse.
405–/v1/*L'endpoint n'accepte pas cette méthode. Pas de corps JSON.Corrigez la requête. La même requête renvoyée reçoit la même réponse.
413too_largePOST /v1/*Le corps dépasse la limite de l'endpoint.Corrigez la requête. La même requête renvoyée reçoit la même réponse.
422invalidPOST /v1/*, GET /v1/reportsL'événement ou le fichier ne peut pas être utilisé tel quel.Corrigez la requête. La même requête renvoyée reçoit la même réponse.
429rate_limitedGET /v1/*Plus de lectures que le quota de la clé. La réponse porte Retry-After.Attendez le nombre de secondes indiqué dans Retry-After, puis renvoyez-la.
500internal/v1/*Quelque chose s'est mal passé de notre côté.Renvoyez-la.
  • Un lot sans événement ou de plus de 1 000 événements est refusé avec un 422 invalid, sans field.
  • Un 401 pour un timestamp ou une signature incorrects avec une clé opérateur figure dans le journal des entrées comme bad_signature. Une requête avec une clé inconnue, ou avec une clé affilié, n'est pas journalisée.
  • Une lecture ne répond 422 que pour une période de rapport inutilisable (from et to). Tout autre paramètre dont la valeur n'est pas autorisée est ignoré et sa valeur par défaut est utilisée. Vérifiez by, page et per_page dans la réponse.
  • Réessayez sur un délai dépassé, sur un 429 et sur un 500. Ne renvoyez aucun autre 4xx inchangé.

Ce que field désigne dans un 422

fieldQuand
bodyLe corps n'est pas du JSON valide, ou porte un champ inconnu ou mal formé (par exemple un ts qui n'est pas au format RFC 3339).
typetype est absent ou n'est pas l'un des quatre.
player_idAbsent ou de plus de 100 caractères ; un placeholder qui n'a pas été renseigné ; pour ngr, un joueur qui n'est pas inscrit.
click_idUn placeholder qui n'a pas été renseigné. Le message commence par misconfigured_macro.
btagUn placeholder qui n'a pas été renseigné. Le message commence par misconfigured_macro.
promo_codeUn placeholder qui n'a pas été renseigné. Le message commence par misconfigured_macro.
txn_idAbsent ou de plus de 200 caractères.
ref_txn_idAbsent ; aucun dépôt avec ce txn_id n'est connu ; ou le dépôt a déjà fait l'objet d'un chargeback.
amountPas un décimal que la devise autorise ; pour un dépôt, pas supérieur à zéro.
currencyPas un code à trois lettres.
ggrPour ngr : pas un montant décimal dans la devise.
bonusesPour ngr : pas un montant décimal dans la devise.
feesPour ngr : pas un montant décimal dans la devise.
taxesPour ngr : pas un montant décimal dans la devise.
tsÀ plus d'un jour dans le futur.
periodNi un jour ni un mois, ou dans le futur.
fromPour GET /v1/reports : from ou to n'est pas au format YYYY-MM-DD, from est après to, ou ils sont distants de plus de 366 jours.
filePour /v1/files : le fichier est vide, n'est pas du CSV, n'est ni un fichier REG ni un fichier SALES, n'a pas une colonne obligatoire, ou compte plus de 200 000 lignes.
chargebackDans un fichier SALES : la cellule chargeback ne peut pas être appliquée. Le message dit pourquoi.

Le statut d'un événement

Dans la réponse à un événement, et par événement dans les results d'un lot.

statusSignification
acceptedEnregistré maintenant. La réponse dit ce qu'il a produit.
duplicateDéjà conservé sous la même clé ; un replay identique ne change rien. Une ancienne correspondance de premier dépôt entre canaux peut aussi renvoyer duplicate_of. Les récapitulatifs SALES actuels conservent les transactions réelles des postbacks et ne comptent les montants quotidiens couverts qu’une fois.
rejectedSeulement dans un lot : l'événement a été refusé, avec field et message. Un événement seul qui est refusé reçoit un 422.

L'effet d'un événement sur le grand livre

commission.effectSignification
createdUne ligne de commission a été créée : un CPA, un RevShare, ou un ajustement pour un chiffre corrigé.
cancelledUn chargeback a rejeté une commission encore en rétention.
reversedUn chargeback a ajouté une ligne d'annulation en regard d'une commission déjà approuvée ou payée.
noneAucune ligne n'a été créée. reason dit pourquoi.

Types de commission

typeSignification
cpaUne par joueur lorsque le minimum du deal est atteint par le premier dépôt ou les dépôts cumulés nets de chargebacks, selon sa base CPA.
revshareUne part du revenu net de jeu (NGR) d'un joueur pour une période. Négative pour une période où le joueur a gagné.
adjustmentLa différence que produit un chiffre de revenu corrigé.
reversalL'opposé d'une commission reprise après son approbation ou son paiement : par un chargeback, ou par l'opérateur avec un motif. Elle est approuvée tout de suite et se déduit du paiement suivant.
tierUn complément issu d'un palier de volume du deal : dès qu'un affilié amène assez de nouveaux joueurs déposants dans un mois civil, la différence entre le taux du palier et le taux de base est ajoutée à côté de chaque commission de ce mois. Il attend dans la même rétention que sa commission, ou est approuvé tout de suite si elle l'est déjà. Négatif quand il reprend un complément antérieur, après le rejet ou l'annulation de sa commission.

Statuts d'une commission

Une ligne est écrite une fois et ses montants ne changent jamais. Son statut, lui, change :

statusSignification
holdEn attente : de la fin de la période de rétention du deal, ou de ce que status_reason désigne.
approvedLa période de rétention est terminée et rien ne retient la ligne. Elle entre dans le prochain lot de paiement mensuel. Les annulations et certains ajustements sont approuvés dès le départ.
paidL'élément de paiement qui contient la ligne a été marqué comme payé.
rejectedElle ne sera pas payée. status_reason dit pourquoi, et status_note porte la note de l'opérateur quand il y en a une.

Valeurs de status_reason

Vide si la ligne attend seulement sa période de rétention, et normalement pour les commissions approuvées ou payées. Un taux de chargeback manquant peut aussi bloquer une ligne approuvée. Les rétentions pour fraude attendent l’opérateur ou l’expiration configurée du signalement.

status_reasonAvec le statutSignification
fx_rate_missingholdUn taux de change nécessaire manque. Le règlement attend ce taux ; les flux quotidiens SALES utilisent leur date d’activité, les postbacks individuels leur jour UTC.
chargeback_fx_missinghold, approved, paidUn chargeback ne peut pas encore être évalué. Le CPA et ses compléments de palier ne peuvent intégrer un nouveau paiement ni être marqués payés dans un paiement existant avant l’arrivée du taux et la fin du rapprochement.
chargeback_reference_missingholdUn récapitulatif reçu avant l’inscription contient un chargeback agrégé qui n’identifie pas le dépôt annulé pour le CPA sur premier dépôt. L’inscription et le récapitulatif sont conservés ; le CPA créé attend en rétention la vérification de la référence de transaction.
self_referralholdUne alerte de fraude ouverte : le joueur semble être l'affilié lui-même. Attend que l'opérateur lève ou confirme l'alerte.
click_velocityholdUne alerte de fraude ouverte, ou un clic qui faisait partie d'une rafale : plus de clics sur un lien depuis une même adresse en une minute que la marque n'en autorise. Attend l'opérateur.
duplicate_conversionholdUne alerte de fraude ouverte : un second « premier » dépôt a été déclaré pour le joueur. Attend l'opérateur.
paid_trafficholdUne alerte de fraude ouverte : le clic qui a amené le joueur portait le click ID d'un réseau publicitaire. Attend l'opérateur.
disposable_emailholdUne alerte de fraude ouverte : le joueur s'est inscrit avec un domaine d'e-mail jetable. Attend l'opérateur.
geo_mismatchholdUne alerte de fraude ouverte : le joueur s'est inscrit dans un autre pays que celui du clic. Attend l'opérateur.
fast_registrationholdUne alerte de fraude ouverte : le joueur s'est inscrit dans le nombre de secondes fixé après le clic. Attend l'opérateur.
affiliate_pendingholdL'affilié n'était pas encore approuvé quand la commission a été gagnée. Une fois l'affilié approuvé, la ligne n'attend plus que sa période de rétention normale.
affiliate_rejectedholdLa candidature de l'affilié a été rejetée.
affiliate_suspendedholdL'affilié était suspendu quand la commission a été gagnée. Une fois l'affilié rétabli, la ligne n'attend plus que sa période de rétention normale.
chargebackrejected, approvedSur une ligne rejetée : un chargeback l'a annulée pendant sa rétention. Sur une ligne d'annulation : elle annule une commission après un chargeback.
below_thresholdrejectedUne fois les taux disponibles, le premier dépôt admissible ou les dépôts cumulés nets étaient sous le minimum du deal.
fraudrejected, approvedL'opérateur a confirmé l'alerte de fraude qui retenait la ligne, ou a retiré la commission pour fraude.
base_rejectedrejectedUn complément de palier dont la commission a été rejetée alors que les deux étaient en rétention.
self_excludedrejected, approvedL'opérateur a retiré la commission : le joueur s'est auto-exclu. Sur une ligne rejetée, la commission n'était pas encore dans un paiement ; sur une ligne d'annulation, elle l'était.
duplicate_accountrejected, approvedL'opérateur a retiré la commission : le joueur a un autre compte.
bonus_abuserejected, approvedL'opérateur a retiré la commission pour abus de bonus.
terms_breachrejected, approvedL'opérateur a retiré la commission pour manquement aux conditions du programme.
test_accountrejected, approvedL'opérateur a retiré la commission : le joueur était un compte de test.
otherrejected, approvedL'opérateur a retiré la commission pour une autre raison, écrite dans status_note.

Statuts des paiements

Un mois se clôture en un lot de paiement, avec un élément par affilié et par devise. Les lots et les éléments se traitent dans le portail ; l'API montre leur résultat par le statut paid des lignes de commission.

Statut du lotSignification
draftLe mois a été clôturé. L'opérateur examine les éléments.
approvedVerrouillé. Avec la facturation activée, chaque élément payable reçoit un numéro de facture. Le fichier bancaire (CSV) peut être téléchargé et les éléments payés.
paidTous les éléments à payer ont été marqués comme payés.
Statut de l'élémentSignification
payableÀ payer dans ce lot.
carriedInférieur au paiement minimum, ou pas supérieur à zéro : reporté au lot suivant.
heldL'affilié a une alerte de fraude ouverte, ou le programme retient les paiements jusqu'à la vérification d'identité et celle-ci n'est pas encore enregistrée. Le portail indique laquelle. L'opérateur peut libérer l'élément, et enregistrer la vérification d'identité le libère.
no_detailsL'affilié n'a pas saisi ses coordonnées de paiement. Absent du fichier bancaire.
written_offLe mois s'est terminé sous zéro et le deal de l'affilié ne reporte pas les mois négatifs : le solde est passé en perte à la clôture et rien n'est reporté sur le lot suivant. Avec un plafond, l'élément est carried et enregistre la partie au-delà du plafond comme passée en perte.
paidMarqué comme payé. Ses lignes de commission sont paid, et le tracker de l'affilié est informé.

Pourquoi une ligne de fichier est refusée

Une ligne refusée est comptée dans rejected, listée dans problems sous la forme line N: reason, et conservée dans le journal des entrées avec la ligne elle-même. En plus des raisons pour lesquelles un événement peut être refusé :

  • La ligne n’est pas un CSV valide.
  • REG : registration_date ou player_id manque ou est illisible.
  • SALES : activitydate ou player_id manque ou est illisible.
  • SALES, revenu : le joueur n’est pas inscrit.
  • SALES, montants : un montant est négatif, illisible ou comporte trop de décimales pour sa devise.
  • SALES : le premier dépôt dépasse le total brut des dépôts.
  • SALES : un jour accepté est réexporté avec des totaux monétaires, une devise ou des limites horaires modifiés.
  • SALES : les dépôts ou chargebacks individuels connus dépassent le récapitulatif du jour.
  • SALES : les données du premier dépôt contredisent un autre jour ou une transaction connue.
  • SALES : un chargeback agrégé est ambigu pour le CPA sur premier dépôt d’un joueur déjà inscrit.

Résultats dans le journal des entrées

StatutSignification
acceptedEnregistré, avec ce qu'il a produit.
duplicateDéjà enregistré ; la première réponse a été renvoyée.
rejectedRefusé ; le détail indique quel champ et pourquoi.
bad_signatureLe timestamp ou la signature était incorrect.
testUn événement de test depuis la page Intégration. Il suit le vrai parcours et n'enregistre rien.

États d'un envoi de postback

StatutSignification
queuedEn attente de son premier essai, ou du prochain nouvel essai. Le portail l'affiche comme en attente ou en nouvel essai.
deliveredLe tracker a répondu par un statut 2xx.
deadTous les nouveaux essais ont échoué. Le portail l'affiche comme abandonné ; l'affilié peut le relancer à la main.

Statuts d'un affilié

StatutSignification
pendingA candidaté ; l'opérateur n'a pas encore décidé.
approvedActif dans le programme.
rejectedLa candidature a été refusée.
suspendedArrêté par l'opérateur.

Le lien de tracking

Un lien de tracking est ouvert par un visiteur, pas appelé par votre plateforme. Il enregistre le clic et répond par une redirection 302 vers la landing page de la marque. Tant que la marque n'a pas vérifié son site, le visiteur voit d'abord une courte page indiquant où mène le lien. L'hôte de destination vient des réglages de la marque ; un lien ne peut choisir qu'un chemin sur celui-ci.

GET https://track.revrake.com/c/a8Kq2Zx?sub1=TRACKER_CLICK_ID&utm_source=telegram
HTTP/1.1 302 Found
Location: https://your-site.example/landing?btag=1001_1&cid=01K6QF3M8Z5T7W9XB2C4D6E8GH&utm_source=telegram
Cache-Control: no-store

Paramètres sur le lien

ParamètreSignification
sub1 … sub5Les valeurs propres à l'affilié, enregistrées avec le clic et renvoyées dans les postbacks. Une valeur dans l'adresse remplace celle fixée sur le lien.
utm_source utm_medium utm_campaign utm_term utm_contentEnregistrés avec le clic et transmis à la landing page. Une valeur dans l'adresse remplace celle fixée sur le lien.
gclid fbclid ttclid msclkid wbraidClick IDs des régies publicitaires. Enregistrés avec le clic ; disponibles comme macros de postback. Non transmis.

Paramètres ajoutés à l'URL d'arrivée

ParamètreSignification
cidLe click ID : 26 caractères en base32 de Crockford (chiffres et lettres majuscules sans I, L, O, U). Il se trie par ordre chronologique. Stockez-le comme du texte.
btag<numéro d'affilié>_<numéro de lien> : un à neuf chiffres, un tiret bas, un à neuf chiffres.
utm_source … utm_contentLes tags UTM du clic, quand ils ne sont pas vides.
  • Si l'URL d'arrivée de la marque contient {click_id} dans sa query, le click ID y est placé et cid n'est pas ajouté.
  • Un clic sur le lien d'un affilié qui n'est pas approuvé est enregistré et signalé, et arrive sans cid ni btag : il ne peut pas être attribué.
  • Les bots, les aperçus de liens, les requêtes HEAD, les requêtes sans user agent et les clics répétés dans l'heure sont enregistrés et signalés, pas écartés. Les rapports les excluent.
  • Les autres paramètres du lien ne sont ni enregistrés ni transmis.
  • Un lien auquel l'affilié a donné un nom court répond aussi à /go/<name>, exactement comme à /c/<code>. Une marque qui a son propre domaine de tracking vérifié sert aussi les deux chemins sur ce domaine.
  • Un code inconnu reçoit un 404 et une page d'erreur HTML, tout comme un lien dont la marque n'a pas encore défini de destination.

Postbacks vers les trackers des affiliés

Cette partie ne demande aucun travail à l'opérateur. Chaque affilié ajoute jusqu'à 10 destinations dans son portail : un modèle d'adresse avec des macros, et les événements dont il veut être informé. Quand un événement est enregistré pour l'un des joueurs de l'affilié, RevRake complète le modèle et l'appelle par une requête GET.

Événements

Événement{status}Quand
registrationleadUn joueur a été attribué à l'affilié.
ftdsaleUn premier dépôt a rapporté une commission CPA à l'affilié.
commission_approvedapprovedUne commission a été approuvée à la fin de sa période de rétention.
commission_rejectedrejectedUne commission en rétention a été rejetée après examen.
commission_paidpaidUne commission a été payée dans un lot de paiement.
chargebackrejectedUn chargeback a repris une commission.

Mots de statut par preset

Une destination créée à partir d'un preset envoie, dans {status}, les mots que son tracker connaît. Les presets de Voluum, PeerClick et FunnelFlux Pro, ainsi que « Other / custom », envoient les mots du tableau ci-dessus. Les mots suivent le preset que porte la destination ; son adresse enregistrée n'est jamais modifiée.

ÉvénementKeitaroBinomRedTrackBeMob
registrationleadleadothernew
ftdsalesalependingapproved
commission_approvedsalesaleapprovedapproved
commission_rejectedrejectedrejectdeclineddeclined
commission_paidsalesaleapprovedapproved
chargebackrejectedrejectdeclinedchargeback

Macros

Une macro est un nom entre accolades. Elle est remplacée par sa valeur, encodée pour l'URL. Les noms sont comparés sans tenir compte de la casse. Un nom que RevRake ne connaît pas est envoyé tel quel, de sorte qu'une faute de frappe apparaît dans le journal des envois. Une valeur qui n'est pas connue est vide.

MacroSignification
{click_id}Le click ID de RevRake (le cid de l'URL d'arrivée). Vide quand le joueur a été attribué par le btag seul.
{sub1} … {sub5}sub1 à sub5 du clic. sub1 porte généralement le click ID du tracker de l'affilié.
{event}Le nom de l'événement, comme dans le tableau ci-dessus.
{status}Le mot que les trackers emploient pour l'événement, comme dans le tableau ci-dessus, ou le mot propre au tracker pour une destination créée à partir d'un preset (voir le tableau suivant).
{event_id}Différent pour chaque événement et stable d'un nouvel essai à l'autre.
{conversion_id}Le même pour tous les événements concernant une même commission : ftd, commission_approved, commission_rejected, commission_paid et chargeback. Une inscription a le sien. Utilisez-le comme identifiant de transaction dans un tracker qui met à jour une conversion arrivant de nouveau sous le même identifiant, afin que le revenu soit compté une seule fois.
{player_id} {affiliate_id} {brand_id} {link_id}L'identifiant du joueur chez l'opérateur, le numéro d'affilié, le programme et le numéro de lien.
{campaign}Le libellé de campagne du lien.
{commission} {commission_minor}La commission, en décimal (40.00) ou en unités mineures (4000). Vide pour une inscription, 0 pour une commission rejetée, négative pour un chargeback.
{amount} {amount_minor}Le dépôt ou le revenu à l'origine de la commission, en décimal ou en unités mineures, dans la devise où il a été fait. Vide pour une inscription.
{amount_currency}Le code ISO de la devise du dépôt. Vide pour une inscription.
{commission_currency}Le code ISO de la devise de la commission : celle du deal. Vide pour une inscription.
{currency}Le code ISO de l'argent dont parle le postback : la devise de la commission quand il y a une commission, sinon celle du dépôt, sinon la devise de reporting de l'espace de travail.
{occurred_at} {occurred_at_unix}Quand cela s'est produit, au format RFC 3339 ou en secondes Unix.
{country} {device} {os} {browser} {referer}Ce que l'on sait du clic : pays, type d'appareil, système d'exploitation, navigateur, référent.
{utm_source} {utm_medium} {utm_campaign} {utm_term} {utm_content}Les tags UTM du clic.
{gclid} {fbclid} {ttclid} {msclkid} {wbraid}Les click IDs des régies publicitaires, quand le clic les portait.

Autres noms pour les mêmes macros

Les trackers emploient des noms différents pour les mêmes choses. Ceux-ci sont aussi acceptés :

{subid}= {sub1}
{clickid}= {click_id}
{payout}= {commission}
{txid}= {event_id}
{transaction_id}= {event_id}

Presets

Le portail affilié fournit ces modèles. L'affilié remplace l'hôte par celui de son tracker. Chacun suit la documentation de son tracker : {conversion_id} est dans le paramètre de transaction là où le tracker met à jour une conversion qui arrive de nouveau sous le même identifiant, {event_id} là où il écarterait les événements ultérieurs.

Keitarohttps://YOUR-TRACKER-DOMAIN/POSTBACK-KEY/postback?subid={sub1}&status={status}&payout={commission}&currency={currency}&tid={conversion_id}
Binomhttps://YOUR-TRACKER-DOMAIN/click?cnv_id={sub1}&payout={commission}&cnv_status={status}&cnv_currency={currency}
Voluumhttps://YOUR-TRACKER-DOMAIN/postback?cid={sub1}&payout={commission}&currency={currency}&txid={event_id}&et={event}
RedTrackhttps://YOUR-TRACKER-DOMAIN/postback?clickid={sub1}&sum={commission}&status={status}&type={event}&rdtk_event_id={event_id}
BeMobhttps://YOUR-TRACKER-DOMAIN/postback?cid={sub1}&payout={commission}&txid={conversion_id}&status={status}
PeerClickhttps://YOUR-TRACKER-DOMAIN/postback?cid={sub1}&payout={commission}&txid={conversion_id}&status={status}&currency={currency}
FunnelFlux Prohttps://YOUR-TRACKER-DOMAIN/pb/?hit={sub1}&tx={conversion_id}&rev={commission}&status={status}

Un exemple

https://YOUR-TRACKER-DOMAIN/postback?click_id={sub1}&event={event}&status={status}&payout={commission}&currency={currency}&tid={conversion_id}&eid={event_id}
GET https://tracker.example/postback?click_id=TRACKER_CLICK_ID&event=ftd&status=sale&payout=40.00&currency=KES&tid=0199b2c9-0a11-7c3e-9f20-7d4b1e6a2c58&eid=0199b2c9-03aa-7e10-b6d2-91c07f5e2a18%3Aftd

Envoi

  • Un postback est mis en file d'attente dans la même transaction que l'événement qu'il annonce, et envoyé en une dizaine de secondes, le plus ancien d'abord.
  • La requête est un GET avec le user agent RevRake-Postback/1. Elle suit trois redirections au plus et abandonne après 5 secondes. Les certificats sont vérifiés.
  • Une réponse 2xx est un succès. Toute autre réponse est un échec, retenté après 1 minute, 5 minutes, 25 minutes, 2 heures, 10 heures et 24 heures.
  • Après le dernier essai, l'envoi est marqué comme abandonné. L'affilié peut le relancer à la main.
  • Chaque tentative est enregistrée avec sa durée, le statut et les 500 premiers caractères de la réponse.
  • Après 5 puis 15 échecs d'affilée, le propriétaire de la destination reçoit un e-mail. À 20, la destination est désactivée. Ses envois, y compris ceux des événements qui surviennent pendant qu'elle est désactivée, continuent d'être mis en file d'attente et sont envoyés quand l'affilié la réactive. Une destination que l'affilié a désactivée lui-même ne reçoit rien de nouveau.
  • RevRake n'appelle que des adresses publiques. Les adresses de bouclage, privées, link-local et de NAT opérateur (CGNAT) sont refusées au moment de la connexion, de sorte qu'une redirection ne peut pas contourner ce contrôle.
  • Le modèle doit être une adresse http:// ou https:// complète de 2 000 caractères au plus.

Pourquoi une tentative a échoué

timeoutAucune réponse en 5 secondes.
dnsLe nom d'hôte est introuvable.
refusedLa connexion a été refusée.
blocked_addressL'adresse est privée ou locale.
tlsUn problème de certificat.
too_many_redirectsPlus de trois redirections.
http_errorLe tracker a répondu par un statut autre que 2xx.
otherLa connexion a échoué pour une autre raison.

Webhooks opérateur

Les webhooks informent les propres systèmes de l'opérateur (un CRM, un entrepôt de données, un canal de discussion) de ce qui se passe dans le programme. Un admin de l'espace de travail ajoute un endpoint sur la page Intégration du portail opérateur : l'adresse sur votre serveur, les événements à envoyer et une description facultative. Pour chacun de ces événements, RevRake envoie un POST JSON signé à cette adresse. Les autres membres de l'équipe ne peuvent ni voir ni modifier les endpoints.

L'adresse est une adresse https:// complète sur un hôte public (http:// est accepté, mais utilisez https://). Chaque endpoint a son propre secret, qui commence par whsec_. Il n'est affiché qu'une fois, quand l'endpoint est créé ou que son secret est renouvelé : copiez-le à ce moment-là. Le renouvellement crée un nouveau secret immédiatement, et l'ancien cesse de signer.

Un endpoint peut aussi être un canal Slack ou Microsoft Teams : choisissez « Message Slack » ou « Message Microsoft Teams » et collez l'adresse de webhook entrant du canal (pour Teams, un flux « Quand une requête webhook Teams est reçue »). Chaque événement arrive alors en une phrase dans le canal au lieu du JSON signé, et un montant peut ne garder que les premiers dépôts à partir de cette taille.

Événements

Cochez-en un ou plusieurs par endpoint. Les champs de data sont décrits plus bas ; un champ qui ne s'applique pas est omis.

ÉvénementChamps de dataQuand
affiliate.appliedaffiliate_number affiliate_name email promotes_on trafficQuelqu'un a postulé sur la page de votre programme ou via un lien d'invitation propre à un deal. Il est envoyé au moment de la candidature, ce qui peut être avant que le candidat ait confirmé son email. Un affilié que vous ajoutez et invitez vous-même envoie affiliate.approved à la place.
affiliate.approvedaffiliate_number affiliate_name email reason decided_by invitedVous avez approuvé un affilié : une nouvelle candidature, ou un affilié que vous aviez refusé ou suspendu. Un affilié que vous ajoutez et invitez vous-même est approuvé immédiatement et envoie cet événement avec invited à true.
affiliate.rejectedaffiliate_number affiliate_name email reason decided_byVous avez refusé une candidature.
affiliate.suspendedaffiliate_number affiliate_name email reason decided_byVous avez suspendu un affilié approuvé.
player.registeredplayer_id occurred_at attributed affiliate_number affiliate_name attribution_reasonUn joueur a été inscrit, par un événement signé ou une ligne d'un fichier REG, attribué ou non. Une fois par joueur.
commission.createdplayer_id occurred_at attributed affiliate_number affiliate_name commission_id commission_type amount amount_currency commission commission_currencyUne ligne de commission a été créée : un CPA admissible selon la base du deal, premier dépôt ou dépôts cumulés, ou une part de revenu ou un ajustement du revenu net déclaré. Les compléments de palier et les annulations n’envoient pas cet événement.
commission.approvedplayer_id occurred_at attributed affiliate_number affiliate_name commission_id commission_type amount amount_currency commission commission_currencyUne commission en rétention a été approuvée parce que sa période de rétention est terminée, compléments de palier compris. Une ligne approuvée dès le départ n'en envoie pas.
commission.rejectedplayer_id occurred_at attributed affiliate_number affiliate_name commission_id commission_type amount amount_currency commission commission_currencyUne commission en rétention a été refusée parce que vous avez confirmé l'alerte de fraude qui la retenait. commission vaut zéro. Un CPA annulé par un chargeback envoie commission.reversed à la place.
commission.paidplayer_id occurred_at attributed affiliate_number affiliate_name commission_id commission_type amount amount_currency commission commission_currencyUne ligne de commission a été payée : un événement par ligne d'un élément de paiement marqué comme payé, lignes d'annulation et de palier comprises.
commission.reversedplayer_id occurred_at attributed affiliate_number affiliate_name commission_id commission_type amount amount_currency commission commission_currencyUn chargeback a repris un CPA : annulé pendant la rétention, ou contrepassé par une nouvelle ligne après avoir été approuvé ou payé. commission_id désigne le CPA, commission est son opposé et amount est le dépôt qui a fait l'objet du chargeback.
payout.batch_closedbatch_id period reference items payable_by_currency closed_by_handUn mois a été clôturé en un lot de paiement brouillon, par une personne ou par la clôture programmée.
payout.batch_approvedbatch_idUn lot de paiement brouillon a été approuvé et verrouillé.
payout.paidbatch_id item_id affiliate_number affiliate_name currency amountUn élément d'un lot approuvé a été marqué comme payé. Un lot dont tous les éléments sont payés n'envoie pas d'événement propre.
tracking.health_alertbrand kind since quiet_hours clicks_at_leastLes liens d'une marque ont reçu au moins le nombre de clics fixé pendant les heures fixées et aucune inscription n'est arrivée (page Intégration, Santé du suivi). Envoyé une fois ; vérifié toutes les heures.
tracking.health_clearedbrand raised_at cleared_atLa première inscription après une alerte de santé du suivi est arrivée, et l'alerte est levée.
flag.raisedkind detail affiliate_number affiliate_nameUn nouveau signalement de fraude a été créé sur un affilié (par une règle, un premier dépôt répété ou un auto-parrainage). Les signalements de rafales de clics sont seulement sur la page Signalements.
postback.destination_offdestination failures affiliate_number affiliate_nameLa destination de postbacks d'un affilié a été désactivée après les échecs de livraison consécutifs que permet l'échelle de relances.
file.latesource dateUn fichier quotidien récupéré selon un horaire n'était pas arrivé trois heures après son heure. Une fois par fichier et par jour.
webhook.testmessageUniquement depuis le bouton « Envoyer un test ». Il ne peut pas être coché, n'est pas réessayé et n'apparaît pas dans le journal des envois.

Le corps

Chaque requête transporte un objet JSON :

ChampTypeSignification
idstringL'identifiant de l'événement, un UUID. Le même pour chaque endpoint qui reçoit l'événement et à chaque nouvel essai : utilisez-le pour écarter les doublons.
typestringL'événement, comme dans le tableau ci-dessus.
created_atstringLe moment où l'événement a été mis en file, au format RFC 3339 en UTC, avec des fractions de seconde.
dataobjectLes faits de l'événement. Voir les champs ci-dessous.
{
  "id": "0199b2c9-0a11-7c3e-9f20-7d4b1e6a2c58",
  "type": "commission.created",
  "created_at": "2026-10-05T09:12:44.318204Z",
  "data": {
    "player_id": "PLR-1001",
    "occurred_at": "2026-10-05T09:12:40Z",
    "attributed": true,
    "affiliate_number": 1001,
    "affiliate_name": "Odds Garage",
    "commission_id": "0199b2c9-0a0f-7a51-8c3d-5e2f4a6b7c90",
    "commission_type": "cpa",
    "amount": "25.00",
    "amount_currency": "USD",
    "commission": "40.00",
    "commission_currency": "USD"
  }
}

Les clés peuvent arriver dans n'importe quel ordre, et l'espacement du JSON n'est pas fixe. Vérifiez les octets tels que reçus, puis analysez-les.

En-têtes

En-têteValeur
RevRake-EventL'événement, le même que type dans le corps.
RevRake-DeliveryL'identifiant de cet envoi vers cet endpoint, un UUID. Le même à chaque nouvel essai, et quand un envoi est renvoyé à la main.
RevRake-TimestampLe moment où cette tentative a été envoyée, en secondes Unix. Nouveau à chaque tentative.
RevRake-SignatureLe HMAC-SHA256 en hexadécimal minuscule, sous le secret de l'endpoint, de <timestamp>.<raw body>. Nouvelle à chaque tentative.

La requête porte aussi Content-Type: application/json et l'agent utilisateur RevRake-Webhook/1.

POST /revrake-webhook HTTP/1.1
Host: example.com
Content-Type: application/json
User-Agent: RevRake-Webhook/1
RevRake-Event: commission.created
RevRake-Delivery: 0199b2c9-0b7e-7d02-8a41-3c5e6f708192
RevRake-Timestamp: 1791191571
RevRake-Signature: <hex HMAC-SHA256 of "1791191571.<raw body>">

Les champs de data

ChampTypeSignification
affiliate_numberintegerLe numéro de l'affilié.
affiliate_namestringLe nom de l'affilié.
emailstringL'adresse email avec laquelle l'affilié se connecte.
promotes_onstringCe que le candidat a écrit sur les endroits où il fait sa promotion.
trafficarray of stringLes sources de trafic cochées par le candidat : seo, telegram, paid_social, youtube, email ou offline.
reasonstringLa raison donnée à l'affilié. Toujours présente pour un refus ou une suspension ; peut être vide pour une approbation.
decided_bystringL'adresse email du membre de l'équipe qui a décidé, ou qui a ajouté l'affilié.
invitedbooleantrue quand vous avez ajouté et invité l'affilié vous-même, sans candidature. Absent sinon.
player_idstringVotre identifiant du joueur.
occurred_atstringLe moment où cela s'est produit, au format RFC 3339 : l'inscription, le dépôt, la fin de la période de revenu ou le chargeback. Pour une approbation, un refus ou un paiement : le moment du changement.
attributedbooleanSi le joueur appartient à un affilié. Toujours true pour une commission.
attribution_reasonstringPourquoi le joueur a été attribué ou non : l'une des raisons d'attribution.
commission_idstringL'identifiant RevRake de la ligne de commission : l'id de la ligne dans GET /v1/commissions. Le même dans chaque événement concernant cette commission.
commission_typestringLe type de la ligne de commission : cpa, revshare, adjustment ou tier, et reversal pour une ligne d'annulation dans commission.paid. Dans commission.reversed, le type du CPA repris.
amountstringPour une commission : le dépôt ou le revenu net qui la sous-tend, en décimal, dans sa propre devise. Pour payout.paid : le montant de l'élément, en décimal.
amount_currencystringLe code ISO de la devise de amount.
commissionstringLa commission, en décimal dans la devise du deal (40.00). Négative pour une annulation, un ajustement à la baisse et une période gagnée par le joueur.
commission_currencystringLe code ISO de la devise de la commission : celle du deal.
batch_idstringL'identifiant RevRake du lot de paiement, un UUID.
periodstringLe mois que le lot clôture, YYYY-MM.
referencestringLa référence du lot, celle du fichier bancaire, par exemple po_2026-09_demo.
itemsintegerLe nombre d'éléments du lot, quel que soit leur statut.
payable_by_currencyobjectPar devise, la somme des montants de tous les éléments, en décimal (1250.00), quel que soit leur statut, éléments reportés compris.
closed_by_handbooleantrue quand une personne a clôturé le mois, false pour la clôture programmée.
item_idstringL'identifiant RevRake de l'élément de paiement, un UUID.
currencystringLa devise de l'élément.
messagestringUne phrase indiquant que le test est arrivé.

Vérifier une requête

  1. Lisez le corps brut en octets, avant toute analyse JSON.
  2. Vérifiez que RevRake-Timestamp est à moins de 5 minutes de votre horloge, et refusez la requête sinon, afin qu'une requête enregistrée ne puisse pas être rejouée plus tard.
  3. Calculez le HMAC-SHA256 en hexadécimal du timestamp, d'un point et du corps brut sous le secret de l'endpoint, et comparez-le à RevRake-Signature en temps constant.
  4. Seulement ensuite, analysez le corps. Si vous avez déjà traité son id, répondez 2xx et ne faites rien.
  5. Répondez par n'importe quel 2xx en moins de 5 secondes, et faites le travail lent ensuite.

La même vérification en Node.js et en Python, telle que la page Intégration l'affiche :

Node.js
const crypto = require("node:crypto");

// raw: the body exactly as received (a Buffer), before parsing it as JSON.
function verify(raw, headers, secret) {
  const ts = headers["revrake-timestamp"] ?? "";
  if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(ts + ".").update(raw).digest();
  const got = Buffer.from(headers["revrake-signature"] ?? "", "hex");
  return got.length === expected.length && crypto.timingSafeEqual(got, expected);
}
Python
import hashlib, hmac, time

# raw: the body exactly as received (bytes), before parsing it as JSON.
def verify(raw: bytes, headers, secret: str) -> bool:
    ts = headers.get("RevRake-Timestamp", "")
    if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
        return False
    expected = hmac.new(secret.encode(), ts.encode() + b"." + raw, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, headers.get("RevRake-Signature", ""))

Envoi, nouveaux essais et désactivation

  • Un envoi est mis en file dans la même transaction que le changement qu'il signale : un événement est envoyé si et seulement si le changement a été enregistré. Il est envoyé en une dizaine de secondes environ, le plus ancien d'abord.
  • L'envoi est « au moins une fois » : le même événement peut arriver plus d'une fois, par exemple quand votre réponse se perd en route. Écartez les doublons grâce à l'id du corps.
  • L'ordre n'est pas garanti : un nouvel essai peut arriver après un événement plus récent. Ordonnez les événements par created_at ou occurred_at, pas par leur heure d'arrivée.
  • Une réponse 2xx en moins de 5 secondes est un succès. Tout le reste est un échec et est réessayé après 1 minute, 5 minutes, 25 minutes, 2 heures, 10 heures et 24 heures, avec le même corps et un nouveau timestamp et une nouvelle signature.
  • Après le dernier essai, l'envoi est marqué comme abandonné. Un admin peut le renvoyer depuis le journal des envois ; il repart alors pour tout le calendrier de nouveaux essais.
  • Après 20 tentatives échouées d'affilée, l'endpoint est désactivé. Ses événements continuent de s'accumuler, et tout ce qui s'est accumulé est envoyé quand un admin le réactive. Aucun email n'est envoyé dans ce cas : l'état de chaque endpoint est visible sur la page Intégration.
  • Un endpoint que vous désactivez vous-même ne reçoit rien de nouveau : aucun événement n'est mis en file pour lui tant qu'il est désactivé.
  • RevRake n'appelle que des adresses publiques : il abandonne après 5 secondes et vérifie les certificats. Les adresses de bouclage, privées, link-local et de NAT de niveau opérateur sont refusées. Une redirection n'est pas suivie : une réponse 3xx est un échec avec l'erreur redirect, et le journal des envois montre l'adresse vers laquelle elle pointait.

Le tester et lire le journal des envois

  • Cliquez sur « Envoyer un test » sur un endpoint : un événement webhook.test est envoyé immédiatement, signé comme un vrai, et vous voyez le statut répondu par votre endpoint et le temps qu'il a pris. Un test n'est pas réessayé, n'apparaît pas dans le journal des envois et ne compte pas comme un échec.
  • Le journal des envois d'un endpoint montre ses 100 derniers envois : l'événement, l'état (en attente, nouvel essai, abandonné ou délivré), le nombre de tentatives, le dernier statut ou la dernière erreur, les 500 premiers caractères de votre réponse, le prochain essai, et le corps tel qu'il a été envoyé.
  • Une tentative échouée est décrite avec les mêmes mots qu'un postback (voir pourquoi une tentative a échoué), plus redirect : votre endpoint a répondu par un 3xx. Les états sont ceux d'un envoi de postback.
{
  "id": "0199b2d4-5c60-7b1a-9e3f-1a2b3c4d5e6f",
  "type": "webhook.test",
  "created_at": "2026-10-05T09:30:02.104377Z",
  "data": {
    "message": "A test from RevRake. If you can read this, your endpoint works."
  }
}

Bonnes pratiques

  • Gardez le secret sur votre serveur, jamais dans une page ou une application. S'il fuit, renouvelez-le et passez votre endpoint au nouveau immédiatement : l'ancien secret cesse de fonctionner aussitôt.
  • Répondez par un 4xx à une requête dont la signature ne correspond pas. Elle est réessayée, signée avec le secret actuel, donc un envoi fait pendant un renouvellement n'est pas perdu.
  • Conservez l'id de chaque événement traité, et ignorez un événement déjà vu.
  • Répondez d'abord, travaillez ensuite : mettez l'événement dans votre propre file et répondez 2xx, pour qu'une base de données lente ne pousse jamais RevRake à réessayer.
  • Saisissez l'adresse finale de votre endpoint. Une redirection n'est pas suivie : la tentative échoue avec redirect, et le journal des envois indique l'adresse à utiliser à la place.
  • Utilisez commission_id pour relier les événements concernant une commission, et rapprochez avec GET /v1/commissions : une lecture nocturne des 60 derniers jours rattrape tout ce qui aurait été manqué.
  • Ne cochez que les événements que vous utilisez. Chacun est une requête vers votre serveur.

Fichier OpenAPI

Les quatre endpoints signés, avec chaque statut et chaque raison sous forme d'énumération, sont décrits dans openapi.yaml (OpenAPI 3.1), pour les générateurs de code et les clients d'API. Son adresse ne change pas.

Prêt à vous connecter ? Créez votre programme et obtenez votre clé.

Vous créez votre programme vous-même : inscrivez-vous, confirmez votre e-mail, et vous y êtes en quelques minutes. Gratuit pour les opérateurs, pour toujours. Sans carte bancaire.

Des questions ? Écrivez à hello@revrake.com