Webhooks
GetAff sendet einen POST an Ihren HTTPS-Endpunkt, wenn in Ihrem Konto etwas geschieht. Jede Zustellung ist signiert; Ihr Code prüft sie mit einem HMAC und muss nie wieder abfragen.
Ereignisse
Vier Ereignisse, jedes ein JSON-Körper { id, type, created_at, data }. data enthält Kennungen und Zählwerte, nie eine E-Mail-Adresse oder eine Nachricht: lesen Sie das vollständige Objekt bei Bedarf über die API. id beginnt mit msg_ und bleibt bei jedem erneuten Versuch desselben Ereignisses gleich.
search.finished- Eine Partnersuche einer Ihrer Websites ist abgeschlossen — vom Bildschirm, dem Chat, der API oder MCP.
data:siteId,searchId,results,onTopic,sources(je Quelle:id,status,count),startedAt,finishedAt. prospect.replied- Ein Kontakt hat auf eine Outreach-Sequenz geantwortet (vom Zehn-Minuten-Takt in Ihrem Postfach erkannt).
data:prospectId,sequenceId,mailboxEmail,repliedAt. Eine von Hand verschobene Stufe löst es nicht aus. application.received- Ein Creator hat sich für eines Ihrer Programme beworben.
data:applicationId,programId,programSlug,programName,affiliateName,affiliateSlug,score,autoApproved. partner.approved- Eine Partnerschaft in einem Ihrer Programme wurde genehmigt — Ihre Entscheidung, Ihre Regel zur automatischen Genehmigung oder eine angenommene Einladung.
data:partnershipId,programId,programSlug,programName,affiliateName,origin.
Einen Endpunkt registrieren
Unter Kontoeinstellungen › API › Webhooks oder per API: POST /v1/webhooks mit dem Scope write. Die Antwort enthält das Signaturgeheimnis (whsec_…) EINMALIG; bewahren Sie es serverseitig auf. Bis zu zehn Endpunkte je Konto, jeder mit eigenen Ereignissen und eigenem Geheimnis. Die URL muss https:// auf einem öffentlichen Host sein: eine private, Loopback- oder Link-local-Adresse, localhost oder ein .internal-Name wird bei der Registrierung und erneut bei jeder Zustellung abgelehnt.
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.Was ankommt
Ein POST mit drei Headern — webhook-id (die Nachrichtenkennung), webhook-timestamp (Unix-Sekunden) und webhook-signature (v1, gefolgt vom base64-kodierten HMAC-SHA256 über {id}.{timestamp}.{body}, mit den Bytes hinter whsec_ als Schlüssel) — und dem JSON-Körper. Antworten Sie innerhalb von 10 Sekunden mit einem beliebigen 2xx; der Körper Ihrer Antwort wird ignoriert. Jeder andere Status, ein Zeitüberschreitung oder ein Verbindungsfehler zählt als fehlgeschlagener Versuch.
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"}}Eine Zustellung prüfen
Das ist das Standard-Webhooks-Schema: jede Bibliothek dieses Namens prüft eine GetAff-Zustellung allein mit dem Geheimnis. Ohne Bibliothek vier Schritte:
- Lesen Sie den ROHEN Anfragekörper wie empfangen — kein neu serialisiertes Objekt: ein umsortierter Schlüssel ändert die Bytes, und die Signatur passt nicht mehr.
- Lehnen Sie einen
webhook-timestampab, der mehr als fünf Minuten von Ihrer Uhr abweicht: das ist das Replay-Fenster. - Berechnen Sie HMAC-SHA256 über
{webhook-id}.{webhook-timestamp}.{body}mit dem base64-dekodierten Geheimnis, kodieren Sie es base64 und vergleichen Sie in konstanter Zeit mit jedem durch Leerzeichen getrenntenv1,…-Wert inwebhook-signature. Ein Treffer genügt (während einer Rotation reisen zwei Werte). - Behandeln Sie
idals Idempotenzschlüssel: ein erneuter Versuch trägt dieselbe, und ein Empfänger, der gesehene Kennungen speichert, verarbeitet jedes Ereignis einmal.
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.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 wrongWiederholungen und Dead Letter
Ein fehlgeschlagener Versuch wird nach 1 Minute, dann 5 Minuten, 30 Minuten, 2 Stunden und 12 Stunden wiederholt — sechs Versuche über rund fünfzehn Stunden — stets mit derselben webhook-id und demselben Körper. Nach dem sechsten wird die Zustellung zur Dead Letter: aufbewahrt und sichtbar (Kontoeinstellungen oder GET /v1/webhooks/{id}), nie wiederholt. Ein abgelehnter Host oder ein 404/410 Ihrerseits ist sofort endgültig. Zustellungen bleiben dreißig Tage sichtbar. Die Aktion „Testen“ (oder POST /v1/webhooks/{id}/test) sendet sofort ein signiertes webhook.test-Ereignis und zeigt die Antwort Ihres Endpunkts.
Ein Geheimnis rotieren
Kontoeinstellungen › Rotieren oder POST /v1/webhooks/{id}/rotate. In den folgenden 24 Stunden trägt jede Zustellung zwei Signaturen in webhook-signature — neu und alt, durch Leerzeichen getrennt —, sodass ein später am Tag aktualisierter Empfänger weiterhin prüft. Danach ist das alte Geheimnis weg.
- Ein Geheimnis wird einmal gezeigt und ruhend versiegelt; niemand bei GetAff kann es zurücklesen. Verloren? Rotieren.
- Zustellungen gehen von GetAffs eigenen Servern aus, nie aus einem Browser, und tragen nie Ihren API-Schlüssel.
- Ein Endpunkt kann pausiert werden (
disabled), ohne sein Geheimnis zu verlieren; pausiert empfängt er nichts, und seine Ereignisse werden nicht eingereiht. - Prüfen Sie jede Zustellung, bevor Sie handeln. Eine unsignierte oder falsch signierte Anfrage an Ihren Endpunkt kam nicht von GetAff.
- Antworten Sie schnell mit
2xxund arbeiten Sie danach: ein langsamer Handler ist eine Zeitüberschreitung, und eine Zeitüberschreitung ist eine Wiederholung, die Sie doppelt erhalten.