GetAff
Documentation API

Webhooks

GetAff envoie un POST à votre point de terminaison HTTPS quand quelque chose se passe sur votre compte. Chaque livraison est signée ; votre code la vérifie avec un HMAC et n’a plus jamais à interroger.

Événements

Quatre événements, chacun un corps JSON { id, type, created_at, data }. data porte des identifiants et des comptes, jamais une adresse e-mail ni un message : lisez l’objet complet par l’API quand il vous le faut. id commence par msg_ et reste identique à chaque nouvel essai du même événement.

search.finished
Une recherche de partenaires d’un de vos sites s’est terminée — depuis l’écran, la conversation, l’API ou MCP. data : siteId, searchId, results, onTopic, sources (par source : id, status, count), startedAt, finishedAt.
prospect.replied
Un prospect a répondu à une séquence de prospection (détecté dans votre boîte par le tick de dix minutes). data : prospectId, sequenceId, mailboxEmail, repliedAt. Un stade déplacé à la main ne le déclenche pas.
application.received
Un créateur a candidaté à l’un de vos programmes. data : applicationId, programId, programSlug, programName, affiliateName, affiliateSlug, score, autoApproved.
partner.approved
Un partenariat sur l’un de vos programmes est devenu approuvé — votre décision, votre règle d’approbation automatique ou une invitation acceptée. data : partnershipId, programId, programSlug, programName, affiliateName, origin.

Enregistrer un point de terminaison

Depuis Réglages du compte › API › Webhooks, ou par l’API : POST /v1/webhooks avec la portée write. La réponse porte le secret de signature (whsec_…) UNE SEULE FOIS ; conservez-le côté serveur. Jusqu’à dix points de terminaison par compte, chacun avec ses événements et son secret. L’URL doit être en https:// sur un hôte public : une adresse privée, de bouclage ou locale au lien, localhost ou un nom en .internal est refusé à l’enregistrement et de nouveau à chaque livraison.

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 · Clés d’API

Ce qui arrive

Un POST avec trois en-têtes — webhook-id (l’identifiant du message), webhook-timestamp (secondes Unix) et webhook-signature (v1, suivi du HMAC-SHA256 en base64 de {id}.{timestamp}.{body}, avec pour clé les octets derrière whsec_) — et le corps JSON. Répondez un 2xx quelconque sous 10 secondes ; le corps de votre réponse est ignoré. Tout autre statut, un délai dépassé ou une erreur de connexion compte comme un essai échoué.

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"}}

Vérifier une livraison

C’est le schéma Standard Webhooks : toute bibliothèque de ce nom vérifie une livraison GetAff avec le seul secret. Sans bibliothèque, quatre étapes :

  1. Lisez le corps BRUT de la requête tel que reçu — pas un objet resérialisé : une clé réordonnée change les octets et la signature ne correspond plus.
  2. Refusez un webhook-timestamp éloigné de plus de cinq minutes de votre horloge : c’est la fenêtre anti-rejeu.
  3. Calculez le HMAC-SHA256 de {webhook-id}.{webhook-timestamp}.{body} avec le secret décodé du base64, encodez-le en base64, et comparez en temps constant avec chaque valeur v1,… séparée par une espace dans webhook-signature. Une correspondance suffit (deux valeurs voyagent pendant une rotation de secret).
  4. Traitez id comme une clé d’idempotence : un nouvel essai porte le même, et un récepteur qui mémorise les identifiants vus traite chaque événement une fois.
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

Nouveaux essais et lettre morte

Un essai échoué est repris après 1 minute, puis 5 minutes, 30 minutes, 2 heures et 12 heures — six essais sur environ quinze heures — toujours avec le même webhook-id et le même corps. Après le sixième, la livraison passe en lettre morte : conservée et visible (Réglages du compte, ou GET /v1/webhooks/{id}), jamais reprise. Un hôte refusé ou un 404/410 de votre côté est définitif immédiatement. Les livraisons restent visibles trente jours. L’action « Tester » (ou POST /v1/webhooks/{id}/test) envoie tout de suite un événement webhook.test signé et montre la réponse de votre point de terminaison.

Faire tourner un secret

Réglages du compte › Rotation, ou POST /v1/webhooks/{id}/rotate. Pendant les 24 heures suivantes, chaque livraison porte deux signatures dans webhook-signature — nouvelle et ancienne, séparées par une espace — pour qu’un récepteur mis à jour plus tard dans la journée vérifie encore. Ensuite, l’ancien secret disparaît.

Ce que vous pouvez attendre de nous, et nous de vous
  • Un secret est montré une fois et scellé au repos ; personne chez GetAff ne peut le relire. Perdu ? Faites une rotation.
  • Les livraisons partent des serveurs de GetAff, jamais d’un navigateur, et ne portent jamais votre clé d’API.
  • Un point de terminaison peut être mis en pause (disabled) sans perdre son secret ; en pause, il ne reçoit rien et ses événements ne sont pas mis en file.
  • Vérifiez chaque livraison avant d’agir. Une requête non signée ou mal signée vers votre point de terminaison ne vient pas de GetAff.
  • Répondez 2xx vite et travaillez ensuite : un traitement lent est un délai dépassé, et un délai dépassé est un nouvel essai que vous recevrez deux fois.

Référence