Aller au contenu principal

Webhooks

Les webhooks permettent à une église d'envoyer des notifications en temps réel vers des outils tiers — plateformes d'automatisation (Zapier, Make, n8n), CRM, systèmes comptables, ou tout ce qui accepte un HTTP POST. Lorsqu'une personne, un groupe ou un foyer change dans B1, B1 envoie une charge JSON signée à chaque URL abonnée à cet événement.

Avant de commencer

  • Un administrateur d'église avec la permission Modifier les paramètres de l'église enregistre et gère les webhooks
  • Votre point de terminaison récepteur doit être accessible via HTTPS à une adresse publique
  • Avoir un moyen de stocker le secret de signature en toute sécurité — il n'est affiché qu'une seule fois

Vue d'ensemble

Les webhooks sont sortants uniquement : B1 appelle votre point de terminaison, vous n'appelez pas B1. Chaque webhook est un abonnement par église composé d'une URL de destination, d'un secret de signature et d'une liste d'événements auxquels vous êtes abonné.

La livraison utilise une boîte d'envoi durable : lorsqu'un événement auquel vous êtes abonné se produit, B1 enregistre une ligne de livraison et un worker en arrière-plan envoie un POST en environ une minute. Les livraisons échouées sont réessayées avec un backoff exponentiel. Rien n'est perdu si une livraison est lente ou si votre point de terminaison est brièvement hors service.

Enregistrement d'un webhook

Dans B1Admin

Allez dans Paramètres → Webhooks → Nouveau webhook. Entrez un nom, l'URL de la charge et sélectionnez les événements auxquels vous abonner. Lors de l'enregistrement, le secret de signature est affiché une fois — copiez-le immédiatement et stockez-le avec votre intégration. Il n'est jamais affiché à nouveau (vous pouvez le faire pivoter plus tard, mais vous ne pouvez pas récupérer l'original).

Via l'API

Tous les points de terminaison sont sous le chemin de base du module Membership /membership/webhooks et nécessitent un JWT d'un administrateur d'église avec la permission Settings / Edit.

POST /membership/webhooks
Authorization: Bearer <jwt>
Content-Type: application/json

{
"name": "Zapier — nouveaux membres",
"url": "https://hooks.zapier.com/hooks/catch/123/abc",
"events": ["person.created", "person.updated", "group.member.added"]
}

La réponse de création — et uniquement la réponse de création — inclut le secret :

{
"id": "a1b2c3d4e5f",
"name": "Zapier — nouveaux membres",
"url": "https://hooks.zapier.com/hooks/catch/123/abc",
"events": ["person.created", "person.updated", "group.member.added"],
"active": true,
"secret": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822c"
}
Méthode et cheminObjectif
GET /membership/webhooksListe les webhooks de l'église (secret omis)
GET /membership/webhooks/eventsLe catalogue des noms d'événements valides
GET /membership/webhooks/:idCharge un webhook
POST /membership/webhooksCréer (sans id) ou mettre à jour (avec id)
POST /membership/webhooks/:id/regenerate-secretFaire pivoter le secret de signature ; retourne la nouvelle valeur une fois
DELETE /membership/webhooks/:idSupprimer un webhook
GET /membership/webhooks/:id/deliveriesTentatives de livraison récentes pour un webhook
GET /membership/webhooks/deliveries/:deliveryIdCharge complète et réponse pour une livraison
POST /membership/webhooks/deliveries/:deliveryId/redeliverRemettre en file d'attente une livraison

Catalogue d'événements

Les noms d'événements suivent le modèle {entité}.{action}. Récupérez la liste en direct depuis GET /membership/webhooks/events.

ÉvénementSe déclenche quand
person.createdUne personne est ajoutée
person.updatedUn dossier de personne est modifié
person.destroyedUne personne est supprimée
household.createdUn foyer est ajouté
household.updatedUn foyer est modifié
household.destroyedUn foyer est supprimé
group.createdUn groupe est ajouté
group.updatedUn groupe est modifié
group.destroyedUn groupe est supprimé
group.member.addedUne personne est ajoutée à un groupe
group.member.removedUne personne est retirée d'un groupe

Format de la charge

Chaque livraison est un POST HTTP avec un corps JSON et ces en-têtes :

En-têteDescription
Content-TypeToujours application/json
X-B1-EventLe nom de l'événement, par exemple person.created
X-B1-Delivery-IdId unique pour cette tentative de livraison — utilisez-le pour dédupliquer
X-B1-SignatureSignature HMAC-SHA256 du corps brut (voir ci-dessous)
X-B1-TimestampSecondes de l'époque Unix lorsque la demande a été envoyée
User-AgentB1-Webhooks/1.0

Le corps enveloppe la ressource modifiée dans une petite enveloppe :

{
"event": "person.created",
"churchId": "AbC123XyZ90",
"occurredAt": "2026-05-17T14:32:08.114Z",
"data": {
"id": "Pq7Rs2Tu4Vw",
"churchId": "AbC123XyZ90",
"name": { "display": "Jordan Rivera", "first": "Jordan", "last": "Rivera" },
"contactInfo": { "email": "jordan@example.com" }
}
}

Pour les événements *.destroyed, data contient uniquement l'id et le churchId de l'enregistrement supprimé.

Vérification des signatures

Vérifiez toujours X-B1-Signature avant de faire confiance à une charge. La signature est sha256= suivi du HMAC-SHA256 hex du corps de requête brut claveté avec votre secret de signature. Calculez-le sur les octets que vous avez reçus — ne re-sérialisez pas le JSON analysé.

Node.js

const crypto = require("crypto");

function isValid(rawBody, signatureHeader, secret) {
const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody, "utf8").digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader || "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Python

import hashlib, hmac

def is_valid(raw_body: bytes, signature_header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header or "")

PHP

function isValid(string $rawBody, string $signatureHeader, string $secret): bool {
$expected = "sha256=" . hash_hmac("sha256", $rawBody, $secret);
return hash_equals($expected, $signatureHeader ?? "");
}

Rejetez toute demande dont la signature ne correspond pas. Rejetez également facultativement les demandes dont le X-B1-Timestamp date de plus de quelques minutes pour limiter les fenêtres de rejeu.

Livraison et réessais

Votre point de terminaison devrait répondre avec un statut 2xx aussi rapidement que possible — idéalement après avoir seulement mis le travail en file d'attente, pas après l'avoir traité. Toute réponse non-2xx, un échec de connexion ou une réponse plus lente que 10 secondes compte comme une livraison échouée.

Les livraisons échouées sont réessayées avec un backoff exponentiel — 16 tentatives sur environ 5 jours. L'intervalle passe de 1 minute, à travers des heures, jusqu'à des écarts de 3 jours pour les dernières tentatives. Après la 16e tentative échouée, la livraison est marquée exhausted et abandonnée.

La livraison est au moins une fois : une livraison peut arriver plus d'une fois (par exemple, si votre point de terminaison réussit mais que la réponse est perdue). Utilisez l'en-tête X-B1-Delivery-Id pour dédupliquer — traitez chaque id une seule fois et traitez les répétitions comme des no-ops.

Désactivation automatique

Si un webhook produit trois livraisons épuisées consécutives, B1 le désactive automatiquement. Corrigez votre point de terminaison, puis réactivez le webhook dans B1Admin (ou via POST /membership/webhooks avec "active": true).

Inspection et relivraison

L'éditeur de webhook dans B1Admin affiche un tableau Livraisons récentes — événement, statut, nombre de tentatives, code de réponse et horodatage. La sélection d'une ligne révèle la charge complète qui a été envoyée et la réponse qui est revenue.

Utilisez Relivrer pour remettre en file d'attente toute livraison passée avec sa charge d'origine — utile après avoir corrigé un bug dans votre point de terminaison, ou pour remplir rétroactivement les événements que votre point de terminaison a manqués pendant qu'il était hors service.

Exigences d'URL

Parce que les URL de webhook sont fournies par l'église, B1 applique des protections contre la falsification de requête côté serveur. Une URL de webhook est rejetée — lors de l'enregistrement et revérifiée avant chaque livraison — si elle :

  • n'utilise pas https
  • pointe vers localhost, un nom d'hôte .local / .internal, ou
  • se résout en une adresse IP privée, de bouclage, link-local ou de métadonnées cloud

Votre point de terminaison doit être un service HTTPS accessible publiquement.