Vai al contenuto principale

Webhook

I webhook consentono a una chiesa di inviare notifiche in tempo reale a strumenti di terze parti — piattaforme di automazione (Zapier, Make, n8n), CRM, sistemi contabili o qualsiasi cosa che accetti un POST HTTP. Quando una persona, gruppo o nucleo familiare cambia in B1, B1 invia un payload JSON firmato a ogni URL iscritto a quell'evento.

Prima di iniziare

  • Un amministratore della chiesa con il permesso Modifica impostazioni chiesa registra e gestisce i webhook
  • L'endpoint ricevente deve essere raggiungibile su HTTPS a un indirizzo pubblico
  • Avere un modo per archiviare il segreto di firma in modo sicuro — viene visualizzato solo una volta

Panoramica

I webhook sono solo in uscita: B1 chiama il tuo endpoint, non il contrario. Ogni webhook è una sottoscrizione per chiesa costituita da un URL di destinazione, un segreto di firma e un elenco di eventi sottoscritti.

La consegna utilizza un outbox durevole: quando si verifica un evento sottoscritto, B1 registra una riga di consegna e un worker di sfondo lo PUBBLICA entro circa un minuto. Le consegne non riuscite vengono ritentate con backoff esponenziale. Nulla va perso se una consegna è lenta o il tuo endpoint è brevemente inattivo.

Registrazione di un Webhook

In B1Admin

Vai a Impostazioni → Webhook → Nuovo Webhook. Immetti un nome, l'URL del payload, e seleziona gli eventi a cui sottoscriversi. Al salvataggio, il segreto di firma viene visualizzato una volta — copialo immediatamente e archivialo con la tua integrazione. Non viene mai più mostrato (puoi ruotarlo in seguito, ma non puoi recuperare l'originale).

Tramite API

Tutti gli endpoint si trovano nel percorso base del modulo Membership /membership/webhooks e richiedono un JWT di un amministratore della chiesa con il permesso Settings / Edit, oppure una chiave API coniata con l'ambito settings:write. Le stesse route accettano entrambi. Questo è ciò che consente a Zapier e Make di registrare webhook per conto della chiesa quando uno Zap o uno scenario viene attivato.

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

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

La risposta di creazione — e solo la risposta di creazione — include il secret:

{
"id": "a1b2c3d4e5f",
"name": "Zapier — new members",
"url": "https://hooks.zapier.com/hooks/catch/123/abc",
"events": ["person.created", "person.updated", "group.member.added"],
"active": true,
"secret": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822c"
}
Metodo e percorsoScopo
GET /membership/webhooksElencare i webhook della chiesa (segreto omesso)
GET /membership/webhooks/eventsIl catalogo dei nomi di eventi validi
GET /membership/webhooks/:idCaricare un webhook
POST /membership/webhooksCreare (senza id) o aggiornare (con id)
POST /membership/webhooks/:id/regenerate-secretRuotare il segreto di firma; restituisce il nuovo valore una volta
DELETE /membership/webhooks/:idEliminare un webhook
GET /membership/webhooks/:id/deliveriesTentativi di consegna recenti per un webhook
GET /membership/webhooks/deliveries/:deliveryIdPayload completo e risposta per una consegna
POST /membership/webhooks/deliveries/:deliveryId/redeliverRimettere in coda una consegna

Catalogo degli eventi

I nomi degli eventi seguono il modello {entity}.{action}. Recupera l'elenco live da GET /membership/webhooks/events.

EventoSi attiva quando
person.createdUna persona viene aggiunta
person.updatedUn record di persona viene modificato
person.destroyedUna persona viene eliminata
household.createdUn nucleo familiare viene aggiunto
household.updatedUn nucleo familiare viene modificato
household.destroyedUn nucleo familiare viene eliminato
group.createdUn gruppo viene aggiunto
group.updatedUn gruppo viene modificato
group.destroyedUn gruppo viene eliminato
group.member.addedUna persona viene aggiunta a un gruppo
group.member.removedUna persona viene rimossa da un gruppo
donation.createdUna donazione viene registrata — inserimento manuale, online o transizione in sospeso → completamento
donation.updatedUn record di donazione viene modificato
attendance.recordedUna visita viene registrata (inserimento manuale o check-in)
session.createdUna nuova sessione di presenza viene creata (manualmente o automaticamente al primo check-in)
form.submission.createdUn modulo viene inviato
event.createdUn evento del calendario viene aggiunto
event.updatedUn evento del calendario viene modificato
event.destroyedUn evento del calendario viene eliminato

Formato del payload

Ogni consegna è un HTTP POST con un corpo JSON e questi intestazioni:

IntestazioneDescrizione
Content-TypeSempre application/json
X-B1-EventIl nome dell'evento, ad esempio person.created
X-B1-Delivery-IdId univoco per questo tentativo di consegna — usalo per deduplicare
X-B1-SignatureFirma HMAC-SHA256 del corpo grezzo (vedi sotto)
X-B1-TimestampUnix epoch secondi quando la richiesta è stata inviata
User-AgentB1-Webhooks/1.0

Il corpo avvolge la risorsa modificata in un piccolo involucro:

{
"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" }
}
}

Per gli eventi *.destroyed, data contiene solo id e churchId del record eliminato.

Tipi di connettore

Il formato di consegna predefinito è l'involucro JSON sopra — connectorType: "standard". Per Slack e Discord lo stesso motore webhook invece pubblica un messaggio di chat che questi servizi accettano direttamente:

connectorTypeCorpo inviatoUsa quando
"standard" (predefinito)Involucro {event, churchId, occurredAt, data}, firmatoStai scrivendo la tua integrazione, oppure puntando a Zapier / Make / un server personalizzato
"slack"{ "text": "💝 New donation: $50.00" }Stai pubblicando direttamente a un URL Webhook in arrivo Slack
"discord"{ "content": "💝 New donation: $50.00" }Stai pubblicando direttamente a un URL webhook del canale Discord

Il tipo di connettore viene impostato nel menu a discesa Tipo di connettore nell'editor webhook, o tramite connectorType nel corpo POST /membership/webhooks. L'intestazione firmata X-B1-Signature viene comunque inviata per le consegne Slack/Discord (le ignorano innocuamente), quindi passare un webhook a standard in seguito non richiede una nuova firma.

Consegne di prova

Ogni editor webhook ha un pulsante Invia evento di prova — la chiamata API corrispondente è POST /membership/webhooks/:id/test. La route di prova costruisce un payload sintetico per il primo evento sottoscritto, lo invia sincronamente attraverso il percorso di consegna firmato reale (e attraverso formatForConnector per Slack/Discord), e restituisce la riga di consegna risultante incluso responseStatus e responseBody. Usalo per confermare la connettività e la gestione della firma prima di attivare l'integrazione per davvero.

Verifica delle firme

Verifica sempre X-B1-Signature prima di fidarti di un payload. La firma è sha256= seguito dal valore esadecimale HMAC-SHA256 del corpo della richiesta grezzo codificato con il tuo segreto di firma. Calcola su i byte che hai ricevuto — non ri-serializzare il JSON analizzato.

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 ?? "");
}

Rifiuta qualsiasi richiesta la cui firma non corrisponde. Facoltativamente, rifiuta anche le richieste il cui X-B1-Timestamp è più vecchio di pochi minuti per limitare le finestre di replay.

Supporto SDK

Per Node.js, @churchapps/integration-sdk fornisce un verificatore tipizzato e un middleware Express che gestisce la cattura del corpo grezzo, il controllo della firma e l'analisi dell'involucro per te:

import express from "express";
import { b1WebhookMiddleware } from "@churchapps/integration-sdk";

const app = express();
// Cattura il corpo grezzo prima dell'analisi JSON — necessario affinché la firma continui a verificarsi.
app.use(express.json({ verify: (req, _res, buf) => { (req as any).rawBody = buf; } }));

app.post("/webhooks/b1", b1WebhookMiddleware({ secret: process.env.B1_WEBHOOK_SECRET! }), (req, res) => {
const env = req.b1Webhook!;
switch (env.event) {
case "donation.created": console.log("new gift", env.data.amount); break;
}
res.sendStatus(200);
});

L'SDK espone anche WebhookVerifier.verify(secret, rawBody, signatureHeader) per runtime non-Express (funzioni serverless, Fastify, ecc.). Vedi il pacchetto su npm.

Consegna e tentativi

Il tuo endpoint dovrebbe rispondere con uno stato 2xx il più rapidamente possibile — idealmente dopo solo aver messo in coda il lavoro, non dopo averlo elaborato. Qualsiasi risposta non-2xx, un errore di connessione o una risposta più lenta di 10 secondi conta come una consegna non riuscita.

Le consegne non riuscite vengono ritentate con backoff esponenziale — 16 tentativi in circa 5 giorni. L'intervallo cresce da 1 minuto, attraverso ore, fino a intervalli di 3 giorni per i tentativi finali. Dopo il 16° tentativo non riuscito la consegna è contrassegnata come exhausted e abbandonata.

La consegna è almeno una volta: una consegna può arrivare più di una volta (ad esempio, se il tuo endpoint ha successo ma la risposta va persa). Usa l'intestazione X-B1-Delivery-Id per deduplicare — elabora ogni id una sola volta e tratta i duplicati come no-op.

Disabilitazione automatica

Se un webhook produce tre consegne esaurite consecutive, B1 lo disabilita automaticamente. Correggi il tuo endpoint, quindi riabilita il webhook in B1Admin (o tramite POST /membership/webhooks con "active": true).

Ispezione e rimessa

L'editor webhook in B1Admin mostra una tabella Consegne recenti — evento, stato, numero di tentativi, codice di risposta e timestamp. Selezionando una riga viene rivelato il payload completo che è stato inviato e la risposta che è tornata.

Usa Rimetti per rimettere in coda qualsiasi consegna passata con il suo payload originale — utile dopo aver corretto un bug nel tuo endpoint, o per retroattivare gli eventi che il tuo endpoint ha perso mentre era inattivo.

Requisiti dell'URL

Poiché gli URL webhook sono forniti dalla chiesa, B1 applica protezioni contro la falsificazione di richieste lato server. Un URL webhook viene rifiutato — alla registrazione e ricontrollato prima di ogni consegna — se:

  • non utilizza https
  • punta a localhost, un nome host .local / .internal, o
  • si risolve in un indirizzo IP privato, loopback, link-local o metadati cloud

Il tuo endpoint deve essere un servizio HTTPS pubblicamente raggiungibile.