Connecter GetAff à votre CRM ou à Slack avec des webhooks signés
La réponse d’un créateur mérite d’être traitée dans l’heure, pas à la prochaine revue hebdomadaire. Les webhooks permettent à GetAff de prévenir vos systèmes dès qu’il se passe quelque chose — une recherche terminée, un prospect qui répond, un créateur qui candidate, un partenariat approuvé. Votre CRM reçoit la mise à jour et votre équipe le message Slack, sans que personne n’ait à interroger quoi que ce soit.
Cet article présente les quatre événements, l’enregistrement d’un point de terminaison, la vérification de la signature en Node et en Python, le calendrier des nouveaux essais et deux intégrations concrètes.
Mis à jour le
Les quatre événements
Chaque livraison est un corps JSON { id, type, created_at, data }. data porte des identifiants et des comptes, jamais une adresse e-mail ni un message : quand il vous faut l’objet complet, lisez-le par l’API à partir de l’identifiant.
search.finished— une recherche de partenaires sur l’un de vos sites s’est terminée, qu’elle ait été lancée depuis l’écran, la conversation, l’API ou MCP.data:siteId,searchId,results,onTopic,sourcespar source,startedAt,finishedAt.prospect.replied— un prospect a répondu à une séquence de prospection, réponse détectée 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.datacontient notammentapplicationId,programSlug,affiliateName,scoreetautoApproved.partner.approved— un partenariat est devenu approuvé : par votre décision, par votre règle d’approbation automatique ou par une invitation acceptée.datacontient notammentpartnershipId,programSlug,affiliateNameetorigin.
Enregistrer un point de terminaison
Depuis Réglages du compte › API › Webhooks, ou avec une clé dotée de la portée write :
curl -s -X POST https://www.getaff.org/api/v1/webhooks \
-H "Authorization: Bearer $GETAFF_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://hooks.example.com/getaff",
"events": ["prospect.replied", "application.received"],
"description": "CRM sync"}'La réponse contient le secret de signature (whsec_…) une seule fois : conservez-le côté serveur, avec vos autres secrets. Un compte peut avoir jusqu’à dix points de terminaison, chacun avec ses propres événements et son propre secret. L’URL doit être en https:// sur un hôte public ; les adresses privées, de bouclage et locales au lien sont refusées à l’enregistrement, puis de nouveau à chaque livraison.
Vérifier chaque livraison
Les livraisons suivent la spécification Standard Webhooks. Trois en-têtes accompagnent le corps : webhook-id, 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_. Toute bibliothèque Standard Webhooks la vérifie avec le seul secret ; sans bibliothèque, une douzaine de lignes suffisent.
- Lisez le corps BRUT tel que reçu. Un objet resérialisé change les octets, et la signature ne correspond plus.
- Refusez un horodatage éloigné de plus de cinq minutes de votre horloge : c’est la fenêtre anti-rejeu.
- Calculez le HMAC et comparez-le en temps constant avec chaque valeur
v1,…séparée par une espace. Une correspondance suffit — deux valeurs voyagent pendant une rotation de secret. - Mémorisez le
webhook-idtraité : un nouvel essai porte le même.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(rawBody, headers, secret) {
const id = headers['webhook-id'];
const ts = Number(headers['webhook-timestamp']);
const sigs = (headers['webhook-signature'] ?? '').split(' ');
if (!id || !ts || Math.abs(Date.now() / 1000 - ts) > 300) return false;
const key = Buffer.from(secret.slice('whsec_'.length), 'base64');
const expected = Buffer.from(
'v1,' + createHmac('sha256', key).update(`${id}.${ts}.${rawBody}`).digest('base64'),
);
return sigs.some((s) => {
const got = Buffer.from(s);
return got.length === expected.length && timingSafeEqual(got, expected);
});
}import base64, hashlib, hmac, time
def verify(raw_body: bytes, headers, secret: str) -> bool:
msg_id = headers.get("webhook-id", "")
ts = headers.get("webhook-timestamp", "")
if not msg_id or not ts.isdigit() or abs(time.time() - int(ts)) > 300:
return False
key = base64.b64decode(secret.removeprefix("whsec_"))
signed = f"{msg_id}.{ts}.".encode() + raw_body
expected = "v1," + base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
return any(hmac.compare_digest(s, expected)
for s in headers.get("webhook-signature", "").split(" "))Une requête non signée ou mal signée vers votre point de terminaison ne vient pas de GetAff. Les livraisons ne portent jamais votre clé d’API.
Répondre vite, prévoir les nouveaux essais
Répondez un 2xx quelconque sous dix secondes ; le corps de votre réponse est ignoré. Tout le reste — un autre statut, un délai dépassé, une erreur de connexion — compte comme un essai échoué, repris après 1 minute, 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 dans Réglages du compte ou via GET /v1/webhooks/{id}, jamais reprise. Un 404 ou un 410 de votre côté est définitif immédiatement.
- Accusez réception d’abord, travaillez ensuite : placez l’événement dans une file et renvoyez
200. - Dédoublonnez sur
webhook-id, pour qu’un nouvel essai consécutif à une réponse lente ne soit traité qu’une fois. - Utilisez l’action « Tester », ou
POST /v1/webhooks/{id}/test, pour envoyer un événementwebhook.testsigné et voir la réponse de votre point de terminaison.
Exemple : synchroniser votre CRM
Abonnez un point de terminaison à prospect.replied et partner.approved. Sur une réponse, récupérez le prospect avec GET /v1/prospects/{prospectId} (statut, canal de contact, provenance), puis créez ou mettez à jour le contact dans votre CRM et ouvrez une tâche pour le responsable du compte. Sur une approbation, passez l’affaire au stade « partenaire » et enregistrez le partnershipId, pour que la finance puisse ensuite y rapprocher les commissions.
Comme data contient des identifiants et non des données personnelles, l’événement lui-même peut être journalisé sans risque ; le détail ne transite que par votre appel authentifié à l’API.
Exemple : un message Slack pour chaque candidature
Les webhooks entrants de Slack acceptent un simple POST en JSON. Après avoir vérifié la signature, transmettez un court résumé de application.received :
if (event.type === 'application.received') {
const d = event.data;
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `New application to ${d.programName} from ${d.affiliateName} (score ${d.score})`,
}),
});
}search.finished se prête aussi bien à un récapitulatif : publiez le nombre de résultats dans le sujet et un lien vers la page « Trouver » du site, pour que l’équipe examine une nouvelle recherche le jour même.
Un point de terminaison par usage
Chaque point de terminaison a ses propres événements et son propre secret : séparez-les par usage plutôt que de tout envoyer à une seule URL. Une synchronisation CRM qui n’a besoin que de prospect.replied et partner.approved n’a pas à recevoir search.finished ; un relais Slack pour l’équipe partenariats peut se contenter de application.received. Des gestionnaires plus petits sont plus simples à vérifier, à surveiller et à mettre en pause.
Chaque point de terminaison affiche aussi le statut de sa dernière livraison et ses vingt dernières tentatives, avec la réponse de votre serveur. Quand un récepteur casse, cet historique dit si le problème vient de votre code, de votre certificat ou de votre pare-feu — et la lettre morte garde trente jours chaque événement manqué : rien n’est perdu pendant la correction.
Faire tourner un secret sans interruption
Lancez la rotation depuis Réglages du compte ou avec POST /v1/webhooks/{id}/rotate. Pendant les 24 heures suivantes, chaque livraison porte deux signatures — la nouvelle et l’ancienne —, si bien qu’un récepteur mis à jour plus tard dans la journée vérifie encore. Un point de terminaison peut aussi être mis en pause sans perdre son secret ; en pause, il ne reçoit rien et rien n’est mis en file. La documentation des webhooks détaille le contrat complet, et la référence chaque point de terminaison.
Documentation
Le contrat exact, généré à partir des schémas que les routes appliquent :
À lire ensuite
De la clé d’API au prospect inscrit : recherche de partenaires, sélection, e-mails à coût confirmé (402), relances sûres par idempotence et limites de débit.
Contactez les créateurs sélectionnés depuis votre propre Gmail, laissez les relances partir seules et suivez chaque personne de Trouvé à Partenaire actif.
Reliez votre assistant IA à GetAff (MCP, OAuth) : cherchez, classez et contactez des créateurs en langage naturel, coûts et envois validés par vous.