Avec les Webhooks, ton système peut réagir automatiquement aux événements dans DigitalMembers, sans devoir vérifier sans cesse si quelque chose a changé. Dès qu’un changement se produit, nous envoyons un message signé à l’adresse que tu as indiquée. Tu le vérifies, tu confirmes sa réception et, si nécessaire, tu récupères les détails avec l’API.
Comment ça marche
Lorsqu’un événement se produit dans ton Organisation, nous envoyons une requête POST contenant quelques données à l’adresse que tu as indiquée. Tu vérifies la signature, tu réponds avec un code de statut 2xx et tu traites le message. Si tu ne réponds pas avec un code 2xx, nous réessayons automatiquement, à intervalles de plus en plus longs. Tu n’as rien à relancer manuellement.
Configurer un Webhook
Tu gères les Webhooks sous Organisation → Paramètres → Webhooks. Pour chaque Webhook, tu indiques une adresse HTTPS et tu choisis les événements que tu veux recevoir.
Tu configures la protection, par signature et éventuellement par OAuth2, une seule fois par Organisation dans la section « Authentification ». Elle s’applique à tous tes Webhooks. Active au moins une protection.
Événements disponibles
Voici les événements actuellement disponibles :
member.changed. Quelque chose a changé concernant un membre. Cet événement se déclenche lorsqu’un membre est créé, modifié ou supprimé, mais aussi lorsqu’un Champ ou une Affectation de champ change. Le type d’objet concerné se trouve dansdata.model(members.member,members.fieldoumembers.fieldassignment). L’action précise se trouve dansdata.operation(created,updated,deleted).registration.changed. Une inscription a changé. Cet événement se déclenche lorsqu’une nouvelle inscription arrive par un formulaire public, lorsqu’un membre soumet ou modifie un changement de profil, ou lorsqu’une inscription est acceptée ou refusée.data.modelcontientregistration.registrationetdata.operationindique l’action précise (created,updated). Si nécessaire, tu peux récupérer l’inscription concernée avec l’API à l’aide dedata.ids.
Structure du message
Chaque envoi a la même structure : des informations sur le message et un objet data qui décrit les objets concernés.
{
"id": "1efdff58-1111-1111-1111-111111111111",
"event": "member.changed",
"version": "v2",
"created_at": "2026-07-05T10:15:00+00:00",
"org_id": "1efdff58-2222-2222-2222-222222222222",
"data": {
"ids": ["1efdff58-0000-0000-0000-000000000000"],
"model": "members.member",
"service": "members",
"operation": "updated"
}
}
id: identifiant unique du message. Il reste le même lors des nouvelles tentatives. Ne traite chaqueidqu’une seule fois.event: type d’événement.version: version du format du message. Nous l’augmentons en cas de changement important.created_at: date et heure de l’événement.org_id: Organisation à laquelle appartient l’événement.data.ids: liste des identifiants des objets concernés. Elle te permet de récupérer les détails avec l’API.data.model/data.service: type d’objet et domaine concerné.data.modelindique à quoi correspondent les identifiants dansdata.ids:members.member(membres),members.field(champs),members.fieldassignment(affectations de champ) ouregistration.registration(inscriptions). Récupère l’objet correspondant avec l’API selon son type.data.operation: action effectuée, soitcreated,updatedoudeleted. Si l’action estdeleted, l’objet n’est plus accessible avec l’API.
Pourquoi le message ne contient pas de données personnelles
Le message indique uniquement ce qui a changé et où. Il ne contient jamais les données elles-mêmes, comme les noms, les adresses ou d’autres données personnelles. Voici pourquoi :
- La protection est facultative. Un Webhook peut aussi fonctionner sans OAuth2. Le message ne contient donc jamais de données personnelles, même si une adresse a été mal configurée par erreur.
- Tu gardes le contrôle de tes données. Tu récupères les détails dont tu as besoin avec l’API, en utilisant ton propre accès. Si quelqu’un prend le contrôle du système qui reçoit les messages, il ne peut pas y trouver les données des membres.
- L’Ordre n’est pas garanti. Dans de rares cas, un message peut aussi arriver deux fois. Considère donc le message comme un signal, pas comme la version définitive des données. Si nécessaire, récupère les données actuelles avec l’API au lieu de te fier uniquement au message.
Vérifier la signature
Si la signature est activée, chaque envoi comporte deux en-têtes. Ils te permettent de vérifier que le message vient bien de DigitalMembers et qu’il n’a pas été modifié en chemin, même si ton point de réception n’a pas d’autre moyen d’authentification.
X-Webhook-Signature: 5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd X-Webhook-Timestamp: 1720087200
- La signature est un HMAC-SHA256 calculé sur la chaîne
{timestamp}{body}avec ta clé de signature. Tu peux obtenir cette clé dans les paramètres des Webhooks avec « Copier la clé de signature ». - Calcule le HMAC sur
{X-Webhook-Timestamp}{empfangener Body}, en remplaçant la seconde partie par le corps reçu. Compare ensuite le résultat àX-Webhook-Signatureavec une comparaison à durée constante. - Rejette les messages dont l’horodatage date de plus de 5 minutes. Cela protège contre la réutilisation d’un ancien message.
- Vérifie toujours le corps brut, sans le modifier. Ne traite son contenu qu’après une vérification réussie.
import hashlib
import hmac
import time
def signature_is_valid(secret, signature, timestamp, body, tolerance_seconds=300):
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > tolerance_seconds:
return False
expected = hmac.new(secret.encode(), timestamp.encode() + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Ta clé de signature est comme un mot de passe. Garde-la en lieu sûr, ne la transmets à personne et ne la place jamais dans du code accessible au public. Si tu penses qu’elle a été divulguée, remplace-la immédiatement. Tu peux le faire à tout moment dans les paramètres des Webhooks. DigitalMembers décline toute responsabilité en cas de dommages causés par une clé de signature divulguée ou conservée sans précaution. Tu es seul responsable de sa conservation en lieu sûr.
OAuth2 (facultatif)
En plus de la signature, ou à sa place, chaque envoi peut contenir un jeton OAuth2. Pour cela, indique l’adresse d’obtention des jetons, l’identifiant client et le secret client de ton propre fournisseur d’identité. Avant chaque envoi, DigitalMembers obtient un jeton à cette adresse avec les identifiants du client et l’ajoute sous la forme Authorization: Bearer <token>. Comme le jeton vient de ton propre fournisseur, tu le vérifies auprès de ce fournisseur à ton point de réception.
Exemple : recevoir un Webhook avec FastAPI
Pour recevoir un Webhook, lis le corps brut, vérifie la signature et ne traite le message que si la vérification réussit.
import hashlib
import hmac
import json
import time
from fastapi import FastAPI, Header, HTTPException, Request
WEBHOOK_SECRET = "whsec_dein_signaturschluessel"
SIGNATURE_TOLERANCE_SECONDS = 300 # 5 minutes
app = FastAPI()
def signature_is_valid(signature, timestamp, body):
if not signature or not timestamp or not timestamp.isdigit():
return False
if abs(time.time() - int(timestamp)) > SIGNATURE_TOLERANCE_SECONDS:
return False
expected = hmac.new(WEBHOOK_SECRET.encode(), timestamp.encode() + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
@app.post("/webhooks/digitalmembers")
async def receive_webhook(request: Request,
x_webhook_signature: str | None = Header(default=None),
x_webhook_timestamp: str | None = Header(default=None)):
body = await request.body() # lire les octets bruts avant de décoder le JSON
if not signature_is_valid(x_webhook_signature, x_webhook_timestamp, body):
raise HTTPException(status_code=401, detail="signature invalide")
event = json.loads(body)
if event["event"] == "member.changed":
model = event["data"]["model"] # members.member | members.field | members.fieldassignment
object_ids = event["data"]["ids"] # liste des identifiants concernés
operation = event["data"]["operation"] # created | updated | deleted
# récupérer les détails avec l’API si nécessaire, avec ton propre accès
handle_member_change(model, object_ids, operation)
elif event["event"] == "registration.changed":
registration_ids = event["data"]["ids"] # inscriptions concernées
operation = event["data"]["operation"] # created | updated
handle_registration_change(registration_ids, operation)
return {"received": True}
Si tu as aussi activé OAuth2, vérifie le jeton Bearer reçu auprès de ton propre fournisseur d’identité et rejette les jetons invalides avec le code 401.
import jwt # pip install "pyjwt[crypto]"
from jwt import PyJWKClient
# JWKS de ton propre fournisseur d’identité, celui dont tu as indiqué l’adresse d’obtention des jetons
JWKS = PyJWKClient("https://dein-idp.example.com/.well-known/jwks.json")
def bearer_is_valid(authorization):
if not authorization or not authorization.lower().startswith("bearer "):
return False
token = authorization.split(" ", 1)[1]
try:
signing_key = JWKS.get_signing_key_from_jwt(token).key
jwt.decode(token, signing_key, algorithms=["RS256"]) # déclenche une erreur si le jeton est invalide ou expiré
return True
except jwt.InvalidTokenError:
return False
Envoi et nouvelles tentatives
Nous envoyons chaque message de manière fiable et réessayons automatiquement en cas de problème. Tu n’as rien à relancer manuellement.
- Réponse attendue : Ton point de réception doit répondre avec un code de statut 2xx dans les 10 secondes. Toute autre réponse, qu’il s’agisse d’un code d’erreur, d’un délai dépassé ou d’une adresse inaccessible, compte comme un échec et déclenche une nouvelle tentative.
- Nouvelles tentatives : Après le premier essai, nous réessayons à intervalles de plus en plus longs : après 5 minutes, 15 minutes, 30 minutes, 2 heures, 6 heures et 24 heures. Cela fait 7 tentatives d’envoi au total. Si ton point de réception répond avec un code 2xx lors de l’une d’elles, le message est considéré comme reçu et nous arrêtons. Si la dernière tentative échoue aussi, nous marquons le message comme définitivement non envoyé.
- Au moins une fois : Un message peut arriver deux fois dans de rares cas. Utilise donc son
idpour repérer les doublons et ne le traite qu’une seule fois. - Ordre : L’ordre d’envoi des messages pour un Webhook reste généralement stable, mais il n’est pas garanti. Ne t’y fie pas. Si nécessaire, récupère les données actuelles avec l’API.
- Renvoyer manuellement : Dans l’historique des envois, accessible depuis les détails du Webhook, tu vois le code de réponse et le nombre de tentatives pour chaque message. Tu peux aussi relancer un envoi à tout moment.
- Désactivation automatique : Si un Webhook échoue pendant une longue période, soit environ 20 échecs consécutifs sur au moins une semaine sans aucun envoi réussi, nous le désactivons automatiquement. Cela évite d’accumuler des messages destinés à une adresse qui ne répond plus. Tu le vois dans le statut du Webhook et tu peux le réactiver dans les paramètres. Le compteur d’échecs est alors remis à zéro.
Protection contre les boucles sans fin : Une limite assez élevée s’applique aussi au nombre d’envois par heure et par Organisation. Si ton système réagit à un événement en effectuant chez nous un changement qui déclenche à son tour un nouvel événement, une boucle peut se créer. Si la limite est dépassée, nous n’envoyons plus d’autres événements pendant cette heure. Ils ne seront pas envoyés plus tard non plus. Dans ce cas, compare les données avec celles de l’API REST. Évite donc de réécrire des données d’une façon qui déclenche à nouveau le même événement.
Conditions pour l’adresse
- Utilise uniquement
https://avec un nom d’hôte accessible publiquement. - Réponds rapidement avec un code 2xx et effectue les traitements longs en arrière-plan pour respecter le délai.
- Traite ta clé de signature comme un mot de passe et remplace-la si tu penses qu’elle a été divulguée.
Tester
Tu n’as pas besoin d’attendre un véritable événement : envoie un événement de test à ton point de réception depuis les paramètres des Webhooks. Dans l’historique des envois, tu vois le code de réponse de chaque message et tu peux relancer un envoi. Tu peux ainsi vérifier de bout en bout la signature et OAuth2 avant de commencer à recevoir de vrais événements.
Pour faire un essai sans utiliser ton propre système, tu peux aussi envoyer un Webhook directement à notre adresse de test :
https://signatera.digitalmembers.ch/v2/webhooks/test/{deine-org-id}/
Notre adresse de test accepte chaque envoi, vérifie la signature avec ta clé de signature et renvoie le résultat dans le corps de sa réponse (signature_present, signature_valid, timestamp_valid).