Integracje
Webhooki
Dowiedz się w ciągu kilku sekund, że ktoś się zarejestrował, zapłacił, wszedł lub usunął swoje dane – w treści podpisanej zgodnie ze Standard Webhooks, więc wiesz, że to od nas.
Jak działają
Punkt końcowy webhooka to twój adres https, który AttendBeacon wywołuje żądaniem POST, gdy coś dzieje się na twoich wydarzeniach. Dodaj go w przestrzeni roboczej w sekcji Integracje › Webhooki albo przez API (POST /webhooks), wybierz rodzaje zdarzeń, które ma otrzymywać, i zachowaj sekret do podpisywania, który zostanie pokazany tylko raz.
AttendBeacon najpierw zapisuje każde zdarzenie w kolejce w swojej bazie danych, a dopiero potem wywołuje każdy nasłuchujący punkt końcowy, więc restart po naszej stronie nigdy nie powoduje utraty zdarzenia. Każde wywołanie ma ten sam identyfikator wiadomości przy każdej próbie, więc powtórzenie łatwo rozpoznać.
Rodzaje zdarzeń
| Rodzaj | Wysyłane, gdy | Subskrypcja wymaga |
|---|---|---|
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 |
Sekcja Zdarzenia webhooków w dokumentacji API pokazuje pola każdego rodzaju i przykład. GET /webhooks/event-types zwraca tę samą listę ze schematem JSON każdej treści – dla kodu, który chce sprawdzać, co otrzymuje.
Co przychodzi
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
}
}
}Treść ma postać { type, timestamp, data }. Uczestnik w data ma te same pola co uczestnik zwracany przez API, więc kod napisany dla jednego działa też dla drugiego. Rodzaje dotyczące pobytów w hotelu, podróży, rozliczeń wydatków, zmian, certyfikatów, materiałów i członków społeczności zawierają tylko identyfikatory, statusy, daty i kwoty, nigdy imion i nazwisk.
Odpowiedz dowolnym kodem 2xx w ciągu 15 sekund. Właściwą pracę wykonaj później: umieść wiadomość we własnej kolejce i odpowiedz od razu.
Sprawdzanie podpisu
Na twój adres może wysłać dane każdy, więc sprawdź każde wywołanie, zanim mu zaufasz. Podpis to HMAC-SHA256 w Base64 z {webhook-id}.{webhook-timestamp}.{body}, obliczony sekretem twojego punktu końcowego (część po whsec_, zdekodowana z Base64), zapisany jako v1, i podpis. W czasie wymiany sekretu nagłówek może zawierać dwa podpisy oddzielone spacją. Przyjmij wywołanie, jeśli pasuje którykolwiek z nich.
- Sprawdzaj surową treść dokładnie w takiej postaci, w jakiej przyszła, przed jakimkolwiek parsowaniem JSON.
- Odrzucaj znaczniki czasu, które różnią się od twojego zegara o więcej niż pięć minut, aby nagranego wywołania nie dało się później odtworzyć.
- Porównuj w stałym czasie.
- Zapamiętuj identyfikatory obsłużonych wiadomości i pomijaj powtórzenia.
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
}Schemat to Standard Webhooks, więc jego biblioteki dla twojego języka zweryfikują nasze wywołania bez zmian, tym samym sekretem.
Ponowienia
Wywołanie, które w ciągu 15 sekund nie dostanie odpowiedzi 2xx, jest ponawiane osiem razy w ciągu niecałej doby: od razu, po 10 sekundach, po minucie, 5 minutach, 30 minutach, 2 godzinach, 6 godzinach i 15 godzinach. Ponawiana jest każda odpowiedź inna niż 2xx, również 4xx: punkt końcowy, który po nieudanym wdrożeniu odpowiada 401, zwykle do rana jest już naprawiony.
Wyjątkiem jest 410 Gone: wyłącza punkt końcowy, bo odbiorca zgłosił, że już nie istnieje. Gdy znów zacznie działać, włącz go ponownie w przestrzeni roboczej.
Strona każdego punktu końcowego w przestrzeni roboczej pokazuje jego dostarczenia z 30 dni: co wysłano, każdą próbę i odpowiedź punktu końcowego, z przyciskiem Wyślij ponownie przy nieudanych. Wyślij zdarzenie testowe wysyła podpisane webhook.test, a ty obserwujesz wynik.
Punkty końcowe przez API
Z uprawnieniem webhooks:write klucz może sam dodawać, zmieniać, testować i usuwać punkty końcowe. To wygodne dla oprogramowania, które konfiguruje je samodzielnie: POST /webhooks zwraca punkt końcowy i jego sekret (tylko raz), a POST /webhooks/{webhookId}/secret tworzy nowy sekret i od razu unieważnia stary. Przestrzeń robocza może mieć najwyżej 20 punktów końcowych.
Platformy automatyzacji
Power Automate, Zapier i Make subskrybują zamiast tego przez POST /hooks, jedna subskrypcja na przepływ, i otrzymują te same zdarzenia jako zwykły JSON z zawartością data przeniesioną na najwyższy poziom: { id, type, timestamp, event, attendee }. Nie potrzebują sekretu: podany przez nie adres sam jest ich sekretem. Zobacz Power Automate i Zapier.