GetAff
Documentación de la API

Webhooks

GetAff envía un POST a su punto de conexión HTTPS cuando ocurre algo en su cuenta. Cada entrega va firmada; su código la verifica con un HMAC y nunca tiene que consultar.

Eventos

Cuatro eventos, cada uno un cuerpo JSON { id, type, created_at, data }. data lleva identificadores y recuentos, nunca una dirección de correo ni un mensaje: lea el objeto completo a través de la API cuando lo necesite. id empieza por msg_ y es el mismo en cada reintento del mismo evento.

search.finished
Una búsqueda de socios de uno de sus sitios terminó — desde la pantalla, el chat, la API o MCP. data: siteId, searchId, results, onTopic, sources (por fuente: id, status, count), startedAt, finishedAt.
prospect.replied
Un prospecto respondió a una secuencia de contacto (detectado en su buzón por el ciclo de diez minutos). data: prospectId, sequenceId, mailboxEmail, repliedAt. Una etapa movida a mano no lo dispara.
application.received
Un creador solicitó unirse a uno de sus programas. data: applicationId, programId, programSlug, programName, affiliateName, affiliateSlug, score, autoApproved.
partner.approved
Una colaboración en uno de sus programas pasó a aprobada — su decisión, su regla de aprobación automática o una invitación aceptada. data: partnershipId, programId, programSlug, programName, affiliateName, origin.

Registrar un punto de conexión

Desde Ajustes de la cuenta › API › Webhooks, o con la API: POST /v1/webhooks con el ámbito write. La respuesta lleva el secreto de firma (whsec_…) UNA SOLA VEZ; guárdelo en el servidor. Hasta diez puntos de conexión por cuenta, cada uno con sus eventos y su secreto. La URL debe ser https:// en un host público: una dirección privada, de bucle local o de enlace local, localhost o un nombre .internal se rechaza al registrar y de nuevo en cada entrega.

curl
curl -s https://www.getaff.org/api/v1/webhooks \
  -H "Authorization: Bearer $GETAFF_API_KEY" \
  -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"url":"https://hooks.example.com/getaff",
       "events":["prospect.replied","application.received"],
       "description":"CRM sync"}'

# → { "data": { "endpoint": { "id": "whe_…", … }, "secret": "whsec_…" } }
#   The secret is in THIS response only. Store it server-side.
Webhooks · Claves de API

Lo que llega

Un POST con tres cabeceras — webhook-id (el identificador del mensaje), webhook-timestamp (segundos Unix) y webhook-signature (v1, seguido del HMAC-SHA256 en base64 de {id}.{timestamp}.{body}, con los bytes detrás de whsec_ como clave) — y el cuerpo JSON. Responda cualquier 2xx en menos de 10 segundos; el cuerpo de su respuesta se ignora. Cualquier otro estado, un tiempo agotado o un error de conexión cuenta como intento fallido.

HTTP
POST /getaff HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
webhook-id: msg_3f1c2b9a7d6e5f4c3b2a19087f6e5d4c
webhook-timestamp: 1762337524
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pfCnzRIJnS2bzE=

{"id":"msg_3f1c2b9a7d6e5f4c3b2a19087f6e5d4c","type":"prospect.replied",
 "created_at":"2026-11-05T10:12:00.000Z",
 "data":{"prospectId":"…","sequenceId":"…","mailboxEmail":"[email protected]",
         "repliedAt":"2026-11-05T10:11:58.000Z"}}

Verificar una entrega

Es el esquema Standard Webhooks: cualquier biblioteca con ese nombre verifica una entrega de GetAff solo con el secreto. Sin biblioteca, cuatro pasos:

  1. Lea el cuerpo CRUDO de la petición tal como llegó — no un objeto reserializado: una clave reordenada cambia los bytes y la firma deja de coincidir.
  2. Rechace un webhook-timestamp a más de cinco minutos de su reloj: es la ventana contra repeticiones.
  3. Calcule el HMAC-SHA256 de {webhook-id}.{webhook-timestamp}.{body} con el secreto decodificado de base64, codifíquelo en base64 y compárelo en tiempo constante con cada valor v1,… separado por espacio de webhook-signature. Basta una coincidencia (durante una rotación de secreto viajan dos valores).
  4. Trate id como clave de idempotencia: un reintento lleva el mismo, y un receptor que guarda los identificadores vistos procesa cada evento una vez.
Node.js — no dependency
import { createHmac, timingSafeEqual } from 'node:crypto';

// Express: app.post("/getaff", express.raw({ type: "application/json" }), handler)
// The body MUST be the raw bytes — never a parsed-then-stringified object.
export function verifyGetAffWebhook(headers, rawBody, secret, toleranceSec = 300) {
  const id = headers['webhook-id'];
  const ts = headers['webhook-timestamp'];
  const sig = headers['webhook-signature'];
  if (!id || !ts || !sig) return false;
  if (Math.abs(Date.now() / 1000 - Number(ts)) > toleranceSec) return false; // replay window

  const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
  const expected = createHmac('sha256', key).update(`${id}.${ts}.${rawBody}`).digest();
  return sig.split(' ').some((part) => {
    const [version, value] = part.split(',');
    if (version !== 'v1' || !value) return false;
    const given = Buffer.from(value, 'base64');
    return given.length === expected.length && timingSafeEqual(given, expected);
  });
}

// Then: const event = JSON.parse(rawBody); dedupe on event.id; answer 200 at once.
Node.js — standardwebhooks
import { Webhook } from 'standardwebhooks';

const wh = new Webhook(process.env.GETAFF_WEBHOOK_SECRET.replace(/^whsec_/, ''));
const event = wh.verify(rawBody, headers); // throws when the signature or timestamp is wrong

Reintentos y la cola de fallidos

Un intento fallido se repite tras 1 minuto, luego 5 minutos, 30 minutos, 2 horas y 12 horas — seis intentos en unas quince horas — siempre con el mismo webhook-id y el mismo cuerpo. Tras el sexto, la entrega pasa a fallida definitiva: conservada y visible (Ajustes de la cuenta, o GET /v1/webhooks/{id}), nunca reintentada. Un host rechazado o un 404/410 de su parte es definitivo de inmediato. Las entregas siguen visibles treinta días. La acción «Probar» (o POST /v1/webhooks/{id}/test) envía al momento un evento webhook.test firmado y muestra la respuesta de su punto de conexión.

Rotar un secreto

Ajustes de la cuenta › Rotar, o POST /v1/webhooks/{id}/rotate. Durante las 24 horas siguientes cada entrega lleva dos firmas en webhook-signature — nueva y antigua, separadas por espacio — para que un receptor actualizado más tarde ese día siga verificando. Después, el secreto antiguo desaparece.

Qué esperar de nosotros, y nosotros de usted
  • Un secreto se muestra una vez y se sella en reposo; nadie en GetAff puede volver a leerlo. ¿Lo perdió? Rótelo.
  • Las entregas salen de los servidores de GetAff, nunca de un navegador, y nunca llevan su clave de API.
  • Un punto de conexión puede pausarse (disabled) sin perder su secreto; en pausa no recibe nada y sus eventos no se encolan.
  • Verifique cada entrega antes de actuar. Una petición sin firma o mal firmada a su punto de conexión no vino de GetAff.
  • Responda 2xx rápido y trabaje después: un manejador lento es un tiempo agotado, y un tiempo agotado es un reintento que recibirá dos veces.

Referencia