Referencia de la API

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.

EndpointSignificado
POST https://app.revrake.com/v1/postbackInforma de un evento, o de un lote de eventos. Firmado.
POST https://app.revrake.com/v1/filesEnvía un archivo diario REG o SALES. Firmado.
GET https://app.revrake.com/v1/reportsLee un informe, como JSON o CSV. Firmado, con una clave de operador o de afiliado.
GET https://app.revrake.com/v1/commissionsLee 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/affiliatesLista los afiliados de la marca. Firmado, con la clave del operador.
GET https://app.revrake.com/v1/linksLista los enlaces de seguimiento. Firmado, con una clave de operador o de afiliado.
GET https://app.revrake.com/v1/dealsLista 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 porDe quién esQué 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 403 affiliate_inactive.
  • Con una clave de afiliado, el parámetro affiliate se ignora y by=affiliate se 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:

CabeceraValor
X-RevRake-KeyTu ID de clave. Empieza por pb_ (operador) o ak_ (afiliado).
X-RevRake-TimestampLa hora Unix actual en segundos. Una petición se acepta dentro de los cinco minutos anteriores o posteriores.
X-RevRake-SignatureEl 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:

  1. Construye el cuerpo y conserva los bytes exactos que vas a enviar.
  2. Toma la hora Unix actual en segundos, como texto decimal.
  3. Concatena el timestamp, un punto (.) y el cuerpo.
  4. Calcula el HMAC-SHA256 de eso con tu secreto y escríbelo en hexadecimal en minúsculas.
  5. 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-Type no es obligatoria. Envía application/json para eventos y text/csv para 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ímiteSignificado
120Lecturas por clave y minuto: GET /v1/reports y GET /v1/commissions juntas
30 000Peticiones de eventos por clave y minuto: POST /v1/postback, hasta 1.000 de golpe
60Archivos por clave y minuto: POST /v1/files, hasta 10 de golpe
4 MBCuerpo de POST /v1/postback, un evento o un lote
32 MBCuerpo de POST /v1/files
1 – 1000Eventos en un lote
200 000Filas de datos en un archivo
300 sDiferencia entre X-RevRake-Timestamp y la hora real, en cualquiera de los dos sentidos
24 hHasta cuánto en el futuro pueden estar ts y period
100Longitud de player_id, en caracteres
200Longitud de txn_id, en caracteres
0 / 2Decimales de un importe: los de la moneda (dos en la mayoría, ninguno en monedas como XOF, UGX o JPY)
1 – 365days de un informe
10, 25, 50, 100, 500, 1000per_page del libro mayor: uno de estos valores
1 – 100 000page del libro mayor
100Longitud de q, en caracteres; un texto más largo se corta
200Longitud 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
10Destinos de postback por afiliado
2000Longitud de una plantilla de dirección de postback, en caracteres
5 sTiempo que tiene un tracker para responder a un postback
50Registro de entrada que muestra el portal: las últimas entradas de los últimos 7 días
2000Longitud de la dirección de un endpoint de webhook, en caracteres
5 sTiempo que tiene un endpoint de webhook para responder
20Intentos de webhook fallidos seguidos antes de que el endpoint se desactive
100Entregas por endpoint de webhook que muestra el registro de entregas
2 – 40Longitud de un código promocional: letras, dígitos, guiones y guiones bajos
32 MBTamaño de un archivo que RevRake descarga desde tu dirección
60 sTiempo que tiene tu dirección para entregar un archivo descargado
0 – 3600 sSegundos tras el clic para la regla de tiempo de registro (0 la desactiva)
0 – 365Días hasta que una alerta de fraude abierta se decide sola (0 significa nunca)
1 – 200Alertas 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 / minEnlaces 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 MBTamañ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 (siempre 120) y X-RateLimit-Remaining (cuántas lecturas quedan en este momento).
  • Superado el límite, la respuesta es 429 rate_limited con una cabecera Retry-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
  • type es uno de registration, 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.
  • ts es 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 (Z o +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" o 25.5. Sin separador de miles, sin exponente y sin más decimales de los que tiene la moneda.
  • currency es 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_id y btag: 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"}
CampoTipoObligatorioEjemploSignificado
player_idstringsí"PLR-1001"Tu ID para el jugador. Hasta 100 caracteres. La clave del evento.
click_idstringno"01K6QF3M8Z5T7W9XB2C4D6E8GH"El cid con el que llegó el jugador: 26 caracteres, letras y dígitos; en mayúsculas o minúsculas.
btagstringno"1001_1"El btag que tu plataforma guardó en el jugador: <número de afiliado>_<número de enlace>, solo dígitos.
promo_codestringno"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_hashstringno"9f86d081…0f00a08"SHA-256 del email en minúsculas, en hexadecimal. Solo lo usa el control de autorreferencia.
email_domainstringno"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ó.
ipstringno"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.
countrystringno"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.
tsstringno"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}
CampoTipoObligatorioEjemploSignificado
player_idstringsí"PLR-1001"Tu ID para el jugador. Hasta 100 caracteres.
txn_idstringsí"TX-88213"Tu ID de transacción. Hasta 200 caracteres. La clave del evento.
amountstring | numbersí"25.00"El depósito, mayor que cero.
currencystringno"KES"La moneda del depósito. Se acepta cualquier moneda.
tsstringno"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.
firstbooleannotruetrue 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"}
CampoTipoObligatorioEjemploSignificado
txn_idstringsí"CB-5001"Tu ID para el propio chargeback. Hasta 200 caracteres. La clave del evento.
ref_txn_idstringsí"TX-88213"El txn_id del depósito devuelto.
player_idstringno"PLR-1001"Opcional. Si se envía, debe coincidir con el jugador del depósito referenciado.
tsstringno"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"}
CampoTipoObligatorioEjemploSignificado
player_idstringsí"PLR-1001"Tu ID para el jugador. Hasta 100 caracteres.
periodstringsí"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.
amountstring | numbersí"118.40"Los ingresos netos de juego (NGR) del periodo. Negativos cuando el jugador ganó.
currencystringno"KES"La moneda del importe.
ggrstring | numberno"240.00"Opcional. Los ingresos brutos de juego (GGR) del periodo, en la misma moneda. Se muestran al afiliado; no cambian la comisión.
bonusesstring | numberno"80.00"Opcional. Los bonos descontados de los ingresos brutos. Solo se muestran.
feesstring | numberno"21.60"Opcional. Las comisiones descontadas de los ingresos brutos, como las de pago o de plataforma. Solo se muestran.
taxesstring | numberno"20.00"Opcional. Los impuestos descontados de los ingresos brutos. Solo se muestran.
tsstringno"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.

CampoTipoSignificado
statusstringaccepted, o duplicate cuando el evento ya estaba guardado.
event_typestringEl type que enviaste.
event_idstringEl ID de RevRake del evento guardado, un UUID. El mismo en cada repetición.
attributionobjectPara un registro: la decisión.
attribution.attributedbooleanSi el jugador pertenece a un afiliado.
attribution.reasonstringPor qué. Consulta la tabla de abajo.
attribution.affiliate_numberintegerEl afiliado. Solo cuando está atribuido.
attribution.link_numberintegerEl enlace del afiliado. Solo cuando está atribuido.
attribution.click_idstringEl clic con el que coincidió, cuando lo hay.
attribution.policy_versionintegerLa versión de la política de atribución de la marca que decidió.
attribution.window_daysintegerLa ventana de atribución de esa versión, en días.
attribution.modelstringlast_click o first_click: el modelo de esa versión.
commissionobjectPara un depósito, un chargeback y ngr: lo que el evento hizo en el libro mayor.
commission.effectstringcreated, cancelled (se rechazó una comisión en retención), reversed (se añadió una fila de reversión) o none.
commission.typestringcpa, revshare, adjustment o reversal.
commission.amount_minorintegerEl 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.statusstringhold, approved o rejected.
commission.reasonstringPor qué no se creó nada, cuando effect es none. Consulta la tabla de abajo.
waiting_for_playerbooleantrue para un depósito cuyo jugador aún no está registrado. Se guarda y se valora cuando llega el registro.
duplicate_ofstringPara 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.
detailstringCon 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.

reasonSignificado
clickAtribuidoEl click ID coincidió con un clic dentro de la ventana.
first_clickAtribuidoLa 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_clickAtribuidoEl btag coincidió con un enlace, y se encontró un clic en ese enlace dentro de la ventana.
btag_onlyAtribuidoEl btag coincidió con un enlace; no se encontró ningún clic dentro de la ventana.
promo_codeAtribuidoNi el click ID ni el btag atribuyeron al jugador, y el código promocional coincidió con un código de un afiliado aprobado.
no_tagNo atribuidoEl registro no llevaba ni click ID ni btag.
unknown_clickNo atribuidoEl click ID no es uno de los tuyos, y no se envió ningún btag.
unknown_btagNo atribuidoEl btag está mal formado o no coincide con ningún enlace.
outside_windowNo atribuidoEl clic es más antiguo que la ventana de atribución, o posterior al registro.
affiliate_inactiveNo atribuidoEl afiliado no está aprobado, o no lo estaba cuando se hizo el clic.
unknown_promo_codeNo atribuidoSolo se envió un código promocional, y no coincide con ninguno de tus códigos.
promo_code_retiredNo atribuidoSolo se envió un código promocional, y fue retirado.

Por qué no se creó ninguna comisión

commission.reasonSignificado
unattributedEl jugador no pertenece a ningún afiliado.
not_firstNo es el primer depósito del jugador.
already_earnedEl CPA del jugador ya se había generado. Un segundo "primer" depósito no paga nada y genera una alerta.
no_deal_versionEl afiliado no tiene deal, o el deal no tenía ninguna versión en el momento del evento.
country_blockedEl jugador es de un país por el que el deal no paga. Sigue registrado y atribuido.
no_cpa_in_dealEl deal no paga CPA.
below_thresholdEl 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_commissionPara un chargeback: el depósito no había generado nada.
no_revshare_in_dealPara ngr: el deal no paga RevShare.
fx_rate_missingPara ngr: aún no hay tipo de cambio para el periodo. El informe se guarda y se valora cuando existe un tipo.
unchangedPara 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
}
CampoTipoSignificado
resultsarrayUna entrada por evento, en el orden de envío.
results[].indexintegerLa posición del evento en el array, desde 0.
results[].statusstringaccepted, duplicate o rejected.
results[].event_typestringComo en una respuesta individual. No aparece en un evento rechazado.
results[].event_idstringComo en una respuesta individual. No aparece en un evento rechazado.
results[].attributionobjectComo en una respuesta individual.
results[].commissionobjectComo en una respuesta individual.
results[].waiting_for_playerbooleanComo en una respuesta individual.
results[].fieldstringPara un evento rechazado: el campo que hay que corregir.
results[].messagestringPara un evento rechazado: qué hay que corregir.
acceptedintegerCuántos eventos se guardaron.
duplicateintegerCuántos ya estaban guardados.
rejectedintegerCuá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

ColumnaObligatorioSignificado
registration_datesíCuándo se registró el jugador. Se convierte en ts.
player_idsíTu ID para el jugador.
btagsí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_codenoOpcional. El código promocional que el jugador escribió al registrarse, usado como el promo_code de un registro.
player_countrynoOpcional. 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

ColumnaObligatorioSignificado
activitydatesíEl día de actividad, determinado en la zona horaria de archivos del workspace.
player_idsíTu ID para el jugador.
currencysíLa moneda de los importes de la fila. Vacía: la moneda de informes del workspace.
depositssí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_amountsí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.
chargebacksí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_revenuenoTodas 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_revenuenoOpcional. 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.
*_bonusnoOpcional. Las columnas llamadas bonus o cuyo nombre termina en _bonus, sumadas en bonuses. Solo se muestran.
*_feesnoOpcional. Las columnas llamadas fees o cuyo nombre termina en _fees, sumadas en fees. Solo se muestran.
*_taxesnoOpcional. 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_amount y chargeback son 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 bajo chargeback_fx_missing hasta 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 con ref_txn_id igual 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."]
}
CampoTipoSignificado
idstringEl ID de RevRake de esta importación.
filenamestringEl nombre que diste, o reg.csv / sales.csv.
kindstringreg o sales.
uploaded_atstringCuándo se recibió.
rows_totalintegerFilas de datos en el archivo.
acceptedintegerEventos guardados.
duplicatesintegerEventos que ya estaban guardados.
rejectedintegerFilas o eventos rechazados. Cada uno aparece en el registro de entrada con la propia línea.
skippedintegerFilas sin nada que registrar.
problemsarray of stringLas 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ámetroTipoEjemploSignificado
bystringlinkCómo se agrupan las filas: day, link, sub1, country o affiliate. Por defecto, day. Con affiliate indicado, by=affiliate se responde como day.
daysinteger7Cuántos días, contando hoy: de 1 a 365. Por defecto, 30.
fromstring2026-09-01El 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.
tostring2026-09-30El último día del periodo, incluido. Por defecto, hoy.
affiliateinteger1001Un 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.
tagstringInfluencersEl 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.
formatstringcsvcsv 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
  }
}
CampoTipoSignificado
bystringLa agrupación que se usó.
fromstringEl primer día del informe, YYYY-MM-DD.
tostringEl último día: hoy en la zona horaria del workspace, o el to que indicaste.
currencystringLa moneda de informes del workspace. Las ganancias están en ella.
rowsarrayUna entrada por grupo.
rows[].keystringLo 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[].labelstringEl 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[].clicksintegerClics limpios.
rows[].flagged_clicksintegerClics marcados como bots, repeticiones, ráfagas o de un afiliado que no está aprobado. No están en clicks.
rows[].registrationsintegerJugadores atribuidos.
rows[].ftdsintegerPrimeros depósitos.
rows[].earnings_minorintegerComisiones generadas, en unidades menores de la moneda de informes, al tipo de cambio del día en que se generó cada una.
totalobjectLa 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ámetroTipoEjemploSignificado
statusstringapprovedhold, approved, paid o rejected. Por defecto: todos.
fromstring2026-09-01Solo las filas ocurridas en este momento o después: RFC 3339, o un día YYYY-MM-DD en UTC.
tostring2026-10-01Solo las filas ocurridas antes de este momento. Los mismos formatos.
affiliateinteger1001Un 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.
tagstringInfluencersEl 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.
qstringPLR-1001Las filas cuyo ID de jugador o nombre de afiliado contiene este texto. Hasta 100 caracteres.
sortstringwhenwhen, affiliate, player, status, deposit o commission. Por defecto, when.
dirstringascasc o desc. Por defecto, desc.
pageinteger2La página, desde 1. Por defecto, 1.
per_pageinteger50010, 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
}
CampoTipoSignificado
currencystringLa moneda de informes del workspace.
totalsobjectLa 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.
owedobjectLa suma por moneda y estado, en la moneda propia de cada comisión: lo que se debe. Sobre las mismas filas que totals.
periodbooleantrue cuando se indicó from o to, así que totals y owed cubren solo ese periodo.
commissionsarrayLas filas de esta página.
totalintegerCuántas filas cumplen los filtros.
pageintegerLa página que se respondió.
per_pageintegerLas filas por página que se usaron.

Una fila de comisión

CampoTipoSignificado
idstringEl ID de RevRake de la fila, un UUID. Nunca cambia: actualiza por él.
occurred_atstringCuándo se generó la comisión: la hora del depósito, el final del periodo de ingresos o la hora del chargeback.
typestringcpa, revshare, adjustment, reversal o tier.
amount_minorintegerLa comisión, en unidades menores de currency. Negativa para una reversión y para un ajuste a la baja.
currencystringLa moneda de la comisión: la del deal.
reporting_minorinteger | nullLa 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_minorintegerEl depósito o los ingresos netos que hay detrás de la comisión, en unidades menores de gross_currency.
gross_currencystringLa moneda del depósito o de los ingresos.
rate_snapshotintegerLas condiciones que se aplicaron: el importe del CPA en unidades menores, o el RevShare en centésimas de punto porcentual (2500 es 25%).
statusstringhold, approved, paid o rejected. Consulta los estados de una comisión.
status_reasonstringPor 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_notestringLa nota del operador sobre una comisión retirada, tal como la lee el afiliado. Vacía en los demás casos.
hold_untilstring | nullCuándo termina el periodo de retención. null para una reversión.
affiliate_numberintegerEl número del afiliado.
affiliate_namestringEl nombre del afiliado.
player_refstringTu ID para el jugador.
deal_namestringEl deal.
deal_versionintegerLa 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

CampoTipoSignificado
numberintegerEl número de afiliado, la primera parte de la btag; en un enlace, el número de enlace, su segunda parte.
namestringEl nombre del afiliado.
emailstringLa dirección de correo del afiliado.
statusstringpending, approved, rejected o suspended.
dealstringEl nombre del acuerdo del afiliado; vacío antes de la aprobación.
created_atstringCuándo se creó, RFC 3339.
decided_atstring | nullCuándo se aprobó o rechazó la solicitud, o se suspendió la cuenta; null antes.

Cada enlace

CampoTipoSignificado
affiliate_numberintegerEl número del afiliado al que pertenece el enlace.
numberintegerEl número de afiliado, la primera parte de la btag; en un enlace, el número de enlace, su segunda parte.
codestringEl código en la dirección del enlace.
urlstringEl enlace de seguimiento, en el dominio de seguimiento verificado de la marca o en el compartido.
btagstring<affiliate_number>_<number>.
campaignstringLa etiqueta de campaña del enlace.
landing_pathstring | nullLa página de tu sitio a la que lleva el enlace; null para la de la marca por defecto.
subsstring[5]sub1 a sub5 fijados en el enlace; cadenas vacías si no se fijaron.
promo_codestring | nullEl código promocional, en un enlace creado para uno; si no, null.
vanity_urlstring | nullLa dirección corta /go/<nombre>, si el afiliado puso una; si no, null.
created_atstringCuándo se creó, RFC 3339.

Cada acuerdo

CampoTipoSignificado
namestringEl nombre del afiliado.
currencystringLa moneda en la que el acuerdo gana y paga.
listedbooleanSi la página pública de alta muestra el acuerdo.
defaultbooleanSi los nuevos afiliados reciben este acuerdo.
versionintegerLa versión del acuerdo vigente ahora.
effective_atstringCuándo entró en vigor esa versión, RFC 3339.
termsobjectLos 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.

CampoTipoSignificado
click_idstringEl ID de clic, como se pasa a tu página de destino y en los postbacks.
clicked_atstringCuándo se hizo el clic.
affiliate_numberintegerEl número de afiliado.
link_numberintegerEl número de enlace.
btagstring<affiliate_number>_<link_number>.
countrystringCódigo de país de dos letras; vacío si se desconoce.
devicestringdesktop, mobile, tablet o bot, según el navegador.
osstringEl sistema operativo, según el navegador.
browserstringEl navegador.
sub1 … sub5stringsub1 a sub5: los sub-ID del clic.
utm_source … utm_contentstringutm_source a utm_content: las etiquetas UTM del clic.
gclid, fbclid, ttclid, msclkid, wbraidstringgclid, fbclid, ttclid, msclkid, wbraid: ID de clic de plataformas publicitarias en la URL de destino.
refererstringLa página de la que venía el visitante.
flagsstringPor 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.

CampoTipoSignificado
player_idstringTu ID de jugador.
registered_atstringLa hora de registro que enviaste.
arrived_atstringCuándo lo registró RevRake.
countrystringCódigo de país de dos letras; vacío si se desconoce.
attributedbooleantrue cuando se acredita a un afiliado.
attribution_reasonstringPor qué, como en la respuesta del postback: click, btag_only, promo_code, no_tag…
affiliate_numberintegerEl número de afiliado.
link_numberintegerEl número de enlace.
btagstring<affiliate_number>_<link_number>.
click_idstringEl clic que acreditó al jugador; vacío si no hay.
policy_versionintegerLa 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.

CampoTipoSignificado
event_idstringEl ID del evento en RevRake.
typestringregistration, deposit, chargeback, daily_sales o ngr; para una comisión, cpa, revshare, tier, adjustment o reversal.
external_idstringLa clave del evento: el ID de jugador, el ID de transacción, o jugador y periodo.
player_idstringTu ID de jugador.
occurred_atstringCuándo ocurrió, según lo enviaste; en una comisión, cuándo se ganó.
received_atstringCuándo aceptó RevRake el evento.
sourcestrings2s (la API) o file (un archivo diario).
payloadJSONEl evento tal como lo leyó RevRake, en JSON.
resultJSONLo que respondió RevRake, en JSON.

commissions.csv.gz: filas de comisión escritas ese día.

CampoTipoSignificado
commission_idstringEl ID de la fila, el id de GET /v1/commissions.
created_atstringCuándo se escribió la fila.
occurred_atstringCuándo ocurrió, según lo enviaste; en una comisión, cuándo se ganó.
typestringregistration, deposit, chargeback, daily_sales o ngr; para una comisión, cpa, revshare, tier, adjustment o reversal.
statusstringhold, approved, paid o rejected, al exportar.
status_reasonstringPor qué está retenida o rechazada; vacío si no.
affiliate_numberintegerEl número de afiliado.
player_idstringTu ID de jugador.
dealstringEl nombre del acuerdo.
deal_versionintegerLa versión del acuerdo que la valoró.
grossdecimalEl depósito o ingreso neto que hay detrás.
gross_currencystringSu moneda.
amountdecimalLa comisión.
currencystringLa moneda del acuerdo.
reporting_amountdecimalLa comisión en la moneda de informes; vacío mientras falte el tipo de cambio.
reporting_currencystringLa moneda de informes del espacio de trabajo.
reverses_idstringEn una reversión, la fila que revierte.
payout_item_idstringLa 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.

EventoClaveLa misma clave otra vez
registrationplayer_idLa primera respuesta. La atribución no se decide de nuevo, aunque las etiquetas sean distintas.
deposittxn_idLa 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.
chargebacktxn_idLa primera respuesta. Un segundo chargeback del mismo depósito con un txn_id nuevo se rechaza.
ngrplayer_id y periodEl 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.
CSVFila REG: player_idComo un registration.
CSVResumen monetario SALES: marca, jugador y día de actividadLos 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.
CSVFila SALES, ingresos: el jugador y el díaComo 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

EstadoerrorEndpointCuándoQué 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.
400bad_requestPOST /v1/postbackEl 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.
401unknown_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.
401bad_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.
401bad_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.
403affiliate_inactiveGET /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.
404unknown_affiliateGET /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.
413too_largePOST /v1/*El cuerpo supera el límite del endpoint.Corrige la petición. La misma petición otra vez recibe la misma respuesta.
422invalidPOST /v1/*, GET /v1/reportsEl 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.
429rate_limitedGET /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.
500internal/v1/*Algo falló en nuestro lado.Envíala de nuevo.
  • Un lote sin eventos o con más de 1.000 se rechaza con 422 invalid y sin field.
  • Un 401 por un timestamp o una firma incorrectos con una clave de operador aparece en el registro de entrada como bad_signature. Una petición con una clave desconocida, o con una clave de afiliado, no se registra.
  • Una lectura responde 422 solo para un periodo de informe que no se puede usar (from y to). Cualquier otro parámetro con un valor no permitido se ignora y se usa su valor por defecto. Comprueba by, page y per_page en la respuesta.
  • Reintenta ante un tiempo de espera agotado, ante un 429 y ante un 500. No reintentes ningún otro 4xx sin cambios.

Qué indica field en un 422

fieldCuándo
bodyEl cuerpo no es JSON válido, o lleva un campo desconocido o mal formado (por ejemplo, un ts que no es RFC 3339).
typetype falta o no es uno de los cuatro.
player_idFalta o supera los 100 caracteres; un marcador que no se rellenó; para ngr, un jugador que no está registrado.
click_idUn marcador que no se rellenó. El mensaje empieza por misconfigured_macro.
btagUn marcador que no se rellenó. El mensaje empieza por misconfigured_macro.
promo_codeUn marcador que no se rellenó. El mensaje empieza por misconfigured_macro.
txn_idFalta o supera los 200 caracteres.
ref_txn_idFalta; no se conoce ningún depósito con ese txn_id; o el depósito ya se había devuelto.
amountNo es un decimal que la moneda admita; para un depósito, no es mayor que cero.
currencyNo es un código de tres letras.
ggrPara ngr: no es un importe decimal en la moneda.
bonusesPara ngr: no es un importe decimal en la moneda.
feesPara ngr: no es un importe decimal en la moneda.
taxesPara ngr: no es un importe decimal en la moneda.
tsEstá más de un día en el futuro.
periodNo es un día ni un mes, o está en el futuro.
fromPara 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.
filePara /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.
chargebackEn 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.

statusSignificado
acceptedGuardado ahora. La respuesta dice lo que hizo.
duplicateYa 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.
rejectedSolo 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.effectSignificado
createdSe creó una fila de comisión: un CPA, un RevShare o un ajuste por una cifra corregida.
cancelledUn chargeback rechazó una comisión que seguía en retención.
reversedUn chargeback añadió una fila de reversión contra una comisión ya aprobada o pagada.
noneNo se creó ninguna fila. reason dice por qué.

Tipos de comisión

typeSignificado
cpaUna 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.
revshareUn porcentaje de los ingresos netos de juego (NGR) de un jugador en un periodo. Negativa en un periodo en que el jugador ganó.
adjustmentLa diferencia que produce una cifra de ingresos corregida.
reversalEl 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.
tierUn 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í:

statusSignificado
holdEn espera: de que termine el periodo de retención del deal, o de lo que indica status_reason.
approvedEl 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.
paidEl elemento de pago que contiene la fila se marcó como pagado.
rejectedNo 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_reasonCon el estadoSignificado
fx_rate_missingholdFalta 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_missinghold, approved, paidUn 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_missingholdUn 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_referralholdUna alerta de fraude abierta: el jugador parece ser el propio afiliado. Espera a que el operador libere o confirme la alerta.
click_velocityholdUna 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_conversionholdUna alerta de fraude abierta: se comunicó un segundo "primer" depósito del jugador. Espera al operador.
paid_trafficholdUna alerta de fraude abierta: el clic que trajo al jugador llevaba el click ID de una red publicitaria. Espera al operador.
disposable_emailholdUna alerta de fraude abierta: el jugador se registró con un dominio de correo desechable. Espera al operador.
geo_mismatchholdUna alerta de fraude abierta: el jugador se registró en un país distinto del país del clic. Espera al operador.
fast_registrationholdUna alerta de fraude abierta: el jugador se registró dentro del número de segundos fijado tras el clic. Espera al operador.
affiliate_pendingholdEl 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_rejectedholdLa solicitud del afiliado fue rechazada.
affiliate_suspendedholdEl afiliado estaba suspendido cuando se generó la comisión. Cuando el afiliado se reactiva, la fila solo espera su periodo de retención normal.
chargebackrejected, approvedEn 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_thresholdrejectedAl estar disponibles los tipos, el primer depósito válido o los depósitos acumulados netos quedaron por debajo del mínimo del deal.
fraudrejected, approvedEl operador confirmó la alerta de fraude que retenía la fila, o retiró la comisión por fraude.
base_rejectedrejectedUn complemento de tramo cuya comisión se rechazó mientras ambos estaban en retención.
self_excludedrejected, approvedEl 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_accountrejected, approvedEl operador retiró la comisión: el jugador tiene otra cuenta.
bonus_abuserejected, approvedEl operador retiró la comisión por abuso de bonos.
terms_breachrejected, approvedEl operador retiró la comisión por incumplir las condiciones del programa.
test_accountrejected, approvedEl operador retiró la comisión: el jugador era una cuenta de prueba.
otherrejected, approvedEl 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 loteSignificado
draftEl mes se cerró. El operador revisa los elementos.
approvedBloqueado. 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.
paidTodos los elementos por pagar se marcaron como pagados.
Estado del elementoSignificado
payableSe paga en este lote.
carriedPor debajo del pago mínimo, o no mayor que cero: pasa al lote siguiente.
heldEl 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_detailsEl afiliado no ha indicado dónde cobrar. No está en el archivo bancario.
written_offEl 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.
paidMarcado 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_date o player_id, o no puede leerse.
  • SALES: falta activitydate o player_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

EstadoSignificado
acceptedGuardado, con lo que hizo.
duplicateYa estaba guardado; se devolvió la primera respuesta.
rejectedRechazado; el detalle indica qué campo y por qué.
bad_signatureEl timestamp o la firma eran incorrectos.
testUn evento de prueba desde la página Integración. Recorre el camino real y no guarda nada.

Estados de una entrega de postback

EstadoSignificado
queuedEn espera de su primer intento, o del siguiente reintento. El portal la muestra como en espera o reintentando.
deliveredEl tracker respondió con un estado 2xx.
deadTodos los reintentos fallaron. El portal la muestra como abandonada; el afiliado puede reenviarla a mano.

Estados de un afiliado

EstadoSignificado
pendingHa enviado su solicitud; el operador aún no ha decidido.
approvedActivo en el programa.
rejectedLa solicitud fue rechazada.
suspendedDetenido 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ámetroSignificado
sub1 … sub5Los 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_contentSe 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 wbraidClick 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ámetroSignificado
cidEl 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_contentLas 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ñade cid.
  • Un clic en el enlace de un afiliado que no está aprobado se guarda y se marca, y llega sin cid ni btag: 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 404 y 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
registrationleadUn jugador fue atribuido al afiliado.
ftdsaleUn primer depósito generó una comisión CPA para el afiliado.
commission_approvedapprovedUna comisión fue aprobada al terminar su periodo de retención.
commission_rejectedrejectedUna comisión en retención fue rechazada tras una revisión.
commission_paidpaidUna comisión fue pagada en un lote de pago.
chargebackrejectedUn 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.

EventoKeitaroBinomRedTrackBeMob
registrationleadleadothernew
ftdsalesalependingapproved
commission_approvedsalesaleapprovedapproved
commission_rejectedrejectedrejectdeclineddeclined
commission_paidsalesaleapprovedapproved
chargebackrejectedrejectdeclinedchargeback

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.

MacroSignificado
{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.

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

Un ejemplo

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

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 GET con el user agent RevRake-Postback/1. Sigue como máximo tres redirecciones y abandona a los 5 segundos. Los certificados se verifican.
  • Una respuesta 2xx es 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:// o https:// completa de como máximo 2.000 caracteres.

Por qué falló un intento

timeoutSin respuesta en 5 segundos.
dnsNo se encontró el nombre de host.
refusedLa conexión fue rechazada.
blocked_addressLa dirección es privada o local.
tlsUn problema de certificado.
too_many_redirectsMás de tres redirecciones.
http_errorEl tracker respondió con un estado distinto de 2xx.
otherLa 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.

EventoCampos de dataCuándo
affiliate.appliedaffiliate_number affiliate_name email promotes_on trafficAlguien 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.approvedaffiliate_number affiliate_name email reason decided_by invitedAprobaste 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.rejectedaffiliate_number affiliate_name email reason decided_byRechazaste una solicitud.
affiliate.suspendedaffiliate_number affiliate_name email reason decided_bySuspendiste a un afiliado aprobado.
player.registeredplayer_id occurred_at attributed affiliate_number affiliate_name attribution_reasonSe registró un jugador, por un evento firmado o una fila de un archivo REG, atribuido o no. Una vez por jugador.
commission.createdplayer_id occurred_at attributed affiliate_number affiliate_name commission_id commission_type amount amount_currency commission commission_currencySe 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.approvedplayer_id occurred_at attributed affiliate_number affiliate_name commission_id commission_type amount amount_currency commission commission_currencyUna 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.rejectedplayer_id occurred_at attributed affiliate_number affiliate_name commission_id commission_type amount amount_currency commission commission_currencyUna 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.paidplayer_id occurred_at attributed affiliate_number affiliate_name commission_id commission_type amount amount_currency commission commission_currencySe 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.reversedplayer_id occurred_at attributed affiliate_number affiliate_name commission_id commission_type amount amount_currency commission commission_currencyUn 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_closedbatch_id period reference items payable_by_currency closed_by_handUn mes se cerró en un lote de pago en borrador, por una persona o por el cierre programado.
payout.batch_approvedbatch_idUn lote de pago en borrador se aprobó y se bloqueó.
payout.paidbatch_id item_id affiliate_number affiliate_name currency amountUna 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_alertbrand kind since quiet_hours clicks_at_leastLos 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_clearedbrand raised_at cleared_atLlegó el primer registro tras una alerta de salud del seguimiento, y la alerta se borra.
flag.raisedkind detail affiliate_number affiliate_nameSe 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_offdestination failures affiliate_number affiliate_nameEl destino de postbacks de un afiliado se desactivó tras los envíos fallidos seguidos que permite la escala de reintentos.
file.latesource dateUn archivo diario descargado con horario no había llegado tres horas después de su hora. Una vez por archivo y día.
webhook.testmessageSolo 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:

CampoTipoSignificado
idstringEl ID del evento, un UUID. El mismo para cada endpoint que recibe el evento y en cada reintento: úsalo para descartar repeticiones.
typestringEl evento, como en la tabla de arriba.
created_atstringCuándo se puso el evento en cola, en formato RFC 3339 en UTC, con fracciones de segundo.
dataobjectLos 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

CabeceraValor
RevRake-EventEl evento, igual que type en el cuerpo.
RevRake-DeliveryEl ID de esta entrega a este endpoint, un UUID. El mismo en cada reintento y cuando una entrega se reenvía a mano.
RevRake-TimestampCuándo se envió este intento, en segundos Unix. Nuevo en cada intento.
RevRake-SignatureEl 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

CampoTipoSignificado
affiliate_numberintegerEl número del afiliado.
affiliate_namestringEl nombre del afiliado.
emailstringLa dirección de email con la que el afiliado inicia sesión.
promotes_onstringLo que el solicitante escribió sobre dónde promociona.
trafficarray of stringLas fuentes de tráfico que marcó el solicitante: seo, telegram, paid_social, youtube, email u offline.
reasonstringEl motivo que se dio al afiliado. Siempre presente en un rechazo o una suspensión; puede estar vacío en una aprobación.
decided_bystringLa dirección de email del miembro del equipo que decidió, o que añadió al afiliado.
invitedbooleantrue cuando añadiste e invitaste al afiliado tú mismo, sin solicitud. Se omite en los demás casos.
player_idstringTu ID del jugador.
occurred_atstringCuá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.
attributedbooleanSi el jugador pertenece a un afiliado. Siempre true en una comisión.
attribution_reasonstringPor qué el jugador se atribuyó o no: uno de los motivos de atribución.
commission_idstringEl 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_typestringEl 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ó.
amountstringEn 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_currencystringEl código ISO de la moneda de amount.
commissionstringLa 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_currencystringEl código ISO de la moneda de la comisión: la del deal.
batch_idstringEl ID de RevRake del lote de pago, un UUID.
periodstringEl mes que cierra el lote, YYYY-MM.
referencestringLa referencia del lote, la del archivo bancario, como po_2026-09_demo.
itemsintegerCuántas partidas tiene el lote, sea cual sea su estado.
payable_by_currencyobjectPor 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_handbooleantrue cuando una persona cerró el mes, false en el cierre programado.
item_idstringEl ID de RevRake de la partida de pago, un UUID.
currencystringLa moneda de la partida.
messagestringUna frase que dice que la prueba llegó.

Verificar una petición

  1. Lee el cuerpo en bruto como bytes, antes de interpretar el JSON.
  2. Comprueba que RevRake-Timestamp está 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.
  3. 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-Signature en tiempo constante.
  4. Solo entonces interpreta el cuerpo. Si ya procesaste su id, responde 2xx y no hagas nada.
  5. Responde con cualquier 2xx en 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 id del cuerpo.
  • El orden no está garantizado: un reintento puede llegar después de un evento posterior. Ordena los eventos por created_at u occurred_at, no por el momento en que llegan.
  • Una respuesta 2xx en 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 3xx es un fallo con el error redirect, 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 un 3xx. 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 4xx a 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 id de 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_id para unir los eventos sobre una misma comisión, y concilia con GET /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