Integraties
Webhooks
Hoor binnen seconden dat iemand zich registreert, betaalt, binnenkomt of de eigen gegevens verwijdert, in een body die volgens Standard Webhooks is ondertekend, zodat je weet dat het van ons komt.
Hoe ze werken
Een webhook-endpoint is een https-adres van jou dat AttendBeacon met een POST aanroept als er iets gebeurt bij je evenementen. Voeg er een toe in de werkruimte onder Integraties › Webhooks, of via de API (POST /webhooks), kies welke eventtypes hij ontvangt, en bewaar het ondertekeningsgeheim dat hij één keer toont.
AttendBeacon schrijft elk event eerst naar een wachtrij in de database en roept daarna elk endpoint aan dat luistert, zodat een herstart aan onze kant er nooit een kwijtraakt. Elke aanroep heeft bij elke poging dezelfde bericht-id, zodat je een herhaling herkent.
De eventtypes
| Type | Verstuurd als | Abonneren vereist |
|---|---|---|
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-events in de referentie toont de velden van elk type en een voorbeeld. GET /webhooks/event-types antwoordt met dezelfde lijst, met het JSON-schema van elke payload, voor code die wil controleren wat ze ontvangt.
Wat er binnenkomt
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
}
}
}De body is { type, timestamp, data }. Een deelnemer in data heeft dezelfde velden als de deelnemer waarmee de API antwoordt, dus code die voor het een is geschreven, werkt ook voor het ander. Types over hotelverblijven, reizen, declaraties, diensten, certificaten, content en communityleden bevatten alleen id's, statussen, datums en bedragen, nooit een naam.
Antwoord binnen 15 seconden met een willekeurige 2xx. Doe het werk daarna: zet het bericht in een eigen wachtrij en antwoord meteen.
De handtekening controleren
Iedereen kan naar je adres posten, dus controleer elke aanroep voordat je hem vertrouwt. De handtekening is de Base64-HMAC-SHA256 van {webhook-id}.{webhook-timestamp}.{body} met het geheim van je endpoint (het deel na whsec_, Base64-gedecodeerd), geschreven als v1, gevolgd door de handtekening. Terwijl een geheim wordt vervangen, kan de header er twee bevatten, gescheiden door een spatie; accepteer de aanroep als er een klopt.
- Controleer de ruwe body, precies zoals hij binnenkwam, vóór elke JSON-parsing.
- Weiger een tijdstempel die meer dan vijf minuten van je klok afwijkt, zodat een opgenomen aanroep later niet opnieuw kan worden afgespeeld.
- Vergelijk in constante tijd.
- Onthoud de bericht-id's die je hebt afgehandeld, en sla een herhaling over.
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
}Het schema is Standard Webhooks, dus de bibliotheken daarvan voor jouw taal controleren onze aanroepen zoals ze zijn, met hetzelfde geheim.
Nieuwe pogingen
Een aanroep die niet binnen 15 seconden 2xx antwoordt, wordt opnieuw geprobeerd, acht keer verspreid over iets minder dan een dag: meteen, na 10 seconden, een minuut, 5 minuten, 30 minuten, 2 uur, 6 uur en 15 uur. Elk antwoord anders dan 2xx krijgt een nieuwe poging, ook een 4xx: een endpoint dat na een mislukte deploy 401 antwoordt, is meestal de volgende ochtend weer in orde.
De uitzondering is 410 Gone: die zet het endpoint uit, omdat de ontvanger zei dat het niet meer bestaat. Zet het in de werkruimte weer aan zodra het er weer is.
De pagina van elk endpoint in de werkruimte toont de afleveringen van 30 dagen: wat er werd verstuurd, elke poging en wat het endpoint antwoordde, met Opnieuw sturen voor een aflevering die mislukte. Testbericht sturen stuurt een ondertekende webhook.test terwijl je meekijkt.
Endpoints via de API
Met webhooks:write kan een sleutel zelf endpoints toevoegen, wijzigen, testen en verwijderen, handig voor software die zijn eigen endpoints inricht: POST /webhooks antwoordt met het endpoint en zijn geheim, één keer; POST /webhooks/{webhookId}/secret maakt een nieuw geheim en stopt het oude meteen. Een werkruimte heeft hoogstens 20 endpoints.
Automatiseringsplatforms
Power Automate, Zapier en Make abonneren zich in plaats daarvan via POST /hooks, één abonnement per flow, en ontvangen dezelfde events als gewone JSON met data naar het hoogste niveau gehaald: { id, type, timestamp, event, attendee }. Ze hebben geen geheim nodig: het adres dat ze opgeven, is hun eigen geheim. Zie Power Automate en Zapier.