Zum Inhalt springen

Webhooks

Für Integratoren. Wie Memberfy dein System benachrichtigt, wenn in der Community etwas passiert, das Format des Umschlags, die Standard-Webhooks-Signatur und wie du sie in Node prüfst, Deduplizierung, Reihenfolge, Wiederholungen, automatisches Ausschalten und die Limits.

Ein Webhook ist Memberfy, das dein System aufruft: Wenn jemand beitritt, kauft, abonniert, sich für eine Veranstaltung anmeldet oder einen Kurs abschließt, sendet Memberfy sofort einen signierten POST an die URL, die du hinterlegt hast. Es ist der umgekehrte Weg der API, bei der dein System fragt.

Den Webhook legt der Eigentümer oder ein Administrator der Community an, unter Einstellungen › Integrationen und APIs, Tab Webhooks. Siehe Integrationen & APIs › Webhooks. Diese Anleitung ist für alle, die den Code schreiben, der ihn empfängt.

Was ankommt

Ein POST pro Ereignis und pro Webhook, mit Content-Type: application/json; charset=utf-8 und User-Agent: Memberfy-Webhooks/1. Der Inhalt ist immer derselbe Umschlag:

{
  "id": "evt_01JAB3K7Q9X2M4N6P8R0S2T4V6",
  "type": "purchase.completed",
  "version": 1,
  "occurredAt": "2026-10-19T14:03:11.402Z",
  "community": { "id": "c3f1a2b4-5d6e-4f70-8a9b-0c1d2e3f4a5b", "slug": "apicon" },
  "data": {
    "member": { "id": "9b8a7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d", "name": "Maria Souza", "email": "[email protected]", "phone": "+5541999998888", "phoneCountry": "BR" },
    "purchaseId": "…",
    "product": { "id": "…", "name": "Pro-Plan", "type": "SUBSCRIPTION" },
    "amount": { "amount": 9990, "currency": "BRL" }
  }
}
FeldWas es ist
idDie Kennung des Ereignisses. Sie ist dieselbe wie im Header webhook-id, bei allen Versuchen und beim erneuten Senden
typeDas Ereignis (member.joined, purchase.completed…) oder ping, der Test
versionDie Version des data-Formats dieses Ereignisses. Heute überall 1
occurredAtWann das Ereignis bei Memberfy passiert ist (ISO 8601, UTC), nicht wann die Zustellung losging
communityDie Community: id und slug
dataWas passiert ist. Unterscheidet sich je Ereignis; siehe Webhook-Ereignisse

Beträge kommen in Cent, etwa { "amount": 9990, "currency": "BRL" }, immer das, was die Person bezahlt hat. Die beteiligte Person steht in data.member, mit id, name, email, phone (im Format E.164, etwa +5541999998888, oder null) und phoneCountry (das Land der Telefonnummer nach ISO 3166-1 Alpha-2, etwa BR, oder null).

Die Ereignisse

Es sind 97 Ereignisse in 27 Familien, dazu der ping:

FamilieEreignisse
membermember.joined (auch für per Tabelle importierte Personen, mit source: "import"), member.left, member.returned, member.role_changed, member.tag_added, member.tag_removed, member.group_joined, member.group_left, member.field_changed, member.level_changed, member.points_changed
purchasepurchase.completed, purchase.refunded
subscriptionsubscription.created, subscription.renewed, subscription.canceled, subscription.ended, subscription.plan_changed, subscription.past_due, subscription.trial_ending
eventevent.registered (mit den Antworten zur Anmeldung in registration), event.checked_in, event.check_in_undone, event.ticket_assigned, event.registration_canceled, event.waitlist_offered, event.created, event.updated, event.deleted, event.stream_started, event.recording_published, event.rescheduled
coursecourse.enrolled, course.completed, course.published, course.updated, course.left
certificatecertificate.issued, certificate.revoked
formform.submitted, form.submission_reviewed, form.published, form.closed, form.reopened
lessonlesson.completed, lesson.rated, lesson.published
commentcomment.created, comment.deleted
reactionreaction.added, reaction.removed
postpost.created, post.updated, post.moved, post.deleted
checkoutcheckout.started, checkout.abandoned, checkout.recovered
spacespace.first_visit, space.access_granted, space.created, space.updated, space.deleted
quizquiz.passed, quiz.failed
couponcoupon.redeemed
surveysurvey.answered
proposalproposal.submitted, proposal.decided
upvoteupvote.topic_created, upvote.topic_status_changed
downloaddownload.completed, download.item_published, download.item_updated, download.item_moved, download.file_added, download.file_replaced, download.file_removed, download.item_deleted, download.category_created
classifiedclassified.published, classified.interest_received
parcelparcel.received, parcel.picked_up
visitorvisitor.invited, visitor.arrived
attendanceattendance.checked_in, attendance.checked_out
support_ticketsupport_ticket.created, support_ticket.resolved
bookingbooking.created, booking.canceled, booking.checked_in, booking.no_show
messagemessage.received (ohne den Text der Nachricht)
badgebadge.awarded, badge.revoked
(Test)ping, gesendet über den Button Test senden und beim Wiedereinschalten, mit data: { "message": "…" }

Ein Webhook abonniert Ereignisse einzeln oder die ganze Familie (member.*); wer die Familie ankreuzt, bekommt auch Ereignisse, die später dazukommen. Die Felder jedes Ereignisses stehen unter Webhook-Ereignisse.

Eine noch nicht angenommene Einladung erzeugt kein member.joined.

Level und Punkte

member.level_changed geht raus, wenn sich das Level der Person ändert:

{
  "member": { "id": "…", "name": "Maria Souza", "email": "[email protected]" },
  "direction": "down",
  "cause": "activity",
  "from": { "id": "…", "level": 5, "name": "Kernel" },
  "to": { "id": "…", "level": 4, "name": "Shell" },
  "points": 640
}
  • direction: up (aufgestiegen, sofort) oder down (abgestiegen).
  • cause: points (die Punkte haben das Level geändert) oder activity (die Anforderungen der Woche: erfüllt, beim Aufstieg, oder in der Vorwoche nicht erfüllt, beim Abstieg am Montag um 00:00 in der Zeitzone der Community).
  • from und to: das Level vorher und nachher, mit id, level (die Nummer, 1 ist das erste) und name. Die id ist null, solange die Community die Standard-Level nutzt und sie nie geöffnet hat.
  • Mehrere Level auf einmal erzeugen ein Ereignis, mit dem Endlevel in to.

member.points_changed geht raus, wenn sich die Punktesumme der Person in der Community ändert: from, to, delta und source (action, eine Aktion der Person; reversal, zurückgenommene Punkte, weil die Aktion rückgängig gemacht wurde; team, eine Anpassung durch das Team; workflow, eine Anpassung durch einen Workflow).

Keines der beiden geht bei einer Neuberechnung der Punkte, einem Import, einer Änderung der Level durch die Community oder bei ausgeschalteter Gamification raus.

Was nie mitgeschickt wird

Kein Ereignis enthält Gebühren, Nettobeträge, Daten des Zahlungsanbieters oder der Karte, Steuernummern aus dem Konto, Adresse, Geburtsdatum, benutzerdefinierte Felder, Passwörter, Tokens oder Secrets, IP oder den Text von Beiträgen, Kommentaren und Tickets. Die Antworten auf ein Formular (form.submitted) und auf die Anmeldefragen einer Veranstaltung (event.registered) gehen so mit, wie die Person sie ausgefüllt hat, einschließlich des Felds Ausweis. Name, E-Mail und Telefonnummer der Person (wenn sie eine hinterlegt hat) sind immer dabei: Deshalb weist die Oberfläche beim Erstellen des Webhooks darauf hin, dass personenbezogene Daten an das Ziel gehen.

Die Signatur prüfen

Jede Zustellung ist nach Standard Webhooks signiert, mit dem Secret des Webhooks (beginnt mit whsec_ und wird nur einmal angezeigt, beim Erstellen oder Rotieren). Drei Header:

HeaderWas es ist
webhook-idDie ID des Ereignisses (dieselbe id wie im Inhalt)
webhook-timestampWann der Versuch losging, in Sekunden seit 1970
webhook-signaturev1, gefolgt vom HMAC-SHA256 in Base64 von {webhook-id}.{webhook-timestamp}.{Inhalt}

Prüfe immer, bevor du dem Inhalt vertraust, mit dem Inhalt genau so, wie er angekommen ist (ohne ihn vorher zu parsen), und lehne einen webhook-timestamp ab, der mehr als 5 Minuten von deiner Uhr abweicht. Die Bibliothek standardwebhooks erledigt beides. In Node mit Express:

import express from 'express';
import { Webhook } from 'standardwebhooks';

const app = express();
const webhook = new Webhook(process.env.MEMBERFY_WEBHOOK_SECRET); // whsec_…

// Der rohe Inhalt: Die Signatur gilt für die angekommenen Bytes.
app.post('/memberfy', express.raw({ type: 'application/json' }), async (req, res) => {
  let event;
  try {
    event = webhook.verify(req.body.toString('utf8'), {
      'webhook-id': req.get('webhook-id'),
      'webhook-timestamp': req.get('webhook-timestamp'),
      'webhook-signature': req.get('webhook-signature'),
    });
  } catch {
    return res.status(400).send('invalid signature');
  }

  if (await alreadyProcessed(event.id)) return res.sendStatus(200); // deduplizieren
  res.sendStatus(200); // sofort antworten, später verarbeiten
  await queue.add(event); // z. B. deine Job-Warteschlange
});

app.listen(3000);

verify prüft Signatur und Zeit und liefert den bereits gelesenen Umschlag. Bibliotheken für denselben Standard gibt es für Python, Go, Ruby, PHP, Java und weitere Sprachen.

Beim Rotieren des Secrets gilt das neue sofort und das alte nicht mehr: Aktualisiere deine Seite direkt danach.

Antworten und verarbeiten

  • Antworte innerhalb von 10 Sekunden mit einem beliebigen 2xx. Danach zählt der Versuch als Fehler.
  • Verarbeite nach der Antwort (eine Warteschlange, ein Job): Eine langsame Integration darf die Antwort nicht aufhalten.
  • Dedupliziere über die webhook-id. Dasselbe Ereignis kann mehrmals ankommen (eine im Netz verlorene Antwort, ein erneutes Senden über die Oberfläche): Speichere die schon verarbeiteten IDs und ignoriere Wiederholungen.
  • Die Reihenfolge ist nicht garantiert. Ein subscription.renewed kann vor dem subscription.created ankommen, das gerade wiederholt wurde. Sortiere nach occurredAt und frag die API, wenn du den aktuellen Stand brauchst.
  • Weiterleitungen werden nicht verfolgt: Ein 301 oder 302 zählt als Fehler. Hinterlege die endgültige URL.

Wiederholungen

Ohne 2xx versucht Memberfy es erneut. Es sind bis zu 8 Versuche pro Zustellung: der erste sofort, die weiteren nach 1 Min., 5 Min., 30 Min., 2 Std., 6 Std., 12 Std. und 24 Std., insgesamt etwa 45 Stunden. Nach allen 8 bleibt die Zustellung im Verlauf als Fehlgeschlagen, und sie lässt sich über die Oberfläche erneut senden (ein neuer Zyklus, mit derselben webhook-id).

Die AntwortWas passiert
2xxZugestellt
410 GoneDer Webhook wird sofort ausgeschaltet, ohne weitere Versuche, und Eigentümer und Administratoren erhalten eine E-Mail
429Neuer Versuch nach dem Retry-After (in Sekunden oder als Datum), falls vorhanden; zählt nicht als Fehler
Jeder andere Status, Zeitüberschreitung oder NetzwerkfehlerFehler; folgt dem Zeitplan oben

Automatisches Ausschalten

Bleibt das Ziel 72 Stunden ohne erfolgreiche Zustellung (mit mindestens 5 Fehlern in dieser Zeit), wird der Webhook ausgeschaltet (Wegen Fehlern ausgeschaltet), ausstehende Versuche werden abgebrochen, und Eigentümer und Administratoren erhalten eine E-Mail. Ereignisse, die währenddessen passieren, werden später nicht gesendet.

Zum Zurückholen nutze Wieder einschalten in der Oberfläche: Memberfy sendet zuerst einen ping, und der Webhook kommt nur zurück, wenn das Ziel mit 2xx antwortet.

Adresse und Netzwerk

  • Nur https, auf Port 443, an einer öffentlichen Adresse. Interne Adressen, private Netze und Loopback werden abgelehnt.
  • Keine Weiterleitung und ein Limit von 10 Sekunden pro Versuch. Von der Antwort speichert Memberfy nur den Anfang (1 KB) für den Verlauf.
  • Akzeptiert deine Firewall nur bekannte Adressen, kreuze beim Webhook Von festen IPs senden an und gib die festen IPs von Memberfy frei.

Limits

LimitWert
Webhooks pro Community10
Erstellen, Test senden, wieder einschalten, erneut senden und URL ändern10 pro Minute pro Community, zusammen; darüber 429 mit Retry-After
Verlauf der Zustellungen und Änderungen30 Tage

Jede erfolgreiche Zustellung eines Ereignisses zählt bei Zugestellte Webhooks im Block Nutzung von Integrationen & APIs. Der ping zählt nicht.

Per API verwalten

Die Oberfläche nutzt die Webhook-API unter https://developers.memberfy.net (/api/webhooks: auflisten, erstellen, URL, Beschreibung, Ereignisse und feste IPs bearbeiten, Test senden, rotieren, ausschalten, wieder einschalten, löschen, Zustellungen, erneutes Senden und Verlauf). Sie akzeptiert nur das Anmelde-Token (Authorization: Bearer) mit der X-CommunityId, von Eigentümer oder Administrator; ein API-Schlüssel kommt nicht hinein. Die Spezifikation liegt unter developers.memberfy.net/docs.json. Der Ereigniskatalog ist öffentlich: GET /api/integrations/events.

Einen Webhook bearbeiten

PATCH /api/webhooks/{id} nur mit dem, was sich ändert; ein fehlendes Feld bleibt, wie es ist.

curl -X PATCH https://developers.memberfy.net/api/webhooks/WEBHOOK_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-CommunityId: $COMMUNITY_ID" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://hooks.memberfy.net/crm-v2", "events": ["member.*", "purchase.completed"], "fixedIps": true}'
FeldTypHinweis
urlText (bis 2048)Dieselbe Prüfung wie beim Erstellen (https, Port 443, öffentliche Adresse); zählt zum Limit von 10 pro Minute und Community
descriptionText (bis 200) oder null
eventsListe (1 bis 50)Typen oder ganze Familien (member.*)
fixedIpsBooleanSchaltet Von festen IPs senden ein oder aus
  • Die neue Konfiguration gilt für Ereignisse, die danach passieren. Ausstehende Zustellungen, Wiederholungen und späteres erneutes Senden laufen so, wie sie erstellt wurden: dieselbe URL, dieselbe Nutzung fester IPs und derselbe Payload. Die Signatur nutzt immer das aktuelle Secret.
  • Eine neue url setzt failureCount zurück.
  • Bearbeiten schaltet nicht wieder ein: Ein Webhook DISABLED oder DISABLED_FAILING bleibt aus. Wieder einschalten ist POST /api/webhooks/{id}/enable, das den Test sendet und nur bei einer 2xx-Antwort einschaltet.
  • Jede Bearbeitung, die etwas ändert, wird zu einer Zeile updated im Verlauf, mit Vorher und Nachher jedes Felds in changes (URLs maskiert). Eine Bearbeitung ohne Änderung erzeugt keine Zeile.
  • Antworten: 200 mit dem Webhook, 404, 422 (ungültiges Feld, in errors[].param) und 429.

Fertige Integrationen

Die fertigen Integrationen (Discord, Slack, Telegram, RD Station, HubSpot) nutzen dieselbe API unter /api/apps: GET /api/apps (Katalog, mit der Installation jeder App), PUT /api/apps/{slug}/installation (schaltet ein oder speichert Konfiguration und Ereignisse), POST …/installation/test, DELETE …/installation (schaltet aus; mit ?remove=true wird sie entfernt und Konfiguration und Zustellungen gelöscht), POST /api/apps/telegram/chats (Meine Gruppe finden) und die Zustellungen unter …/installation/deliveries, mit erneutem Senden. Dieselben Zugriffsregeln: Login-Token von Eigentümer oder Administrator, nie ein API-Schlüssel. Geheime Felder kommen maskiert zurück; bei einem PUT behält ein fehlendes geheimes Feld den gespeicherten Wert.

Verwandte Artikel