Integrationen
Webhooks
Erfahren Sie innerhalb von Sekunden, wenn sich jemand anmeldet, bezahlt, eintritt oder seine Daten löscht, in einem nach Standard Webhooks signierten Body, damit Sie wissen, dass er von uns kommt.
So funktionieren sie
Ein Webhook-Endpunkt ist eine HTTPS-Adresse von Ihnen, die AttendBeacon mit einem POST aufruft, wenn bei Ihren Veranstaltungen etwas passiert. Fügen Sie einen im Arbeitsbereich unter Integrationen › Webhooks hinzu oder über die API (POST /webhooks), wählen Sie die Ereignistypen, die er empfängt, und bewahren Sie das Signiergeheimnis auf, das nur einmal angezeigt wird.
AttendBeacon schreibt jedes Ereignis zuerst in eine Warteschlange in seiner Datenbank und ruft dann jeden Endpunkt auf, der zuhört. Ein Neustart auf unserer Seite verliert also nie eines. Jeder Aufruf trägt bei jedem Versuch dieselbe Nachrichten-ID, sodass sich eine Wiederholung erkennen lässt.
Die Ereignistypen
| Typ | Gesendet, wenn | Zum Abonnieren nötig |
|---|---|---|
attendee.registered | A registration was created. | attendees:read people:read |
attendee.updated | A registration was changed. | attendees:read people:read |
attendee.cancelled | A registration was cancelled. | attendees:read people:read |
attendee.erased | An attendee deleted their data. | attendees:read |
order.paid | An order was paid. | orders:read people:read |
checkin.created | Somebody was let in. | checkins:read people:read |
feedback.given | A rating was given. | surveys:read people:read |
lead.captured | A lead was captured. | sponsors:read people:read |
certificate.issued | A certificate was issued. | attendees:read |
content.published | Session slides were published. | program:read |
member.joined | Somebody joined a community. | communities:read |
member.left | A member left a community. | communities:read |
stay.requested | A hotel stay was asked for. | events:read |
stay.assigned | A hotel stay was assigned. | events:read |
stay.changed | A hotel stay changed. | events:read |
stay.cancelled | A hotel stay was cancelled. | events:read |
shift.signed_up | A volunteer took a shift. | crew:read |
shift.dropped | A volunteer came off a shift. | crew:read |
travel.changed | A guest's travel changed. | events:read |
claim.submitted | An expense claim was sent in. | finance:read |
claim.approved | An expense claim was approved. | finance:read |
claim.paid | An expense claim was paid. | finance:read |
Webhook-Ereignisse in der Referenz zeigt die Felder jedes Typs und ein Beispiel. GET /webhooks/event-types antwortet mit derselben Liste samt dem JSON-Schema jeder Nutzlast, für Code, der prüfen will, was er empfängt.
Was ankommt
POST /attendbeacon HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
webhook-id: msg_2f6c1a9e4b7d4c0f8e3a5b9d1c7e0f42
webhook-timestamp: 1792130400
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{
"type": "attendee.registered",
"timestamp": "2026-10-14T12:00:00.000Z",
"data": {
"event": { "eventId": "b3ac0000-0000-4000-8000-000000000001", "slug": "beacon-summit", "name": "Beacon Summit" },
"attendee": {
"attendeeId": "b3ac0008-0000-4000-8000-000000000000",
"ticketTypeName": "Conference pass",
"firstName": "Emma",
"lastName": "Bakker",
"email": "emma.bakker0@example.com",
"status": "active",
"valid": true
}
}
}Der Body ist { type, timestamp, data }. Eine teilnehmende Person in data hat dieselben Felder wie in den Antworten der API, sodass Code für das eine auch für das andere funktioniert. Typen zu Hotelaufenthalten, Reisen, Spesenabrechnungen, Schichten, Bescheinigungen, Inhalten und Community-Mitgliedern tragen nur IDs, Status, Datumsangaben und Beträge, nie einen Namen.
Antworten Sie innerhalb von 15 Sekunden mit einem beliebigen 2xx. Erledigen Sie die eigentliche Arbeit danach: Legen Sie die Nachricht in eine eigene Warteschlange und antworten Sie sofort.
Die Signatur prüfen
Jeder kann an Ihre Adresse posten. Prüfen Sie deshalb jeden Aufruf, bevor Sie ihm vertrauen. Die Signatur ist der Base64-kodierte HMAC-SHA256 von {webhook-id}.{webhook-timestamp}.{body} mit dem Geheimnis Ihres Endpunkts (dem Teil nach whsec_, Base64-dekodiert), geschrieben als v1, gefolgt von der Signatur. Während ein Geheimnis ersetzt wird, kann der Header zwei enthalten, getrennt durch ein Leerzeichen; akzeptieren Sie den Aufruf, wenn eine davon passt.
- Prüfen Sie den unveränderten Body, genau so, wie er ankam, vor jedem JSON-Parsing.
- Lehnen Sie einen Zeitstempel ab, der mehr als fünf Minuten von Ihrer Uhr abweicht, damit ein aufgezeichneter Aufruf nicht später erneut abgespielt werden kann.
- Vergleichen Sie in konstanter Zeit.
- Merken Sie sich die Nachrichten-IDs, die Sie verarbeitet haben, und überspringen Sie Wiederholungen.
import { createHmac, timingSafeEqual } from "node:crypto";
// secret: "whsec_..." as the workspace showed it; body: the raw request body as a string.
export function verify(secret, headers, body) {
const id = headers["webhook-id"];
const timestamp = Number(headers["webhook-timestamp"]);
if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const expected = createHmac("sha256", key).update(`${id}.${timestamp}.${body}`).digest("base64");
return headers["webhook-signature"].split(" ").some((candidate) => {
const [version, signature] = candidate.split(",");
return version === "v1" && signature.length === expected.length
&& timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
});
}using System.Security.Cryptography;
using System.Text;
// secret: "whsec_..." as the workspace showed it; body: the raw request body as a string.
static bool Verify(string secret, string id, string timestamp, string signatures, string body)
{
if (!long.TryParse(timestamp, out var seconds) || Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - seconds) > 300) return false;
var key = Convert.FromBase64String(secret.StartsWith("whsec_") ? secret[6..] : secret);
var expected = Encoding.ASCII.GetBytes("v1," + Convert.ToBase64String(HMACSHA256.HashData(key, Encoding.UTF8.GetBytes($"{id}.{timestamp}.{body}"))));
return signatures.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.Any(candidate => CryptographicOperations.FixedTimeEquals(Encoding.ASCII.GetBytes(candidate), expected));
}import base64, hashlib, hmac, time
# secret: "whsec_..." as the workspace showed it; body: the raw request body as bytes.
def verify(secret: str, headers: dict, body: bytes) -> bool:
msg_id = headers["webhook-id"]
timestamp = headers["webhook-timestamp"]
if abs(time.time() - int(timestamp)) > 300:
return False
key = base64.b64decode(secret.removeprefix("whsec_"))
signed = f"{msg_id}.{timestamp}.".encode() + body
expected = "v1," + base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
return any(hmac.compare_digest(candidate, expected) for candidate in headers["webhook-signature"].split(" "))# $secret: "whsec_..." as the workspace showed it; $body: the raw request body as a string.
function Test-AttendBeaconSignature($secret, $id, $timestamp, $signatures, $body) {
$now = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
if ([math]::Abs($now - [long]$timestamp) -gt 300) { return $false }
$hmac = [System.Security.Cryptography.HMACSHA256]::new([Convert]::FromBase64String($secret -replace '^whsec_', ''))
$hash = $hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes("$id.$timestamp.$body"))
$expected = [Text.Encoding]::ASCII.GetBytes("v1," + [Convert]::ToBase64String($hash))
foreach ($candidate in $signatures -split ' ') {
if ([System.Security.Cryptography.CryptographicOperations]::FixedTimeEquals([Text.Encoding]::ASCII.GetBytes($candidate), $expected)) { return $true }
}
return $false
}Das Verfahren ist Standard Webhooks. Dessen Bibliotheken für Ihre Sprache prüfen unsere Aufrufe also unverändert, mit demselben Geheimnis.
Wiederholungen
Ein Aufruf, der nicht innerhalb von 15 Sekunden mit 2xx beantwortet wird, wird erneut versucht, achtmal über knapp einen Tag verteilt: sofort, nach 10 Sekunden, nach einer Minute, 5 Minuten, 30 Minuten, 2 Stunden, 6 Stunden und 15 Stunden. Jede Antwort außer 2xx wird wiederholt, auch ein 4xx: Ein Endpunkt, der nach einem fehlerhaften Deployment mit 401 antwortet, ist meist bis zum Morgen repariert.
Die Ausnahme ist 410 Gone: Damit wird der Endpunkt ausgeschaltet, weil der Empfänger gesagt hat, dass es ihn nicht mehr gibt. Schalten Sie ihn im Arbeitsbereich wieder ein, sobald es ihn wieder gibt.
Die Seite jedes Endpunkts im Arbeitsbereich listet seine Zustellungen der letzten 30 Tage auf: was gesendet wurde, jeden Versuch und was der Endpunkt geantwortet hat, mit Erneut senden für eine fehlgeschlagene Zustellung. Testereignis senden sendet ein signiertes webhook.test, während Sie zusehen.
Endpunkte über die API
Mit webhooks:write kann ein Schlüssel selbst Endpunkte hinzufügen, ändern, testen und entfernen. Das passt zu Software, die ihre eigenen einrichtet: POST /webhooks antwortet einmalig mit dem Endpunkt und seinem Geheimnis; POST /webhooks/{webhookId}/secret erzeugt ein neues Geheimnis und macht das alte sofort ungültig. Ein Arbeitsbereich hat höchstens 20 Endpunkte.
Automatisierungsplattformen
Power Automate, Zapier und Make abonnieren stattdessen über POST /hooks, ein Abonnement pro Flow, und erhalten dieselben Ereignisse als einfaches JSON, mit data auf die oberste Ebene gehoben: { id, type, timestamp, event, attendee }. Sie brauchen kein Geheimnis: Die Adresse, die sie angeben, ist ihr eigenes Geheimnis. Siehe Power Automate und Zapier.