La API de RevRake.
Todo lo que la plataforma de un operador envía a RevRake, lo que vuelve y lo que RevRake envía a los trackers de los afiliados y a los sistemas propios del operador. Para el orden en que hacer las cosas, lee la guía de integración.
URLs base
La API son cuatro endpoints firmados para la plataforma del operador, dos de escritura y dos de lectura, y el enlace de tracking que abren los visitantes. Los endpoints firmados también se publican como un archivo OpenAPI 3.1.
| Endpoint | Significado |
|---|---|
POST https://app.revrake.com/v1/postback | Informa de un evento, o de un lote de eventos. Firmado. |
POST https://app.revrake.com/v1/files | Envía un archivo diario REG o SALES. Firmado. |
GET https://app.revrake.com/v1/reports | Lee un informe, como JSON o CSV. Firmado, con una clave de operador o de afiliado. |
GET https://app.revrake.com/v1/commissions | Lee el libro mayor de comisiones, página a página. Firmado, con una clave de operador o de afiliado. |
GET https://app.revrake.com/v1/affiliates | Lista los afiliados de la marca. Firmado, con la clave del operador. |
GET https://app.revrake.com/v1/links | Lista los enlaces de seguimiento. Firmado, con una clave de operador o de afiliado. |
GET https://app.revrake.com/v1/deals | Lista los acuerdos con los términos vigentes. Firmado, con la clave del operador. |
GET https://track.revrake.com/c/{code} | El enlace de tracking: guarda el clic y redirige. Sin firma. |
Los portales en app.revrake.com usan sus propios endpoints tras una sesión iniciada. No son una API pública y pueden cambiar sin aviso. Los informes, el libro mayor y las filas de pagos también se pueden descargar allí como CSV.
Claves
Hay dos tipos de clave. Cada una tiene un ID de clave y un secreto, y cada una firma las peticiones de la misma forma. Las claves nunca se comparten: cada parte tiene la suya.
| El ID de clave empieza por | De quién es | Qué puede hacer |
|---|---|---|
pb_ | El operador. Una clave activa por marca, en la página Integración del portal de operador. Un admin del workspace puede ver el secreto y rotar la clave. | Envía eventos y archivos. Lee los informes y el libro mayor de todo el programa, o de un afiliado con affiliate=<number>. |
ak_ | Un afiliado. Cada afiliado crea la suya en la pestaña API de su portal de afiliado. El secreto se muestra una sola vez, al crear la clave. | Lee los informes y las comisiones de ese afiliado, y nada más. No puede enviar eventos: /v1/postback y /v1/files responden 401 unknown_key. |
Nunca des la clave de operador a un afiliado. Lee las cifras de todos los afiliados y puede escribir eventos. Un afiliado que quiere acceso por API crea su propia clave.
- Una clave nueva revoca la anterior al instante, en ambos tipos. Un afiliado también puede revocar su clave y quedarse sin ninguna.
- Una clave de afiliado solo funciona mientras el afiliado esté aprobado en el programa. Si no, una lectura responde
403affiliate_inactive. - Con una clave de afiliado, el parámetro
affiliatese ignora yby=affiliatese responde por día. - El límite de frecuencia de las lecturas se cuenta por clave.
Autenticación
Cada petición a /v1 se firma con una clave. Cada petición lleva tres cabeceras:
| Cabecera | Valor |
|---|---|
X-RevRake-Key | Tu ID de clave. Empieza por pb_ (operador) o ak_ (afiliado). |
X-RevRake-Timestamp | La hora Unix actual en segundos. Una petición se acepta dentro de los cinco minutos anteriores o posteriores. |
X-RevRake-Signature | El HMAC-SHA256 en hexadecimal en minúsculas, con tu secreto, de <timestamp>.<body> para un POST y de <timestamp>.<path>?<query> para un GET. |
Para firmar una petición:
- Construye el cuerpo y conserva los bytes exactos que vas a enviar.
- Toma la hora Unix actual en segundos, como texto decimal.
- Concatena el timestamp, un punto (
.) y el cuerpo. - Calcula el HMAC-SHA256 de eso con tu secreto y escríbelo en hexadecimal en minúsculas.
- Envía el cuerpo sin cambios, con las tres cabeceras.
Firmar una lectura
Un GET no tiene cuerpo, así que la firma cubre en su lugar el destino de la petición: la ruta y la query exactamente como se envían, por ejemplo 1790000000./v1/reports?by=link&days=7. Sin query, es el timestamp, un punto y la ruta. Construye la dirección una vez, firma ese texto y envía ese texto: un cliente que reordena o recodifica la query después de firmar rompe la firma.
- Primero se comprueba la clave, luego el timestamp y luego la firma.
- La cabecera
Content-Typeno es obligatoria. Envíaapplication/jsonpara eventos ytext/csvpara archivos. - Una clave rotada deja de funcionar al instante.
Ejemplos de firma
Cada ejemplo tiene una función de firma, envía un registro y después lee un informe. Sustituye el ID de clave y el secreto por los tuyos.
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))
} Vector de prueba
Con estas entradas, tu código debe producir exactamente estas firmas:
- Secreto
- test_secret
- Timestamp
- 1791072000
- Cuerpo
- {"type":"registration","player_id":"PLR-1001","btag":"1001_1"}
- Firma
- 115d8c73c6069d8953a36ca3a68c114da6232e4ba31889fd519d41ab1350a603
- Destino de la petición de una lectura
- /v1/reports?by=link&days=7
- Firma de la lectura
- ed1ed72dd7ddef22c762f63b8919011e7c36c7214b55b72693c5927fb4b911d0
Límites
Estos son todos los límites que el producto aplica hoy a integraciones, tracking y afiliados, con sus cifras. El inicio de sesión, el cambio de contraseña y las solicitudes también tienen límites contra el abuso.
| Límite | Significado |
|---|---|
| 120 | Lecturas por clave y minuto: GET /v1/reports y GET /v1/commissions juntas |
| 30 000 | Peticiones de eventos por clave y minuto: POST /v1/postback, hasta 1.000 de golpe |
| 60 | Archivos por clave y minuto: POST /v1/files, hasta 10 de golpe |
| 4 MB | Cuerpo de POST /v1/postback, un evento o un lote |
| 32 MB | Cuerpo de POST /v1/files |
| 1 – 1000 | Eventos en un lote |
| 200 000 | Filas de datos en un archivo |
| 300 s | Diferencia entre X-RevRake-Timestamp y la hora real, en cualquiera de los dos sentidos |
| 24 h | Hasta cuánto en el futuro pueden estar ts y period |
| 100 | Longitud de player_id, en caracteres |
| 200 | Longitud de txn_id, en caracteres |
| 0 / 2 | Decimales de un importe: los de la moneda (dos en la mayoría, ninguno en monedas como XOF, UGX o JPY) |
| 1 – 365 | days de un informe |
| 10, 25, 50, 100, 500, 1000 | per_page del libro mayor: uno de estos valores |
| 1 – 100 000 | page del libro mayor |
| 100 | Longitud de q, en caracteres; un texto más largo se corta |
| 200 | Longitud de sub1 a sub5, de cada etiqueta UTM y de cada click ID publicitario en un enlace de tracking, en caracteres; los valores más largos se cortan |
| 10 | Destinos de postback por afiliado |
| 2000 | Longitud de una plantilla de dirección de postback, en caracteres |
| 5 s | Tiempo que tiene un tracker para responder a un postback |
| 50 | Registro de entrada que muestra el portal: las últimas entradas de los últimos 7 días |
| 2000 | Longitud de la dirección de un endpoint de webhook, en caracteres |
| 5 s | Tiempo que tiene un endpoint de webhook para responder |
| 20 | Intentos de webhook fallidos seguidos antes de que el endpoint se desactive |
| 100 | Entregas por endpoint de webhook que muestra el registro de entregas |
| 2 – 40 | Longitud de un código promocional: letras, dígitos, guiones y guiones bajos |
| 32 MB | Tamaño de un archivo que RevRake descarga desde tu dirección |
| 60 s | Tiempo que tiene tu dirección para entregar un archivo descargado |
| 0 – 3600 s | Segundos tras el clic para la regla de tiempo de registro (0 la desactiva) |
| 0 – 365 | Días hasta que una alerta de fraude abierta se decide sola (0 significa nunca) |
| 1 – 200 | Alertas liberadas o confirmadas en una sola decisión en bloque |
| 0 – 10 000 (20) | Clics por minuto en un enlace desde una misma dirección antes de que salte la alerta de ráfaga de clics (0 la desactiva; 20 si no la cambias) |
| 60 + 1 / min | Enlaces de tracking que puede crear un afiliado: 60 de una vez, luego uno por minuto. Pasado eso la respuesta es 429 con Retry-After |
| 2 MB | Tamaño de una imagen de banner subida en el portal: PNG, JPEG, GIF o WebP |
Los dos endpoints de escritura, POST /v1/postback y POST /v1/files, tienen un límite por clave (ver la tabla). Cada respuesta lleva X-RateLimit-Limit y X-RateLimit-Remaining; un rechazo es 429 con Retry-After. Envía al ritmo de tus eventos y usa lotes para una carga histórica. El enlace de tracking no tiene límite de frecuencia; una ráfaga inusual de clics en un enlace desde una misma dirección genera una alerta de fraude y aun así se redirige.
El límite de frecuencia de las lecturas
- Cada clave puede hacer 120 lecturas por minuto. El cupo es un cubo de 120: una lectura gasta una, y cada medio segundo se recupera una. Una clave que ha estado inactiva puede hacer 120 lecturas de golpe; después, dos por segundo.
- Cada respuesta a una lectura lleva
X-RateLimit-Limit(siempre120) yX-RateLimit-Remaining(cuántas lecturas quedan en este momento). - Superado el límite, la respuesta es
429rate_limitedcon una cabeceraRetry-After: los segundos que hay que esperar. Espera y envía la misma petición de nuevo, firmada con un timestamp nuevo. - El límite se cuenta por clave: la clave del operador y la clave de cada afiliado tienen su propio cupo.
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."} Cómo tratar 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
Informa de un evento de la plataforma del operador. El cuerpo es un objeto JSON con un type y los campos de ese tipo, o un array JSON de esos objetos (un lote).
POST https://app.revrake.com/v1/postback typees uno deregistration,deposit,chargeback,ngr.- Un campo que no figura en la lista del tipo se rechaza con
422, así que una errata nunca se ignora en silencio. tses la hora del evento en formato RFC 3339 (2026-10-04T08:15:00Z). Si se omite, se usa la hora de la petición. Debe llevar un desfase UTC (Zo+03:00); una hora sin él se rechaza. No puede estar más de un día en el futuro.- Un importe es un decimal, como texto JSON o como número JSON:
"25.00"o25.5. Sin separador de miles, sin exponente y sin más decimales de los que tiene la moneda. currencyes un código ISO 4217 de tres letras. Se aceptan minúsculas. Si se omite, se usa la moneda de informes del workspace.- A los valores de texto se les quitan los espacios de ambos extremos, salvo a
click_idybtag: envíalos exactamente como llegaron a la landing page.
registration
Un jugador nuevo. Envía click_id, btag, promo_code, o varios de ellos; sin ninguno, el jugador se registra como no atribuido. El jugador se atribuye una sola vez, aquí. Respuesta: attribution.
{"type":"registration","player_id":"PLR-1001","click_id":"01K6QF3M8Z5T7W9XB2C4D6E8GH","btag":"1001_1"} | Campo | Tipo | Obligatorio | Ejemplo | Significado |
|---|---|---|---|---|
player_id | string | sí | "PLR-1001" | Tu ID para el jugador. Hasta 100 caracteres. La clave del evento. |
click_id | string | no | "01K6QF3M8Z5T7W9XB2C4D6E8GH" | El cid con el que llegó el jugador: 26 caracteres, letras y dígitos; en mayúsculas o minúsculas. |
btag | string | no | "1001_1" | El btag que tu plataforma guardó en el jugador: <número de afiliado>_<número de enlace>, solo dígitos. |
promo_code | string | no | "GARAGE50" | El código promocional que el jugador escribió al registrarse. No distingue mayúsculas de minúsculas. Solo atribuye al jugador cuando click_id y btag no lo hacen. |
email_hash | string | no | "9f86d081…0f00a08" | SHA-256 del email en minúsculas, en hexadecimal. Solo lo usa el control de autorreferencia. |
email_domain | string | no | "example.com" | El dominio del email del jugador, la parte después de la @ (example.com). Solo lo usa la regla de correo desechable. Envía solo el dominio: si llega una dirección completa, solo se guarda el dominio en el jugador, pero el registro de entrada sigue mostrando la petición tal como llegó. |
ip | string | no | "203.0.113.7" | La dirección IP en el registro. Se guarda como hash, y solo se usa para el control de autorreferencia y para encontrar el clic que corresponde a un btag. |
country | string | no | "KE" | El país del jugador: dos letras, ISO 3166-1 alpha-2 (GB, KE). Se aceptan minúsculas. Un valor que no tenga dos letras deja al jugador sin país. Sin el campo, se usa el país del clic que trajo al jugador. Un deal puede listar países por los que no paga. |
ts | string | no | "2026-10-04T08:15:00Z" | Cuándo se registró el jugador. La ventana de atribución se mide desde ese momento. |
{
"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
Envía un evento deposit por cada depósito, con tu ID de transacción en txn_id. El deal define si el mínimo para CPA se compara con el primer depósito o con los depósitos acumulados, descontados los chargebacks. La calificación crea una comisión CPA por jugador, en retención.
{"type":"deposit","player_id":"PLR-1001","txn_id":"TX-88213","amount":"25.00","currency":"KES","first":true} | Campo | Tipo | Obligatorio | Ejemplo | Significado |
|---|---|---|---|---|
player_id | string | sí | "PLR-1001" | Tu ID para el jugador. Hasta 100 caracteres. |
txn_id | string | sí | "TX-88213" | Tu ID de transacción. Hasta 200 caracteres. La clave del evento. |
amount | string | number | sí | "25.00" | El depósito, mayor que cero. |
currency | string | no | "KES" | La moneda del depósito. Se acepta cualquier moneda. |
ts | string | no | "2026-10-04T08:20:00Z" | Cuándo se hizo el depósito. Decide qué versión del deal y qué tipo de cambio diario se aplican. |
first | boolean | no | true | true si es el primer depósito del jugador. Si se omite, el depósito cuenta como primero si RevRake no conoce ninguno anterior del jugador. |
{
"status": "accepted",
"event_type": "deposit",
"event_id": "0199b2c9-03aa-7e10-b6d2-91c07f5e2a18",
"commission": {
"effect": "created",
"type": "cpa",
"amount_minor": 4000,
"status": "hold"
}
} chargeback
Envía un evento chargeback con su propio txn_id y el txn_id del depósito original en ref_txn_id. Revierte el depósito completo. En CPA acumulado, la comisión solo se retira si el total válido restante cae por debajo del mínimo: una comisión en retención se cancela; una aprobada o pagada recibe una fila de reversión.
{"type":"chargeback","txn_id":"CB-5001","ref_txn_id":"TX-88213"} | Campo | Tipo | Obligatorio | Ejemplo | Significado |
|---|---|---|---|---|
txn_id | string | sí | "CB-5001" | Tu ID para el propio chargeback. Hasta 200 caracteres. La clave del evento. |
ref_txn_id | string | sí | "TX-88213" | El txn_id del depósito devuelto. |
player_id | string | no | "PLR-1001" | Opcional. Si se envía, debe coincidir con el jugador del depósito referenciado. |
ts | string | no | "2026-10-09T11:00:00Z" | Cuándo ocurrió el chargeback. |
{
"status": "accepted",
"event_type": "chargeback",
"event_id": "0199cc10-7b2d-7c41-a3f0-5d2e8b9c1f47",
"commission": {
"effect": "cancelled",
"type": "cpa",
"amount_minor": 4000,
"status": "rejected"
}
} ngr
Los ingresos netos de juego (NGR) de un jugador en un día o en un mes. Generan el RevShare de la versión del deal vigente al final del periodo, en retención. El jugador debe estar registrado. Respuesta: commission.
{"type":"ngr","player_id":"PLR-1001","period":"2026-10-03","amount":"118.40","currency":"KES"} | Campo | Tipo | Obligatorio | Ejemplo | Significado |
|---|---|---|---|---|
player_id | string | sí | "PLR-1001" | Tu ID para el jugador. Hasta 100 caracteres. |
period | string | sí | "2026-10-03" | El día (YYYY-MM-DD) o el mes (YYYY-MM) al que corresponde el importe. Un mes y su primer día son periodos distintos. No más de un día en el futuro. |
amount | string | number | sí | "118.40" | Los ingresos netos de juego (NGR) del periodo. Negativos cuando el jugador ganó. |
currency | string | no | "KES" | La moneda del importe. |
ggr | string | number | no | "240.00" | Opcional. Los ingresos brutos de juego (GGR) del periodo, en la misma moneda. Se muestran al afiliado; no cambian la comisión. |
bonuses | string | number | no | "80.00" | Opcional. Los bonos descontados de los ingresos brutos. Solo se muestran. |
fees | string | number | no | "21.60" | Opcional. Las comisiones descontadas de los ingresos brutos, como las de pago o de plataforma. Solo se muestran. |
taxes | string | number | no | "20.00" | Opcional. Los impuestos descontados de los ingresos brutos. Solo se muestran. |
ts | string | no | "2026-10-04T02:00:00Z" | Cuándo se comunicó la cifra. |
{
"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, btag, promo_code o player_id que contiene { o } se rechaza: significa que tu plataforma envió un marcador sin rellenarlo.
La respuesta
Un evento que se guardó, o que ya estaba guardado, se responde con 200 y un objeto JSON. Los campos que no aplican se omiten.
| Campo | Tipo | Significado |
|---|---|---|
status | string | accepted, o duplicate cuando el evento ya estaba guardado. |
event_type | string | El type que enviaste. |
event_id | string | El ID de RevRake del evento guardado, un UUID. El mismo en cada repetición. |
attribution | object | Para un registro: la decisión. |
attribution.attributed | boolean | Si el jugador pertenece a un afiliado. |
attribution.reason | string | Por qué. Consulta la tabla de abajo. |
attribution.affiliate_number | integer | El afiliado. Solo cuando está atribuido. |
attribution.link_number | integer | El enlace del afiliado. Solo cuando está atribuido. |
attribution.click_id | string | El clic con el que coincidió, cuando lo hay. |
attribution.policy_version | integer | La versión de la política de atribución de la marca que decidió. |
attribution.window_days | integer | La ventana de atribución de esa versión, en días. |
attribution.model | string | last_click o first_click: el modelo de esa versión. |
commission | object | Para un depósito, un chargeback y ngr: lo que el evento hizo en el libro mayor. |
commission.effect | string | created, cancelled (se rechazó una comisión en retención), reversed (se añadió una fila de reversión) o none. |
commission.type | string | cpa, revshare, adjustment o reversal. |
commission.amount_minor | integer | El importe en unidades menores de la moneda del deal (4000 es 40.00). Negativo para una reversión y para un ajuste a la baja. |
commission.status | string | hold, approved o rejected. |
commission.reason | string | Por qué no se creó nada, cuando effect es none. Consulta la tabla de abajo. |
waiting_for_player | boolean | true para un depósito cuyo jugador aún no está registrado. Se guarda y se valora cuando llega el registro. |
duplicate_of | string | Para una coincidencia antigua de primer depósito entre eventos en directo y archivos diarios antiguos: el event_id anterior. Los resúmenes SALES actuales usan cobertura diaria y conservan cada ID de transacción real. |
detail | string | Con duplicate_of: una frase que dice qué depósito anterior repite este. |
{
"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" }
} Una comisión se crea con el estado hold. Se aprueba automáticamente cuando termina el periodo de retención del deal, salvo que algo la siga reteniendo: una alerta de fraude abierta, un afiliado que no está aprobado o un tipo de cambio que falta.
Motivos de atribución
Primero se prueba el click ID. Si no es uno de los tuyos, se prueba el btag. Cuando la política dice que gana el btag y el btag coincide con un enlace, se prueba primero el btag. Un btag que coincide con un enlace atribuye al jugador aunque no se encuentre ningún clic. Un código promocional se prueba al final, solo cuando ni el click ID ni el btag atribuyeron al jugador. La política, su ventana y cuál de los dos gana se definen en la página Integración; cada cambio es una nueva versión. Cuando la política dice que gana el código promocional, un código que coincide con un enlace se prueba antes que ambos, y uno que no coincide vuelve a ellos. Un acuerdo puede fijar su propia ventana, que sustituye a la de la política para los clics en los enlaces de sus afiliados.
reason | Significado | |
|---|---|---|
click | Atribuido | El click ID coincidió con un clic dentro de la ventana. |
first_click | Atribuido | La política es primer clic: el click ID coincidió con un clic dentro de la ventana, y se acreditó en su lugar un clic contado anterior del mismo visitante (misma dirección y dispositivo) dentro de la ventana. |
btag_click | Atribuido | El btag coincidió con un enlace, y se encontró un clic en ese enlace dentro de la ventana. |
btag_only | Atribuido | El btag coincidió con un enlace; no se encontró ningún clic dentro de la ventana. |
promo_code | Atribuido | Ni el click ID ni el btag atribuyeron al jugador, y el código promocional coincidió con un código de un afiliado aprobado. |
no_tag | No atribuido | El registro no llevaba ni click ID ni btag. |
unknown_click | No atribuido | El click ID no es uno de los tuyos, y no se envió ningún btag. |
unknown_btag | No atribuido | El btag está mal formado o no coincide con ningún enlace. |
outside_window | No atribuido | El clic es más antiguo que la ventana de atribución, o posterior al registro. |
affiliate_inactive | No atribuido | El afiliado no está aprobado, o no lo estaba cuando se hizo el clic. |
unknown_promo_code | No atribuido | Solo se envió un código promocional, y no coincide con ninguno de tus códigos. |
promo_code_retired | No atribuido | Solo se envió un código promocional, y fue retirado. |
Por qué no se creó ninguna comisión
commission.reason | Significado |
|---|---|
unattributed | El jugador no pertenece a ningún afiliado. |
not_first | No es el primer depósito del jugador. |
already_earned | El CPA del jugador ya se había generado. Un segundo "primer" depósito no paga nada y genera una alerta. |
no_deal_version | El afiliado no tiene deal, o el deal no tenía ninguna versión en el momento del evento. |
country_blocked | El jugador es de un país por el que el deal no paga. Sigue registrado y atribuido. |
no_cpa_in_deal | El deal no paga CPA. |
below_threshold | El primer depósito o los depósitos acumulados netos están por debajo del mínimo del deal, según su base de CPA. |
no_commission | Para un chargeback: el depósito no había generado nada. |
no_revshare_in_deal | Para ngr: el deal no paga RevShare. |
fx_rate_missing | Para ngr: aún no hay tipo de cambio para el periodo. El informe se guarda y se valora cuando existe un tipo. |
unchanged | Para ngr: una cifra corregida que no cambia nada en la comisión. |
Lotes
El cuerpo puede ser un array JSON de 1 a 1.000 eventos, enviado al mismo endpoint con las mismas cabeceras. La firma cubre el array entero. Los eventos se procesan en orden, cada uno por separado, así que un evento rechazado nunca detiene a los demás.
Un lote que se pudo leer se responde con 200, también cuando todos sus eventos se rechazaron. Mira los recuentos.
[
{"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
} | Campo | Tipo | Significado |
|---|---|---|
results | array | Una entrada por evento, en el orden de envío. |
results[].index | integer | La posición del evento en el array, desde 0. |
results[].status | string | accepted, duplicate o rejected. |
results[].event_type | string | Como en una respuesta individual. No aparece en un evento rechazado. |
results[].event_id | string | Como en una respuesta individual. No aparece en un evento rechazado. |
results[].attribution | object | Como en una respuesta individual. |
results[].commission | object | Como en una respuesta individual. |
results[].waiting_for_player | boolean | Como en una respuesta individual. |
results[].field | string | Para un evento rechazado: el campo que hay que corregir. |
results[].message | string | Para un evento rechazado: qué hay que corregir. |
accepted | integer | Cuántos eventos se guardaron. |
duplicate | integer | Cuántos ya estaban guardados. |
rejected | integer | Cuántos se rechazaron. |
POST /v1/files
Envía un archivo CSV diario, como alternativa a subirlo en la página Integración o a dejar que RevRake lo descargue desde una dirección donde tu plataforma lo publica. El cuerpo es el propio archivo; las cabeceras y la firma son las de un evento, y la firma cubre el timestamp y el archivo. El nombre del archivo va en el parámetro de query filename; se muestra en el portal y no se usa para nada más.
POST https://app.revrake.com/v1/files?filename=sales-2026-10-03.csv La fila de cabecera decide de qué archivo se trata: un archivo con una columna registration_date es un archivo REG, y uno con una columna activitydate es un archivo SALES. Los nombres de las columnas se comparan sin distinguir mayúsculas de minúsculas. Las columnas que no figuran abajo pueden quedarse en el archivo; se ignoran.
Las fechas admitidas son YYYY-MM-DD, YYYY-MM-DD HH:MM:SS, YYYY-MM-DDTHH:MM:SS, RFC 3339 o YYYY/MM/DD. Sin desfase UTC, se aplica la zona de archivos de Ajustes del workspace (UTC por defecto). Un timestamp con desfase identifica un instante exacto; para SALES se usa su día de calendario en la zona de archivos. Día y mes nunca se adivinan: 30/09/2026 se rechaza.
Archivo REG: una fila por jugador registrado
| Columna | Obligatorio | Significado |
|---|---|---|
registration_date | sí | Cuándo se registró el jugador. Se convierte en ts. |
player_id | sí | Tu ID para el jugador. |
btag | sí | El btag guardado en el jugador. Puede estar vacío: el jugador queda entonces sin atribuir, salvo que lo atribuya un código promocional. |
promo_code | no | Opcional. El código promocional que el jugador escribió al registrarse, usado como el promo_code de un registro. |
player_country | no | Opcional. El código de dos letras del país del jugador (ISO 3166-1 alpha-2), usado como el country de un registro. Vacío: el país del 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 Archivo SALES: una fila por jugador y día
| Columna | Obligatorio | Significado |
|---|---|---|
activitydate | sí | El día de actividad, determinado en la zona horaria de archivos del workspace. |
player_id | sí | Tu ID para el jugador. |
currency | sí | La moneda de los importes de la fila. Vacía: la moneda de informes del workspace. |
deposits | sí | Depósitos brutos del día, incluido el primer depósito realizado ese día. No es el total histórico; no restes chargebacks. |
first_deposit_amount | sí | Importe del primer depósito, solo en el día en que ocurrió; cero en los demás días. Ya está incluido en deposits y nunca se suma otra vez. No puede superar los depósitos brutos del día. |
chargeback | sí | Total de chargebacks de este día de actividad, separado de los depósitos brutos. No es el total histórico. Reduce los depósitos acumulados para CPA, pero no identifica una transacción concreta. |
*_net_revenue | no | Todas las columnas llamadas net_revenue o cuyo nombre termina en _net_revenue. Las celdas rellenadas se suman en los ingresos netos del jugador de ese día, comunicados como ngr. Una fila sin ninguna de ellas rellenada no comunica ingresos. |
*_gross_revenue | no | Opcional. Las columnas llamadas gross_revenue o cuyo nombre termina en _gross_revenue, sumadas en el ggr del ngr del día. Solo se muestran al afiliado. |
*_bonus | no | Opcional. Las columnas llamadas bonus o cuyo nombre termina en _bonus, sumadas en bonuses. Solo se muestran. |
*_fees | no | Opcional. Las columnas llamadas fees o cuyo nombre termina en _fees, sumadas en fees. Solo se muestran. |
*_taxes | no | Opcional. Las columnas llamadas taxes o cuyo nombre termina en _taxes, sumadas en taxes. Solo se muestran. |
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, - Cada fila SALES registra un resumen monetario diario y, si hay celdas de ingresos rellenas, un evento NGR separado. Se procesan de forma independiente: rechazar el resumen monetario no deshace ingresos aceptados, ni al revés. Revisa todos los problemas de la respuesta.
- Un resumen por marca, jugador y día de actividad; la primera moneda aceptada queda fija. Los valores monetarios idénticos son replays aunque renombres o reordenes el archivo. Cambiar importes, moneda o límites de zona horaria de un día aceptado genera conflicto, incluso al cambiar ceros. El dinero ya registrado no cambia.
deposits,first_deposit_amountychargebackson columnas obligatorias. Las celdas vacías equivalen a cero. Los importes deben ser decimales no negativos con la precisión de la moneda de la fila. Confirma estas definiciones con tu plataforma antes de conectar su exportación.- Envía REG antes de SALES. Los resúmenes monetarios recibidos antes se guardan y calculan al llegar el registro. El NGR de un jugador desconocido se rechaza: después de REG, reenvía SALES o NGR. El resumen monetario guardado será un replay.
- El resumen cubre los postbacks individuales de su día de actividad y moneda en la zona de archivos, sin sumar importes dos veces. Los IDs de transacción siguen disponibles como referencias. Los depósitos o chargebacks conocidos que superen el resumen, o pruebas contradictorias del primer depósito, generan conflicto.
- En CPA acumulado, depósitos menos chargebacks deben alcanzar el mínimo del deal. Cada flujo diario usa el tipo de cambio de su fecha de actividad; los postbacks individuales usan su propio día UTC. Si faltan tipos, la liquidación queda en retención bajo
fx_rate_missing. Si falta el tipo de un chargeback, se bloquea el pago bajochargeback_fx_missinghasta la conciliación, incluso para comisiones ya incluidas en un pago. - En CPA de primer depósito, un chargeback agregado puede no identificar el depósito revertido. Un resumen monetario ambiguo de un jugador ya registrado se rechaza. Si llegó antes de REG, se conserva y el registro se acepta, pero el CPA resultante queda en retención bajo
chargeback_reference_missing. Para revisar la referencia de la transacción, envía un chargeback conref_txn_idigual al ID de transacción del depósito original. - Los ingresos son independientes de los totales monetarios inmutables: cambiar el NGR diario es una rectificación, y la diferencia de comisión se registra como ajuste.
La respuesta
Un archivo legible devuelve 200. Los contadores son por evento, no solo por fila: una fila SALES puede registrar resumen monetario y NGR por separado, incluso con éxito parcial. Comprueba siempre rejected y 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."]
} | Campo | Tipo | Significado |
|---|---|---|
id | string | El ID de RevRake de esta importación. |
filename | string | El nombre que diste, o reg.csv / sales.csv. |
kind | string | reg o sales. |
uploaded_at | string | Cuándo se recibió. |
rows_total | integer | Filas de datos en el archivo. |
accepted | integer | Eventos guardados. |
duplicates | integer | Eventos que ya estaban guardados. |
rejected | integer | Filas o eventos rechazados. Cada uno aparece en el registro de entrada con la propia línea. |
skipped | integer | Filas sin nada que registrar. |
problems | array of string | Las primeras 20 líneas rechazadas, como line N: reason. Se omite cuando no hay ninguna. |
GET /v1/reports
Devuelve el informe de la página Informes: clics, clics marcados, registros, primeros depósitos y ganancias, agrupados de una forma, para los días hasta hoy en la zona horaria del workspace. Las cifras de hoy se actualizan cuando se pide un informe, como máximo cada 30 segundos en un programa con mucha actividad.
GET https://app.revrake.com/v1/reports?by=link&days=7 | Parámetro | Tipo | Ejemplo | Significado |
|---|---|---|---|
by | string | link | Cómo se agrupan las filas: day, link, sub1, country o affiliate. Por defecto, day. Con affiliate indicado, by=affiliate se responde como day. |
days | integer | 7 | Cuántos días, contando hoy: de 1 a 365. Por defecto, 30. |
from | string | 2026-09-01 | El primer día de cualquier periodo, YYYY-MM-DD en la zona horaria del workspace. Con from o to, days se ignora. Como máximo 366 días. |
to | string | 2026-09-30 | El último día del periodo, incluido. Por defecto, hoy. |
affiliate | integer | 1001 | Un número de afiliado. Limita el informe a ese afiliado. Solo con clave de operador; se ignora con una clave de afiliado, que siempre lee su propio afiliado. |
tag | string | Influencers | El nombre de un grupo, como en la página Grupos (repítelo para varios: cualquiera de ellos). Solo con la clave del operador; se ignora con affiliate o con una clave de afiliado. Un nombre desconocido da 422 unknown_tag. |
format | string | csv | csv devuelve un archivo CSV en lugar 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
}
} | Campo | Tipo | Significado |
|---|---|---|
by | string | La agrupación que se usó. |
from | string | El primer día del informe, YYYY-MM-DD. |
to | string | El último día: hoy en la zona horaria del workspace, o el to que indicaste. |
currency | string | La moneda de informes del workspace. Las ganancias están en ella. |
rows | array | Una entrada por grupo. |
rows[].key | string | Lo que identifica al grupo: el día, el ID de RevRake del enlace o del afiliado, el valor de sub1 o el país en dos letras. |
rows[].label | string | El texto que el portal muestra para el grupo: el día, la etiqueta de campaña del enlace, el valor de sub1, el país, o el nombre y el número del afiliado. |
rows[].clicks | integer | Clics limpios. |
rows[].flagged_clicks | integer | Clics marcados como bots, repeticiones, ráfagas o de un afiliado que no está aprobado. No están en clicks. |
rows[].registrations | integer | Jugadores atribuidos. |
rows[].ftds | integer | Primeros depósitos. |
rows[].earnings_minor | integer | Comisiones generadas, en unidades menores de la moneda de informes, al tipo de cambio del día en que se generó cada una. |
total | object | La suma de las filas, con la forma de una fila. |
Como CSV
Con format=csv la respuesta es un archivo. Su primera columna lleva el nombre de la agrupación y contiene la etiqueta; después vienen clicks, flagged_clicks, registrations, first_deposits y earnings_<currency> como decimal. La última línea es el 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
Devuelve una página del libro mayor de comisiones, con las más recientes primero salvo que ordenes de otra forma. Los importes de las filas nunca se editan y las filas nunca se borran, pero el status de una fila cambia después de escribirse: hold, luego approved, luego paid, o rejected. Una sincronización debe leer de nuevo un periodo y actualizar las filas por id, no solo traer las filas nuevas.
GET https://app.revrake.com/v1/commissions?from=2026-09-01&to=2026-10-01&per_page=500 | Parámetro | Tipo | Ejemplo | Significado |
|---|---|---|---|
status | string | approved | hold, approved, paid o rejected. Por defecto: todos. |
from | string | 2026-09-01 | Solo las filas ocurridas en este momento o después: RFC 3339, o un día YYYY-MM-DD en UTC. |
to | string | 2026-10-01 | Solo las filas ocurridas antes de este momento. Los mismos formatos. |
affiliate | integer | 1001 | Un número de afiliado. Limita las filas, totals y owed a ese afiliado. Solo con clave de operador; se ignora con una clave de afiliado. |
tag | string | Influencers | El nombre de un grupo, como en la página Grupos (repítelo para varios: cualquiera de ellos). Solo con la clave del operador; se ignora con affiliate o con una clave de afiliado. Un nombre desconocido da 422 unknown_tag. |
q | string | PLR-1001 | Las filas cuyo ID de jugador o nombre de afiliado contiene este texto. Hasta 100 caracteres. |
sort | string | when | when, affiliate, player, status, deposit o commission. Por defecto, when. |
dir | string | asc | asc o desc. Por defecto, desc. |
page | integer | 2 | La página, desde 1. Por defecto, 1. |
per_page | integer | 500 | 10, 25, 50, 100, 500 o 1000. Por defecto, 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
} | Campo | Tipo | Significado |
|---|---|---|
currency | string | La moneda de informes del workspace. |
totals | object | La suma por estado, en unidades menores de la moneda de informes: de todo el libro mayor, o del periodo cuando se indica from o to. Los demás filtros no la limitan, salvo affiliate. Las filas que aún esperan un tipo de cambio no están incluidas. |
owed | object | La suma por moneda y estado, en la moneda propia de cada comisión: lo que se debe. Sobre las mismas filas que totals. |
period | boolean | true cuando se indicó from o to, así que totals y owed cubren solo ese periodo. |
commissions | array | Las filas de esta página. |
total | integer | Cuántas filas cumplen los filtros. |
page | integer | La página que se respondió. |
per_page | integer | Las filas por página que se usaron. |
Una fila de comisión
| Campo | Tipo | Significado |
|---|---|---|
id | string | El ID de RevRake de la fila, un UUID. Nunca cambia: actualiza por él. |
occurred_at | string | Cuándo se generó la comisión: la hora del depósito, el final del periodo de ingresos o la hora del chargeback. |
type | string | cpa, revshare, adjustment, reversal o tier. |
amount_minor | integer | La comisión, en unidades menores de currency. Negativa para una reversión y para un ajuste a la baja. |
currency | string | La moneda de la comisión: la del deal. |
reporting_minor | integer | null | La comisión en la moneda de informes del workspace, al tipo de cambio del día en que se generó. null mientras falte ese tipo. |
gross_minor | integer | El depósito o los ingresos netos que hay detrás de la comisión, en unidades menores de gross_currency. |
gross_currency | string | La moneda del depósito o de los ingresos. |
rate_snapshot | integer | Las condiciones que se aplicaron: el importe del CPA en unidades menores, o el RevShare en centésimas de punto porcentual (2500 es 25%). |
status | string | hold, approved, paid o rejected. Consulta los estados de una comisión. |
status_reason | string | Por qué una fila sigue en retención o se rechazó. Vacío en los demás casos. Todos los valores están en la tabla de motivos. |
status_note | string | La nota del operador sobre una comisión retirada, tal como la lee el afiliado. Vacía en los demás casos. |
hold_until | string | null | Cuándo termina el periodo de retención. null para una reversión. |
affiliate_number | integer | El número del afiliado. |
affiliate_name | string | El nombre del afiliado. |
player_ref | string | Tu ID para el jugador. |
deal_name | string | El deal. |
deal_version | integer | La versión del deal con la que se valoró la fila. |
Listas del programa: afiliados, enlaces, acuerdos
Tu backend puede listar el programa como lo muestra el portal, firmado como las demás lecturas y contado en el mismo límite. GET /v1/affiliates y GET /v1/deals necesitan la clave del operador; con una clave de afiliado responden 403 operator_key_required. GET /v1/links responde los enlaces de la marca, o los de un afiliado con affiliate=<número>; con una clave de afiliado, los suyos.
Las listas se paginan con page (desde 1) y per_page (de 1 a 500, por defecto 100) e incluyen total. GET /v1/affiliates acepta también status: pending, approved, rejected o suspended. Crear afiliados o enlaces por la API aún no se ofrece: cuéntanos para qué lo usarías.
GET https://app.revrake.com/v1/links?affiliate=1001&per_page=100 Cada afiliado
| Campo | Tipo | Significado |
|---|---|---|
number | integer | El número de afiliado, la primera parte de la btag; en un enlace, el número de enlace, su segunda parte. |
name | string | El nombre del afiliado. |
email | string | La dirección de correo del afiliado. |
status | string | pending, approved, rejected o suspended. |
deal | string | El nombre del acuerdo del afiliado; vacío antes de la aprobación. |
created_at | string | Cuándo se creó, RFC 3339. |
decided_at | string | null | Cuándo se aprobó o rechazó la solicitud, o se suspendió la cuenta; null antes. |
Cada enlace
| Campo | Tipo | Significado |
|---|---|---|
affiliate_number | integer | El número del afiliado al que pertenece el enlace. |
number | integer | El número de afiliado, la primera parte de la btag; en un enlace, el número de enlace, su segunda parte. |
code | string | El código en la dirección del enlace. |
url | string | El enlace de seguimiento, en el dominio de seguimiento verificado de la marca o en el compartido. |
btag | string | <affiliate_number>_<number>. |
campaign | string | La etiqueta de campaña del enlace. |
landing_path | string | null | La página de tu sitio a la que lleva el enlace; null para la de la marca por defecto. |
subs | string[5] | sub1 a sub5 fijados en el enlace; cadenas vacías si no se fijaron. |
promo_code | string | null | El código promocional, en un enlace creado para uno; si no, null. |
vanity_url | string | null | La dirección corta /go/<nombre>, si el afiliado puso una; si no, null. |
created_at | string | Cuándo se creó, RFC 3339. |
Cada acuerdo
| Campo | Tipo | Significado |
|---|---|---|
name | string | El nombre del afiliado. |
currency | string | La moneda en la que el acuerdo gana y paga. |
listed | boolean | Si la página pública de alta muestra el acuerdo. |
default | boolean | Si los nuevos afiliados reciben este acuerdo. |
version | integer | La versión del acuerdo vigente ahora. |
effective_at | string | Cuándo entró en vigor esa versión, RFC 3339. |
terms | object | Los términos de esa versión: importes en unidades menores, porcentajes en puntos básicos (2500 = 25 %), días de retención, regla de meses negativos, la ventana de atribución propia del acuerdo, niveles. |
Exportación al data warehouse
Se configura en la página Integración: cada noche RevRake escribe el día anterior de cada marca en tu propio bucket de cualquier servicio compatible con S3, en <carpeta>/<marca>/<AAAA-MM-DD>/, con el día contado en la zona horaria del espacio de trabajo. Cuatro archivos CSV comprimidos con gzip (UTF-8, fila de cabecera, comillas según RFC 4180) y después manifest.json. El manifiesto se escribe el último: carga un día cuando exista y comprueba rows, bytes y sha256 de cada archivo.
Una fila pertenece al día en que llegó (un clic: cuando se hizo), así que un archivo diario que llega tarde está en el día en que llegó, nunca se pierde. Las horas son RFC 3339 en UTC con milisegundos; los importes, decimales en su moneda. Los estados son los del momento de la exportación: el de una comisión cambia después, así que vuelve a leer GET /v1/commissions para el actual. Un día fallido se reintenta cada hora y se recupera hasta siete días atrás; exportar un día de nuevo sobrescribe sus archivos. Pueden añadirse columnas al final de un archivo; schema_version del manifiesto cambia cuando un cargador debe cambiar.
manifest.json: schema_version, workspace, brand, day, timezone, from y to (los límites del día en UTC), generated_at y files: para cada uno, name, rows, bytes, sha256 (del archivo comprimido).
clicks.csv.gz: todos los clics en los enlaces de la marca ese día, también los marcados.
| Campo | Tipo | Significado |
|---|---|---|
click_id | string | El ID de clic, como se pasa a tu página de destino y en los postbacks. |
clicked_at | string | Cuándo se hizo el clic. |
affiliate_number | integer | El número de afiliado. |
link_number | integer | El número de enlace. |
btag | string | <affiliate_number>_<link_number>. |
country | string | Código de país de dos letras; vacío si se desconoce. |
device | string | desktop, mobile, tablet o bot, según el navegador. |
os | string | El sistema operativo, según el navegador. |
browser | string | El navegador. |
sub1 … sub5 | string | sub1 a sub5: los sub-ID del clic. |
utm_source … utm_content | string | utm_source a utm_content: las etiquetas UTM del clic. |
gclid, fbclid, ttclid, msclkid, wbraid | string | gclid, fbclid, ttclid, msclkid, wbraid: ID de clic de plataformas publicitarias en la URL de destino. |
referer | string | La página de la que venía el visitante. |
flags | string | Por qué no se cuenta el clic, separado por ;: bot, duplicate, burst, inactive. Vacío para un clic limpio. |
players.csv.gz: jugadores cuyo registro llegó ese día.
| Campo | Tipo | Significado |
|---|---|---|
player_id | string | Tu ID de jugador. |
registered_at | string | La hora de registro que enviaste. |
arrived_at | string | Cuándo lo registró RevRake. |
country | string | Código de país de dos letras; vacío si se desconoce. |
attributed | boolean | true cuando se acredita a un afiliado. |
attribution_reason | string | Por qué, como en la respuesta del postback: click, btag_only, promo_code, no_tag… |
affiliate_number | integer | El número de afiliado. |
link_number | integer | El número de enlace. |
btag | string | <affiliate_number>_<link_number>. |
click_id | string | El clic que acreditó al jugador; vacío si no hay. |
policy_version | integer | La versión de la política de atribución que lo decidió. |
events.csv.gz: todos los eventos aceptados de la marca que llegaron ese día, incluidos los resúmenes diarios SALES, con su payload.
| Campo | Tipo | Significado |
|---|---|---|
event_id | string | El ID del evento en RevRake. |
type | string | registration, deposit, chargeback, daily_sales o ngr; para una comisión, cpa, revshare, tier, adjustment o reversal. |
external_id | string | La clave del evento: el ID de jugador, el ID de transacción, o jugador y periodo. |
player_id | string | Tu ID de jugador. |
occurred_at | string | Cuándo ocurrió, según lo enviaste; en una comisión, cuándo se ganó. |
received_at | string | Cuándo aceptó RevRake el evento. |
source | string | s2s (la API) o file (un archivo diario). |
payload | JSON | El evento tal como lo leyó RevRake, en JSON. |
result | JSON | Lo que respondió RevRake, en JSON. |
commissions.csv.gz: filas de comisión escritas ese día.
| Campo | Tipo | Significado |
|---|---|---|
commission_id | string | El ID de la fila, el id de GET /v1/commissions. |
created_at | string | Cuándo se escribió la fila. |
occurred_at | string | Cuándo ocurrió, según lo enviaste; en una comisión, cuándo se ganó. |
type | string | registration, deposit, chargeback, daily_sales o ngr; para una comisión, cpa, revshare, tier, adjustment o reversal. |
status | string | hold, approved, paid o rejected, al exportar. |
status_reason | string | Por qué está retenida o rechazada; vacío si no. |
affiliate_number | integer | El número de afiliado. |
player_id | string | Tu ID de jugador. |
deal | string | El nombre del acuerdo. |
deal_version | integer | La versión del acuerdo que la valoró. |
gross | decimal | El depósito o ingreso neto que hay detrás. |
gross_currency | string | Su moneda. |
amount | decimal | La comisión. |
currency | string | La moneda del acuerdo. |
reporting_amount | decimal | La comisión en la moneda de informes; vacío mientras falte el tipo de cambio. |
reporting_currency | string | La moneda de informes del espacio de trabajo. |
reverses_id | string | En una reversión, la fila que revierte. |
payout_item_id | string | La línea de pago que la incluyó, cuando el mes se cerró. |
Idempotencia
Cada evento tiene una clave natural. Un reintento idéntico devuelve la respuesta guardada con "status":"duplicate". La respuesta de un depósito pendiente de registro se actualiza al llegar este. Los datos monetarios cambiados pueden generar conflicto; las rectificaciones NGR usan revisiones. Tras un timeout, reintenta con un timestamp de firma nuevo.
| Evento | Clave | La misma clave otra vez |
|---|---|---|
registration | player_id | La primera respuesta. La atribución no se decide de nuevo, aunque las etiquetas sean distintas. |
deposit | txn_id | La respuesta guardada, actualizada al llegar el registro pendiente. Cambiar jugador, importe o moneda genera conflicto. Un resumen SALES que cubra la transacción no elimina su ID. |
chargeback | txn_id | La primera respuesta. Un segundo chargeback del mismo depósito con un txn_id nuevo se rechaza. |
ngr | player_id y period | El mismo importe y la misma moneda: la primera respuesta. Un importe distinto: una corrección, guardada como una revisión nueva, con la diferencia como un adjustment. |
| CSV | Fila REG: player_id | Como un registration. |
| CSV | Resumen monetario SALES: marca, jugador y día de actividad | Los totales idénticos son replays. Cambiar depósitos, primer depósito, chargebacks, moneda o límites del día genera conflicto, incluso en un resumen antes de ceros. |
| CSV | Fila SALES, ingresos: el jugador y el día | Como ngr: una cifra cambiada en una nueva exportación es una corrección. |
Estados y errores
Todas las respuestas que la API puede dar y todos los estados que puede tener un registro, en un solo lugar. Un error se responde con un objeto JSON: error (el código), message (una frase en inglés) y, para invalid, field.
{"error":"invalid","field":"amount","message":"amount must be a positive decimal in KES, for example 25.00."} Estados HTTP y códigos de error
| Estado | error | Endpoint | Cuándo | Qué hacer |
|---|---|---|---|---|
| 200 | – | /v1/* | La petición se procesó. Para un evento: guardado, o ya guardado. Para un lote o un archivo: leído; mira los recuentos, algunos elementos pueden haberse rechazado. | |
| 400 | bad_request | POST /v1/postback | El cuerpo empieza como un array pero no es un array JSON de eventos. | Corrige la petición. La misma petición otra vez recibe la misma respuesta. |
| 401 | unknown_key | /v1/* | La clave en X-RevRake-Key es desconocida o está revocada, o es una clave de afiliado en un endpoint de escritura. | Usa la clave actual. Enviarla de nuevo no sirve. |
| 401 | bad_timestamp | /v1/* | X-RevRake-Timestamp falta, no es un número entero de segundos o se desvía más de cinco minutos. | Comprueba el reloj y el código de firma, y envíala de nuevo con un timestamp nuevo. |
| 401 | bad_signature | /v1/* | La firma no coincide con el timestamp y el cuerpo (en una lectura: la ruta y la query). | Comprueba el reloj y el código de firma, y envíala de nuevo con un timestamp nuevo. |
| 403 | affiliate_inactive | GET /v1/* | Una lectura con una clave de afiliado cuyo afiliado no está aprobado en el programa: pendiente, rechazado o suspendido. | No hay nada que corregir en la petición: el operador debe aprobar la cuenta. |
| 404 | unknown_affiliate | GET /v1/* | Una lectura con una clave de operador: ningún afiliado tiene el número indicado en affiliate. | Corrige la petición. La misma petición otra vez recibe la misma respuesta. |
| 405 | – | /v1/* | El endpoint no acepta ese método. Sin cuerpo JSON. | Corrige la petición. La misma petición otra vez recibe la misma respuesta. |
| 413 | too_large | POST /v1/* | El cuerpo supera el límite del endpoint. | Corrige la petición. La misma petición otra vez recibe la misma respuesta. |
| 422 | invalid | POST /v1/*, GET /v1/reports | El evento o el archivo no se puede usar tal como está. | Corrige la petición. La misma petición otra vez recibe la misma respuesta. |
| 429 | rate_limited | GET /v1/* | Más lecturas que el cupo de la clave. La respuesta lleva Retry-After. | Espera los segundos de Retry-After y envíala de nuevo. |
| 500 | internal | /v1/* | Algo falló en nuestro lado. | Envíala de nuevo. |
- Un lote sin eventos o con más de 1.000 se rechaza con
422invalidy sinfield. - Un
401por un timestamp o una firma incorrectos con una clave de operador aparece en el registro de entrada comobad_signature. Una petición con una clave desconocida, o con una clave de afiliado, no se registra. - Una lectura responde
422solo para un periodo de informe que no se puede usar (fromyto). Cualquier otro parámetro con un valor no permitido se ignora y se usa su valor por defecto. Compruebaby,pageyper_pageen la respuesta. - Reintenta ante un tiempo de espera agotado, ante un
429y ante un500. No reintentes ningún otro4xxsin cambios.
Qué indica field en un 422
field | Cuándo |
|---|---|
body | El cuerpo no es JSON válido, o lleva un campo desconocido o mal formado (por ejemplo, un ts que no es RFC 3339). |
type | type falta o no es uno de los cuatro. |
player_id | Falta o supera los 100 caracteres; un marcador que no se rellenó; para ngr, un jugador que no está registrado. |
click_id | Un marcador que no se rellenó. El mensaje empieza por misconfigured_macro. |
btag | Un marcador que no se rellenó. El mensaje empieza por misconfigured_macro. |
promo_code | Un marcador que no se rellenó. El mensaje empieza por misconfigured_macro. |
txn_id | Falta o supera los 200 caracteres. |
ref_txn_id | Falta; no se conoce ningún depósito con ese txn_id; o el depósito ya se había devuelto. |
amount | No es un decimal que la moneda admita; para un depósito, no es mayor que cero. |
currency | No es un código de tres letras. |
ggr | Para ngr: no es un importe decimal en la moneda. |
bonuses | Para ngr: no es un importe decimal en la moneda. |
fees | Para ngr: no es un importe decimal en la moneda. |
taxes | Para ngr: no es un importe decimal en la moneda. |
ts | Está más de un día en el futuro. |
period | No es un día ni un mes, o está en el futuro. |
from | Para GET /v1/reports: from o to no tiene la forma YYYY-MM-DD, from es posterior a to, o hay más de 366 días entre ellos. |
file | Para /v1/files: el archivo está vacío, no es CSV, no es un archivo REG ni SALES, le falta una columna obligatoria o tiene más de 200.000 filas. |
chargeback | En un archivo SALES: la celda chargeback no se puede aplicar. El mensaje dice por qué. |
El estado de un evento
En la respuesta a un evento, y por evento en results de un lote.
status | Significado |
|---|---|
accepted | Guardado ahora. La respuesta dice lo que hizo. |
duplicate | Ya guardado con la misma clave; un replay idéntico no cambia nada. Una coincidencia antigua de primer depósito entre canales también puede devolver duplicate_of. Los resúmenes SALES actuales conservan las transacciones reales de los postbacks y cuentan los importes diarios cubiertos una sola vez. |
rejected | Solo en un lote: el evento se rechazó, con field y message. Un evento individual que se rechaza es un 422. |
El efecto de un evento en el libro mayor
commission.effect | Significado |
|---|---|
created | Se creó una fila de comisión: un CPA, un RevShare o un ajuste por una cifra corregida. |
cancelled | Un chargeback rechazó una comisión que seguía en retención. |
reversed | Un chargeback añadió una fila de reversión contra una comisión ya aprobada o pagada. |
none | No se creó ninguna fila. reason dice por qué. |
Tipos de comisión
type | Significado |
|---|---|
cpa | Una por jugador cuando el mínimo del deal se alcanza con el primer depósito o los depósitos acumulados menos chargebacks, según su base de CPA. |
revshare | Un porcentaje de los ingresos netos de juego (NGR) de un jugador en un periodo. Negativa en un periodo en que el jugador ganó. |
adjustment | La diferencia que produce una cifra de ingresos corregida. |
reversal | El importe negativo de una comisión retirada después de aprobada o pagada: por un chargeback, o por el operador con un motivo. Se aprueba al momento y se compensa en el siguiente pago. |
tier | Un complemento de un tramo por volumen del deal: cuando un afiliado trae suficientes jugadores nuevos con depósito en un mes natural, la diferencia entre la tarifa del tramo y la tarifa base se añade junto a cada comisión de ese mes. Espera en la misma retención que su comisión, o se aprueba en el acto si esta ya lo está. Negativo cuando retira un complemento anterior, después de que su comisión se rechazara o revirtiera. |
Estados de una comisión
Una fila se escribe una vez y sus importes nunca cambian. Su estado sí:
status | Significado |
|---|---|
hold | En espera: de que termine el periodo de retención del deal, o de lo que indica status_reason. |
approved | El periodo de retención terminó y nada retiene la fila. Entra en el siguiente lote de pago mensual. Las reversiones y algunos ajustes están aprobados desde el principio. |
paid | El elemento de pago que contiene la fila se marcó como pagado. |
rejected | No se pagará. status_reason dice por qué, y status_note lleva la nota del operador cuando la hay. |
Valores de status_reason
Vacío si la fila solo espera su periodo de retención y normalmente en comisiones aprobadas o pagadas. Un tipo de chargeback ausente también puede bloquear una fila aprobada. Las retenciones por fraude esperan al operador o al vencimiento configurado de la alerta.
status_reason | Con el estado | Significado |
|---|---|---|
fx_rate_missing | hold | Falta un tipo de cambio necesario. La liquidación espera al tipo; los flujos diarios SALES usan su fecha de actividad y los postbacks individuales su día UTC. |
chargeback_fx_missing | hold, approved, paid | Un chargeback aún no puede valorarse. El CPA y sus complementos de nivel no pueden entrar en un nuevo pago ni marcarse como pagados en uno existente hasta que llegue el tipo y finalice la conciliación. |
chargeback_reference_missing | hold | Un resumen recibido antes del registro contiene un chargeback agregado que no identifica el depósito revertido para CPA de primer depósito. Se conservan el registro y el resumen; el CPA resultante queda en retención hasta revisar la referencia de la transacción. |
self_referral | hold | Una alerta de fraude abierta: el jugador parece ser el propio afiliado. Espera a que el operador libere o confirme la alerta. |
click_velocity | hold | Una alerta de fraude abierta, o un clic que formó parte de una ráfaga: más clics en un enlace desde una misma dirección en un minuto de los que permite la marca. Espera al operador. |
duplicate_conversion | hold | Una alerta de fraude abierta: se comunicó un segundo "primer" depósito del jugador. Espera al operador. |
paid_traffic | hold | Una alerta de fraude abierta: el clic que trajo al jugador llevaba el click ID de una red publicitaria. Espera al operador. |
disposable_email | hold | Una alerta de fraude abierta: el jugador se registró con un dominio de correo desechable. Espera al operador. |
geo_mismatch | hold | Una alerta de fraude abierta: el jugador se registró en un país distinto del país del clic. Espera al operador. |
fast_registration | hold | Una alerta de fraude abierta: el jugador se registró dentro del número de segundos fijado tras el clic. Espera al operador. |
affiliate_pending | hold | El afiliado aún no estaba aprobado cuando se generó la comisión. Cuando el afiliado se aprueba, la fila solo espera su periodo de retención normal. |
affiliate_rejected | hold | La solicitud del afiliado fue rechazada. |
affiliate_suspended | hold | El afiliado estaba suspendido cuando se generó la comisión. Cuando el afiliado se reactiva, la fila solo espera su periodo de retención normal. |
chargeback | rejected, approved | En una fila rechazada: un chargeback la canceló mientras estaba en retención. En una fila de reversión: revierte una comisión tras un chargeback. |
below_threshold | rejected | Al estar disponibles los tipos, el primer depósito válido o los depósitos acumulados netos quedaron por debajo del mínimo del deal. |
fraud | rejected, approved | El operador confirmó la alerta de fraude que retenía la fila, o retiró la comisión por fraude. |
base_rejected | rejected | Un complemento de tramo cuya comisión se rechazó mientras ambos estaban en retención. |
self_excluded | rejected, approved | El operador retiró la comisión: el jugador se autoexcluyó. En una fila rechazada la comisión aún no estaba en un pago; en una fila de reversión, sí. |
duplicate_account | rejected, approved | El operador retiró la comisión: el jugador tiene otra cuenta. |
bonus_abuse | rejected, approved | El operador retiró la comisión por abuso de bonos. |
terms_breach | rejected, approved | El operador retiró la comisión por incumplir las condiciones del programa. |
test_account | rejected, approved | El operador retiró la comisión: el jugador era una cuenta de prueba. |
other | rejected, approved | El operador retiró la comisión por otro motivo, escrito en status_note. |
Estados de los pagos
Un mes se cierra en un lote de pago con un elemento por afiliado y moneda. Los lotes y los elementos se gestionan en el portal; la API muestra su resultado como el estado paid de las filas de comisión.
| Estado del lote | Significado |
|---|---|
draft | El mes se cerró. El operador revisa los elementos. |
approved | Bloqueado. Con la facturación activada, cada elemento pagable recibe un número de factura. El archivo bancario (CSV) se puede descargar y los elementos se pueden pagar. |
paid | Todos los elementos por pagar se marcaron como pagados. |
| Estado del elemento | Significado |
|---|---|
payable | Se paga en este lote. |
carried | Por debajo del pago mínimo, o no mayor que cero: pasa al lote siguiente. |
held | El afiliado tiene una alerta de fraude abierta, o el programa retiene los pagos hasta verificar la identidad y la verificación aún no se ha registrado. El portal indica cuál. El operador puede liberar el elemento, y registrar la verificación de identidad lo libera. |
no_details | El afiliado no ha indicado dónde cobrar. No está en el archivo bancario. |
written_off | El mes terminó por debajo de cero y el deal del afiliado no arrastra un mes negativo: el saldo se da de baja en el cierre y nada pasa al siguiente lote. Con un tope, el elemento queda carried y registra como dada de baja la parte por encima del tope. |
paid | Marcado como pagado. Sus filas de comisión están en paid, y se avisa al tracker del afiliado. |
Por qué se rechaza una fila de un archivo
Una fila rechazada se cuenta en rejected, aparece en problems como line N: reason y se conserva en el registro de entrada con la propia línea. Además de los motivos por los que se puede rechazar un evento:
- La línea no es CSV válido.
- REG: falta
registration_dateoplayer_id, o no puede leerse. - SALES: falta
activitydateoplayer_id, o no puede leerse. - SALES, ingresos: el jugador no está registrado.
- SALES, dinero: un importe es negativo, ilegible o tiene demasiados decimales para su moneda.
- SALES: el primer depósito supera los depósitos brutos.
- SALES: se reexporta un día aceptado con totales monetarios, moneda o límites de zona horaria cambiados.
- SALES: los depósitos o chargebacks individuales conocidos superan el resumen del día.
- SALES: la prueba del primer depósito contradice otro día o una transacción conocida.
- SALES: un chargeback agregado es ambiguo para CPA de primer depósito de un jugador ya registrado.
Resultados en el registro de entrada
| Estado | Significado |
|---|---|
accepted | Guardado, con lo que hizo. |
duplicate | Ya estaba guardado; se devolvió la primera respuesta. |
rejected | Rechazado; el detalle indica qué campo y por qué. |
bad_signature | El timestamp o la firma eran incorrectos. |
test | Un evento de prueba desde la página Integración. Recorre el camino real y no guarda nada. |
Estados de una entrega de postback
| Estado | Significado |
|---|---|
queued | En espera de su primer intento, o del siguiente reintento. El portal la muestra como en espera o reintentando. |
delivered | El tracker respondió con un estado 2xx. |
dead | Todos los reintentos fallaron. El portal la muestra como abandonada; el afiliado puede reenviarla a mano. |
Estados de un afiliado
| Estado | Significado |
|---|---|
pending | Ha enviado su solicitud; el operador aún no ha decidido. |
approved | Activo en el programa. |
rejected | La solicitud fue rechazada. |
suspended | Detenido por el operador. |
El enlace de tracking
Un enlace de tracking lo abre un visitante, no lo llama tu plataforma. Guarda el clic y responde con una redirección 302 a la landing page de la marca. Mientras la marca no haya verificado su sitio, el visitante ve antes una página breve que dice adónde lleva el enlace. El host de destino viene de la configuración de la marca; un enlace solo puede elegir una ruta dentro de él.
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 Parámetros en el enlace
| Parámetro | Significado |
|---|---|
sub1 … sub5 | Los valores propios del afiliado, guardados con el clic y devueltos en los postbacks. Un valor en la dirección sustituye al fijado en el enlace. |
utm_source utm_medium utm_campaign utm_term utm_content | Se guardan con el clic y se pasan a la landing page. Un valor en la dirección sustituye al fijado en el enlace. |
gclid fbclid ttclid msclkid wbraid | Click IDs de redes publicitarias. Se guardan con el clic; están disponibles como macros de postback. No se pasan a la landing page. |
Parámetros añadidos a la URL de destino
| Parámetro | Significado |
|---|---|
cid | El click ID: 26 caracteres en base32 de Crockford (dígitos y letras mayúsculas sin I, L, O, U). Se ordena por tiempo. Guárdalo como texto. |
btag | <número de afiliado>_<número de enlace>: de uno a nueve dígitos, un guion bajo y de uno a nueve dígitos. |
utm_source … utm_content | Las etiquetas UTM del clic, cuando no están vacías. |
- Si la URL de destino de la marca contiene
{click_id}en su query, el click ID se pone ahí y no se añadecid. - Un clic en el enlace de un afiliado que no está aprobado se guarda y se marca, y llega sin
cidnibtag: no se puede atribuir. - Los bots, las previsualizaciones de enlaces, las peticiones
HEAD, las peticiones sin user agent y los clics repetidos en una hora se guardan y se marcan, no se descartan. Los informes los dejan fuera. - Los demás parámetros del enlace no se guardan ni se pasan.
- Un enlace al que el afiliado ha dado un nombre corto responde también en
/go/<name>, exactamente igual que en/c/<code>. Una marca con su propio dominio de tracking verificado sirve también ambas rutas en ese dominio. - Un código desconocido se responde con
404y una página de error en HTML, igual que un enlace cuya marca aún no ha fijado un destino.
Postbacks a los trackers de los afiliados
Esta parte no exige trabajo al operador. Cada afiliado añade hasta 10 destinos en su portal: una plantilla de dirección con macros y los eventos de los que quiere enterarse. Cuando se registra un evento de uno de los jugadores del afiliado, RevRake rellena la plantilla y la llama con una petición GET.
Eventos
| Evento | {status} | Cuándo |
|---|---|---|
registration | lead | Un jugador fue atribuido al afiliado. |
ftd | sale | Un primer depósito generó una comisión CPA para el afiliado. |
commission_approved | approved | Una comisión fue aprobada al terminar su periodo de retención. |
commission_rejected | rejected | Una comisión en retención fue rechazada tras una revisión. |
commission_paid | paid | Una comisión fue pagada en un lote de pago. |
chargeback | rejected | Un chargeback retiró una comisión. |
Palabras de estado por preset
Un destino creado con un preset envía, en {status}, las palabras que conoce su tracker. Los presets de Voluum, PeerClick y FunnelFlux Pro, y "Otro / personalizado", envían las palabras de la tabla de arriba. Las palabras siguen al preset que lleva el destino; su dirección guardada no se cambia nunca.
| Evento | Keitaro | Binom | RedTrack | BeMob |
|---|---|---|---|---|
registration | lead | lead | other | new |
ftd | sale | sale | pending | approved |
commission_approved | sale | sale | approved | approved |
commission_rejected | rejected | reject | declined | declined |
commission_paid | sale | sale | approved | approved |
chargeback | rejected | reject | declined | chargeback |
Macros
Una macro es un nombre entre llaves. Se sustituye por su valor, codificado para URL. Los nombres se comparan sin distinguir mayúsculas de minúsculas. Un nombre que RevRake no conoce se envía tal cual, así que una errata se ve en el registro de entregas. Un valor que no se conoce queda vacío.
| Macro | Significado |
|---|---|
{click_id} | El click ID de RevRake (el cid de la URL de destino). Vacío cuando el jugador se atribuyó solo por btag. |
{sub1} … {sub5} | sub1 a sub5 del clic. sub1 suele llevar el click ID del tracker del afiliado. |
{event} | El nombre del evento, como en la tabla de arriba. |
{status} | La palabra que usan los trackers para el evento, como en la tabla de arriba, o la palabra propia del tracker en un destino creado con un preset (consulta la tabla siguiente). |
{event_id} | Distinto para cada evento y estable entre reintentos. |
{conversion_id} | El mismo para todos los eventos de una misma comisión: ftd, commission_approved, commission_rejected, commission_paid y chargeback. Un registro tiene el suyo. Úsalo como ID de transacción en un tracker que actualiza una conversión que llega de nuevo con el mismo ID, para que los ingresos se cuenten una sola vez. |
{player_id} {affiliate_id} {brand_id} {link_id} | El ID de jugador del operador, el número de afiliado, el programa y el número de enlace. |
{campaign} | La etiqueta de campaña del enlace. |
{commission} {commission_minor} | La comisión, como decimal (40.00) o en unidades menores (4000). Vacía para un registro, 0 para una comisión rechazada, negativa para un chargeback. |
{amount} {amount_minor} | El depósito o los ingresos que hay detrás de la comisión, como decimal o en unidades menores, en la moneda en que se hizo. Vacío para un registro. |
{amount_currency} | El código ISO de la moneda del depósito. Vacío para un registro. |
{commission_currency} | El código ISO de la moneda de la comisión: la del deal. Vacío para un registro. |
{currency} | El código ISO de la moneda a la que se refiere el postback: la de la comisión cuando hay comisión, si no la del depósito, y si no la moneda de informes del workspace. |
{occurred_at} {occurred_at_unix} | Cuándo ocurrió, en formato RFC 3339 o en segundos Unix. |
{country} {device} {os} {browser} {referer} | Lo que se sabe del clic: país, tipo de dispositivo, sistema operativo, navegador, referrer. |
{utm_source} {utm_medium} {utm_campaign} {utm_term} {utm_content} | Las etiquetas UTM del clic. |
{gclid} {fbclid} {ttclid} {msclkid} {wbraid} | Click IDs de redes publicitarias, cuando el clic los llevaba. |
Otros nombres para las mismas macros
Los trackers usan nombres distintos para las mismas cosas. También se aceptan estos:
{subid} | = {sub1} |
{clickid} | = {click_id} |
{payout} | = {commission} |
{txid} | = {event_id} |
{transaction_id} | = {event_id} |
Presets
El portal de afiliado incluye estas plantillas. El afiliado sustituye el host por el de su tracker. Cada una sigue la documentación de su tracker: {conversion_id} va en el parámetro de transacción donde el tracker actualiza una conversión que llega de nuevo con el mismo ID, {event_id} donde descartaría los eventos posteriores.
| Keitaro | https://YOUR-TRACKER-DOMAIN/POSTBACK-KEY/postback?subid={sub1}&status={status}&payout={commission}¤cy={currency}&tid={conversion_id} |
| Binom | https://YOUR-TRACKER-DOMAIN/click?cnv_id={sub1}&payout={commission}&cnv_status={status}&cnv_currency={currency} |
| Voluum | https://YOUR-TRACKER-DOMAIN/postback?cid={sub1}&payout={commission}¤cy={currency}&txid={event_id}&et={event} |
| RedTrack | https://YOUR-TRACKER-DOMAIN/postback?clickid={sub1}&sum={commission}&status={status}&type={event}&rdtk_event_id={event_id} |
| BeMob | https://YOUR-TRACKER-DOMAIN/postback?cid={sub1}&payout={commission}&txid={conversion_id}&status={status} |
| PeerClick | https://YOUR-TRACKER-DOMAIN/postback?cid={sub1}&payout={commission}&txid={conversion_id}&status={status}¤cy={currency} |
| FunnelFlux Pro | https://YOUR-TRACKER-DOMAIN/pb/?hit={sub1}&tx={conversion_id}&rev={commission}&status={status} |
Un ejemplo
https://YOUR-TRACKER-DOMAIN/postback?click_id={sub1}&event={event}&status={status}&payout={commission}¤cy={currency}&tid={conversion_id}&eid={event_id} GET https://tracker.example/postback?click_id=TRACKER_CLICK_ID&event=ftd&status=sale&payout=40.00¤cy=KES&tid=0199b2c9-0a11-7c3e-9f20-7d4b1e6a2c58&eid=0199b2c9-03aa-7e10-b6d2-91c07f5e2a18%3Aftd Entrega
- Un postback se pone en cola en la misma transacción que el evento del que informa, y se envía en unos diez segundos, los más antiguos primero.
- La petición es un
GETcon el user agentRevRake-Postback/1. Sigue como máximo tres redirecciones y abandona a los 5 segundos. Los certificados se verifican. - Una respuesta
2xxes un éxito. Cualquier otra cosa es un fallo y se reintenta a 1 minuto, 5 minutos, 25 minutos, 2 horas, 10 horas y 24 horas. - Tras el último reintento, la entrega se marca como abandonada. El afiliado puede reenviarla a mano.
- Cada intento se registra con su duración, el estado y los primeros 500 caracteres de la respuesta.
- Tras 5 y tras 15 fallos seguidos, el propietario del destino recibe un email. A los 20 el destino se desactiva. Sus entregas, incluidas las de los eventos que ocurren mientras está desactivado, siguen poniéndose en cola y se envían cuando el afiliado lo activa de nuevo. Un destino que el propio afiliado desactivó no recibe nada nuevo.
- RevRake solo llama a direcciones públicas. Las direcciones de loopback, privadas, link-local y de NAT de operador (carrier-grade NAT) se rechazan al establecer la conexión, así que una redirección no puede saltarse el control.
- La plantilla debe ser una dirección
http://ohttps://completa de como máximo 2.000 caracteres.
Por qué falló un intento
timeout | Sin respuesta en 5 segundos. |
dns | No se encontró el nombre de host. |
refused | La conexión fue rechazada. |
blocked_address | La dirección es privada o local. |
tls | Un problema de certificado. |
too_many_redirects | Más de tres redirecciones. |
http_error | El tracker respondió con un estado distinto de 2xx. |
other | La conexión falló por otro motivo. |
Webhooks del operador
Los webhooks cuentan a los sistemas propios del operador (un CRM, un data warehouse, un canal de chat) lo que pasa en el programa. Un admin del workspace añade un endpoint en la página Integración del portal del operador: la dirección de tu servidor, los eventos que se envían y una descripción opcional. Por cada uno de esos eventos, RevRake envía un POST JSON firmado a la dirección. Los demás miembros del equipo no pueden ver ni cambiar los endpoints.
La dirección es una dirección https:// completa en un host público (se acepta http://, pero usa https://). Cada endpoint tiene su propio secreto, que empieza por whsec_. Se muestra una sola vez, cuando se crea el endpoint o se rota su secreto: cópialo en ese momento. Rotar crea un secreto nuevo al instante, y el anterior deja de firmar.
Un endpoint también puede ser un canal de Slack o Microsoft Teams: elige «Mensaje de Slack» o «Mensaje de Microsoft Teams» y pega la dirección de incoming webhook del canal (para Teams, un flujo «Cuando se recibe una solicitud de webhook de Teams»). Cada evento llega entonces como una frase en el canal en lugar de JSON firmado, y un importe puede dejar solo los primeros depósitos desde ese tamaño.
Eventos
Marca uno o varios por endpoint. Los campos de data se describen más abajo; un campo que no aplica se omite.
| Evento | Campos de data | Cuándo |
|---|---|---|
affiliate.applied | affiliate_number affiliate_name email promotes_on traffic | Alguien solicitó unirse en la página de tu programa o mediante un enlace de invitación a un deal. Se envía cuando se hace la solicitud, que puede ser antes de que el solicitante haya confirmado su email. Un afiliado que añades e invitas tú mismo envía affiliate.approved en su lugar. |
affiliate.approved | affiliate_number affiliate_name email reason decided_by invited | Aprobaste a un afiliado: una solicitud nueva, o uno que habías rechazado o suspendido. Un afiliado que añades e invitas tú mismo queda aprobado al instante y envía este evento con invited a true. |
affiliate.rejected | affiliate_number affiliate_name email reason decided_by | Rechazaste una solicitud. |
affiliate.suspended | affiliate_number affiliate_name email reason decided_by | Suspendiste a un afiliado aprobado. |
player.registered | player_id occurred_at attributed affiliate_number affiliate_name attribution_reason | Se registró un jugador, por un evento firmado o una fila de un archivo REG, atribuido o no. Una vez por jugador. |
commission.created | player_id occurred_at attributed affiliate_number affiliate_name commission_id commission_type amount amount_currency commission commission_currency | Se creó una fila de comisión: CPA válido según la base de primer depósito o depósitos acumulados del deal, o revenue share o ajuste de los ingresos netos informados. Los complementos de nivel y las reversiones no envían este evento. |
commission.approved | player_id occurred_at attributed affiliate_number affiliate_name commission_id commission_type amount amount_currency commission commission_currency | Una comisión en retención se aprobó porque terminó su periodo de retención, complementos de tier incluidos. Una fila aprobada desde el principio no envía ninguno. |
commission.rejected | player_id occurred_at attributed affiliate_number affiliate_name commission_id commission_type amount amount_currency commission commission_currency | Una comisión en retención se rechazó porque confirmaste la alerta de fraude que la retenía. commission es cero. Un CPA cancelado por un chargeback envía commission.reversed en su lugar. |
commission.paid | player_id occurred_at attributed affiliate_number affiliate_name commission_id commission_type amount amount_currency commission commission_currency | Se pagó una fila de comisión: un evento por cada fila de una partida de pago marcada como pagada, filas de reversión y de tier incluidas. |
commission.reversed | player_id occurred_at attributed affiliate_number affiliate_name commission_id commission_type amount amount_currency commission commission_currency | Un chargeback anuló un CPA: cancelado mientras estaba en retención, o revertido con una fila nueva después de aprobarse o pagarse. commission_id identifica el CPA, commission es su negativo y amount es el depósito que sufrió el chargeback. |
payout.batch_closed | batch_id period reference items payable_by_currency closed_by_hand | Un mes se cerró en un lote de pago en borrador, por una persona o por el cierre programado. |
payout.batch_approved | batch_id | Un lote de pago en borrador se aprobó y se bloqueó. |
payout.paid | batch_id item_id affiliate_number affiliate_name currency amount | Una partida de un lote aprobado se marcó como pagada. Un lote cuyas partidas están todas pagadas no envía ningún evento propio. |
tracking.health_alert | brand kind since quiet_hours clicks_at_least | Los enlaces de una marca recibieron al menos el número de clics fijado en las horas fijadas y no llegó ningún registro (página Integración, Salud del seguimiento). Se envía una vez; se comprueba cada hora. |
tracking.health_cleared | brand raised_at cleared_at | Llegó el primer registro tras una alerta de salud del seguimiento, y la alerta se borra. |
flag.raised | kind detail affiliate_number affiliate_name | Se creó una nueva marca de fraude sobre un afiliado (por una regla, un primer depósito repetido o autorreferido). Las marcas por ráfagas de clics solo están en la página Marcas. |
postback.destination_off | destination failures affiliate_number affiliate_name | El destino de postbacks de un afiliado se desactivó tras los envíos fallidos seguidos que permite la escala de reintentos. |
file.late | source date | Un archivo diario descargado con horario no había llegado tres horas después de su hora. Una vez por archivo y día. |
webhook.test | message | Solo desde el botón "Enviar prueba". No se puede marcar, no se reintenta y no aparece en el registro de entregas. |
El cuerpo
Cada petición lleva un objeto JSON:
| Campo | Tipo | Significado |
|---|---|---|
id | string | El ID del evento, un UUID. El mismo para cada endpoint que recibe el evento y en cada reintento: úsalo para descartar repeticiones. |
type | string | El evento, como en la tabla de arriba. |
created_at | string | Cuándo se puso el evento en cola, en formato RFC 3339 en UTC, con fracciones de segundo. |
data | object | Los datos del evento. Mira los campos más abajo. |
{
"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"
}
} Las claves pueden venir en cualquier orden, y el espaciado del JSON no es fijo. Verifica los bytes tal como llegan y después interprétalos.
Cabeceras
| Cabecera | Valor |
|---|---|
RevRake-Event | El evento, igual que type en el cuerpo. |
RevRake-Delivery | El ID de esta entrega a este endpoint, un UUID. El mismo en cada reintento y cuando una entrega se reenvía a mano. |
RevRake-Timestamp | Cuándo se envió este intento, en segundos Unix. Nuevo en cada intento. |
RevRake-Signature | El HMAC-SHA256 en hexadecimal en minúsculas, con el secreto del endpoint, de <timestamp>.<raw body>. Nueva en cada intento. |
La petición también lleva Content-Type: application/json y el user agent 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>"> Los campos de data
| Campo | Tipo | Significado |
|---|---|---|
affiliate_number | integer | El número del afiliado. |
affiliate_name | string | El nombre del afiliado. |
email | string | La dirección de email con la que el afiliado inicia sesión. |
promotes_on | string | Lo que el solicitante escribió sobre dónde promociona. |
traffic | array of string | Las fuentes de tráfico que marcó el solicitante: seo, telegram, paid_social, youtube, email u offline. |
reason | string | El motivo que se dio al afiliado. Siempre presente en un rechazo o una suspensión; puede estar vacío en una aprobación. |
decided_by | string | La dirección de email del miembro del equipo que decidió, o que añadió al afiliado. |
invited | boolean | true cuando añadiste e invitaste al afiliado tú mismo, sin solicitud. Se omite en los demás casos. |
player_id | string | Tu ID del jugador. |
occurred_at | string | Cuándo ocurrió, en formato RFC 3339: el registro, el depósito, el final del periodo de ingresos o el chargeback. En una aprobación, un rechazo o un pago: el momento del cambio. |
attributed | boolean | Si el jugador pertenece a un afiliado. Siempre true en una comisión. |
attribution_reason | string | Por qué el jugador se atribuyó o no: uno de los motivos de atribución. |
commission_id | string | El ID de RevRake de la fila de comisión: el id de la fila en GET /v1/commissions. El mismo en cada evento sobre esa comisión. |
commission_type | string | El tipo de la fila de comisión: cpa, revshare, adjustment o tier, y reversal para una fila de reversión en commission.paid. En commission.reversed, el tipo del CPA que se revirtió. |
amount | string | En una comisión: el depósito o los ingresos netos que la originan, como decimal, en su propia moneda. En payout.paid: el importe de la partida, como decimal. |
amount_currency | string | El código ISO de la moneda de amount. |
commission | string | La comisión, como decimal en la moneda del deal (40.00). Negativa en una reversión, un ajuste a la baja y un periodo que ganó el jugador. |
commission_currency | string | El código ISO de la moneda de la comisión: la del deal. |
batch_id | string | El ID de RevRake del lote de pago, un UUID. |
period | string | El mes que cierra el lote, YYYY-MM. |
reference | string | La referencia del lote, la del archivo bancario, como po_2026-09_demo. |
items | integer | Cuántas partidas tiene el lote, sea cual sea su estado. |
payable_by_currency | object | Por moneda, la suma de los importes de todas las partidas, como decimal (1250.00), sea cual sea su estado, partidas pasadas al mes siguiente incluidas. |
closed_by_hand | boolean | true cuando una persona cerró el mes, false en el cierre programado. |
item_id | string | El ID de RevRake de la partida de pago, un UUID. |
currency | string | La moneda de la partida. |
message | string | Una frase que dice que la prueba llegó. |
Verificar una petición
- Lee el cuerpo en bruto como bytes, antes de interpretar el JSON.
- Comprueba que
RevRake-Timestampestá a menos de 5 minutos de tu reloj, y rechaza la petición si no, para que una petición grabada no se pueda reproducir más tarde. - Calcula el HMAC-SHA256 en hexadecimal del timestamp, un punto y el cuerpo en bruto con el secreto del endpoint, y compáralo con
RevRake-Signatureen tiempo constante. - Solo entonces interpreta el cuerpo. Si ya procesaste su
id, responde2xxy no hagas nada. - Responde con cualquier
2xxen menos de 5 segundos, y haz el trabajo lento después.
La misma comprobación en Node.js y Python, tal como la muestra la página Integración:
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", "")) Entrega, reintentos y desactivación
- Una entrega se pone en cola en la misma transacción que el cambio que informa, así que un evento se envía si y solo si el cambio se guardó. Se envía en unos diez segundos, el más antiguo primero.
- La entrega es al menos una vez: el mismo evento puede llegar más de una vez, por ejemplo cuando tu respuesta se pierde por el camino. Descarta las repeticiones por el
iddel cuerpo. - El orden no está garantizado: un reintento puede llegar después de un evento posterior. Ordena los eventos por
created_atuoccurred_at, no por el momento en que llegan. - Una respuesta
2xxen menos de 5 segundos es un éxito. Cualquier otra cosa es un fallo y se reintenta tras 1 minuto, 5 minutos, 25 minutos, 2 horas, 10 horas y 24 horas, con el mismo cuerpo y un timestamp y una firma nuevos. - Tras el último reintento, la entrega se marca como abandonada. Un admin puede reenviarla con "Enviar de nuevo" desde el registro de entregas; entonces vuelve a recibir todo el calendario de reintentos.
- Tras 20 intentos fallidos seguidos, el endpoint se desactiva. Sus eventos siguen en cola, y todo lo que se acumuló se envía cuando un admin lo vuelve a activar. No se envía ningún email cuando esto ocurre: el estado de cada endpoint está en la página Integración.
- Un endpoint que desactivas tú mismo no recibe nada nuevo: no se pone en cola ningún evento para él mientras está desactivado.
- RevRake solo llama a direcciones públicas: se rinde tras 5 segundos y verifica los certificados. Las direcciones de loopback, privadas, link-local y de NAT de operador (CGNAT) se rechazan. Una redirección no se sigue: una respuesta
3xxes un fallo con el errorredirect, y el registro de entregas muestra la dirección a la que apuntaba.
Pruébalo y lee el registro de entregas
- Pulsa "Enviar prueba" en un endpoint: se envía al instante un evento
webhook.test, firmado como uno real, y ves el estado que respondió tu endpoint y cuánto tardó. Una prueba no se reintenta, no aparece en el registro de entregas y no cuenta como fallo. - El registro de entregas de un endpoint muestra sus últimas 100 entregas: el evento, el estado (en espera, reintentando, abandonado o entregado), el número de intentos, el último estado o error, los primeros 500 caracteres de tu respuesta, el próximo intento y el cuerpo tal como se envió.
- Un intento fallido se describe con las mismas palabras que un postback (mira por qué falló un intento), más
redirect: tu endpoint respondió con un3xx. Los estados son los de una entrega 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."
}
} Buenas prácticas
- Guarda el secreto en tu servidor, nunca en una página ni en una app. Si se filtra, rótalo y pasa tu endpoint al nuevo enseguida: el secreto anterior deja de valer al instante.
- Responde con un
4xxa una petición cuya firma no coincide. Se reintenta, firmada con el secreto actual, así que una entrega hecha mientras rotas no se pierde. - Guarda el
idde cada evento que hayas procesado, e ignora un evento que ya hayas visto. - Responde primero y trabaja después: pon el evento en una cola propia y responde
2xx, para que una base de datos lenta nunca haga que RevRake reintente. - Introduce la dirección final de tu endpoint. Una redirección no se sigue: el intento falla con
redirect, y el registro de entregas indica la dirección que debes usar. - Usa
commission_idpara unir los eventos sobre una misma comisión, y concilia conGET /v1/commissions: una lectura nocturna de los últimos 60 días recupera todo lo que se haya perdido. - Marca solo los eventos que uses. Cada uno es una petición a tu servidor.
Archivo OpenAPI
Los cuatro endpoints firmados, con cada estado y cada motivo como una enumeración, están descritos en openapi.yaml (OpenAPI 3.1), para generadores de código y clientes de API. Su dirección no cambia.
¿Listo para conectar? Crea tu programa y obtén tu clave.
Creas tu programa tú mismo: te registras, confirmas tu email y entras en pocos minutos. Gratis para operadores, para siempre. Sin tarjeta.
¿Preguntas? Escribe a hello@revrake.com