Intégrations
Webhooks
Soyez prévenu en quelques secondes quand quelqu’un s’inscrit, paie, arrive ou supprime ses données, dans un corps signé selon Standard Webhooks pour que vous sachiez que l’appel vient bien de nous.
Comment ils fonctionnent
Un point de terminaison webhook est une adresse https à vous qu’AttendBeacon appelle avec un POST quand quelque chose se passe dans vos événements. Ajoutez-en un dans l’espace de travail sous Intégrations › Webhooks, ou par l’API (POST /webhooks), choisissez les types d’événements qu’il reçoit, et conservez le secret de signature qu’il affiche une seule fois.
AttendBeacon écrit d’abord chaque événement dans une file d’attente de sa base de données, puis appelle chaque point de terminaison qui l’écoute : un redémarrage de notre côté n’en perd donc jamais un seul. Chaque appel porte le même identifiant de message à chaque tentative, pour qu’une répétition puisse être reconnue.
Les types d’événements
| Type | Envoyé quand | Requis pour s’abonner |
|---|---|---|
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 |
La page Événements webhook de la référence montre les champs de chaque type et un exemple. GET /webhooks/event-types renvoie la même liste avec le schéma JSON de chaque contenu, pour le code qui veut vérifier ce qu’il reçoit.
Ce qui arrive
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
}
}
}Le corps est { type, timestamp, data }. Un participant dans data a les mêmes champs que le participant renvoyé par l’API : le code écrit pour l’un fonctionne pour l’autre. Les types concernant les séjours à l’hôtel, les déplacements, les notes de frais, les créneaux, les attestations, les contenus et les membres des communautés ne portent que des identifiants, des statuts, des dates et des montants, jamais un nom.
Répondez avec n’importe quel 2xx dans les 15 secondes. Faites le travail ensuite : placez le message dans votre propre file d’attente et répondez immédiatement.
Vérifier la signature
N’importe qui peut envoyer des requêtes à votre adresse : vérifiez donc chaque appel avant de lui faire confiance. La signature est le HMAC-SHA256 en Base64 de {webhook-id}.{webhook-timestamp}.{body} avec le secret de votre point de terminaison (la partie après whsec_, décodée en Base64), écrite v1, suivi de la signature. Pendant le remplacement d’un secret, l’en-tête peut en contenir deux, séparées par une espace ; acceptez l’appel si l’une d’elles correspond.
- Vérifiez le corps brut, exactement tel qu’il est arrivé, avant tout parsing JSON.
- Refusez un horodatage éloigné de plus de cinq minutes de votre horloge, pour qu’un appel enregistré ne puisse pas être rejoué plus tard.
- Comparez en temps constant.
- Mémorisez les identifiants de messages déjà traités, et ignorez les répétitions.
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
}Le schéma est celui de Standard Webhooks : ses bibliothèques pour votre langage vérifient nos appels tels quels, avec le même secret.
Nouvelles tentatives
Un appel qui ne reçoit pas de 2xx dans les 15 secondes est retenté, huit fois sur un peu moins d’une journée : immédiatement, puis après 10 secondes, une minute, 5 minutes, 30 minutes, 2 heures, 6 heures et 15 heures. Toute réponse autre que 2xx est retentée, y compris un 4xx : un point de terminaison qui répond 401 après un déploiement raté est généralement réparé au matin.
L’exception est 410 Gone : il désactive le point de terminaison, puisque le destinataire a indiqué qu’il n’existe plus. Réactivez-le dans l’espace de travail quand il existe de nouveau.
La page de chaque point de terminaison dans l’espace de travail liste ses livraisons sur 30 jours : ce qui a été envoyé, chaque tentative et ce que le point de terminaison a répondu, avec Renvoyer pour une livraison en échec. Envoyer l'événement d'essai envoie un webhook.test signé sous vos yeux.
Points de terminaison par l’API
Avec webhooks:write, une clé peut ajouter, modifier, tester et supprimer elle-même des points de terminaison, ce qui convient aux logiciels qui configurent les leurs : POST /webhooks renvoie le point de terminaison et son secret, une seule fois ; POST /webhooks/{webhookId}/secret en crée un nouveau et désactive immédiatement l’ancien. Un espace de travail compte au maximum 20 points de terminaison.
Plateformes d’automatisation
Power Automate, Zapier et Make s’abonnent plutôt via POST /hooks, un abonnement par flux, et reçoivent les mêmes événements en JSON simple, avec data remonté au premier niveau : { id, type, timestamp, event, attendee }. Ils n’ont pas besoin de secret : l’adresse qu’ils fournissent est leur propre secret. Voir Power Automate et Zapier.