Pular para o conteúdo principal

Webhooks

Webhooks permitem que uma igreja envie notificações em tempo real para ferramentas de terceiros — plataformas de automação (Zapier, Make, n8n), CRMs, sistemas de contabilidade ou qualquer coisa que aceite um POST HTTP. Quando uma pessoa, grupo ou família muda em B1, B1 envia um payload JSON assinado para cada URL inscrita naquele evento.

Antes de Começar

  • Um administrador da igreja com permissão Editar Configurações da Igreja registra e gerencia webhooks
  • Seu endpoint receptivo deve estar acessível sobre HTTPS em um endereço público
  • Tenha uma maneira de armazenar o segredo de assinatura com segurança — ele é mostrado apenas uma vez

Visão Geral

Webhooks são apenas saída: B1 chama seu endpoint, você não chama B1. Cada webhook é uma inscrição por igreja consistindo em uma URL de destino, um segredo de assinatura e uma lista de eventos inscritos.

A entrega usa uma caixa de saída durável: quando um evento inscrito ocorre, B1 registra uma linha de entrega e um worker de fundo POSTs dentro de cerca de um minuto. Entregas falhadas são retentadas com backoff exponencial. Nada é perdido se uma entrega é lenta ou seu endpoint está brevemente inativo.

Registrando um Webhook

Em B1Admin

Vá para Configurações → Desenvolvedor → Webhooks → Novo Webhook. Digite um nome, a URL de payload e selecione os eventos para inscrever. Ao salvar, o segredo de assinatura é exibido uma vez — copie-o imediatamente e armazene-o com sua integração. Nunca é mostrado novamente (você pode girá-lo mais tarde, mas não pode recuperar o original).

Via a API

Todos os endpoints estão sob o caminho base do módulo Membership /membership/webhooks e requerem ou um JWT de um administrador da igreja com permissão Settings / Edit, ou uma chave de API criada com escopo settings:write. As mesmas rotas aceitam ambas. Isso é o que permite Zapier e Make registrem webhooks em nome da igreja quando um Zap ou cenário é ligado.

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

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

A resposta de criação — e apenas a resposta de criação — inclui o secret:

{
"id": "a1b2c3d4e5f",
"name": "Zapier — novos membros",
"url": "https://hooks.zapier.com/hooks/catch/123/abc",
"events": ["person.created", "person.updated", "group.member.added"],
"active": true,
"secret": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822c"
}
Método e CaminhoPropósito
GET /membership/webhooksListar os webhooks da igreja (segredo omitido)
GET /membership/webhooks/eventsO catálogo de nomes de eventos válidos
GET /membership/webhooks/:idCarregue um webhook
POST /membership/webhooksCriar (sem id) ou atualizar (com id)
POST /membership/webhooks/:id/regenerate-secretGirar o segredo de assinatura; retorna o novo valor uma vez
DELETE /membership/webhooks/:idDeletar um webhook
GET /membership/webhooks/:id/deliveriesTentativas de entrega recentes para um webhook
GET /membership/webhooks/deliveries/:deliveryIdPayload completo e resposta para uma entrega
POST /membership/webhooks/deliveries/:deliveryId/redeliverRe-fila uma entrega

Catálogo de Eventos

Nomes de eventos seguem o padrão {entity}.{action}. Busque a lista ao vivo de GET /membership/webhooks/events.

EventoDispara quando
person.createdUma pessoa é adicionada
person.updatedUm registro de pessoa é mudado
person.destroyedUma pessoa é deletada
household.createdUma família é adicionada
household.updatedUma família é mudada
household.destroyedUma família é deletada
group.createdUm grupo é adicionado
group.updatedUm grupo é mudado
group.destroyedUm grupo é deletado
group.member.addedUma pessoa é adicionada a um grupo
group.member.removedUma pessoa é removida de um grupo
donation.createdUm presente é registrado — entrada manual, online ou a transição pendente → completo
donation.updatedUm registro de doação é editado
attendance.recordedUma visita é registrada (entrada manual ou check-in)
session.createdUma nova sessão de frequência é criada (manualmente ou automática no primeiro check-in)
form.submission.createdUm formulário é enviado
event.createdUm evento de calendário é adicionado
event.updatedUm evento de calendário é editado
event.destroyedUm evento de calendário é deletado

Formato de Payload

Cada entrega é um HTTP POST com um corpo JSON e esses cabeçalhos:

CabeçalhoDescrição
Content-TypeSempre application/json
X-B1-EventO nome do evento, por ex. person.created
X-B1-Delivery-IdId único para esta tentativa de entrega — use para deduplicar
X-B1-SignatureAssinatura HMAC-SHA256 do corpo bruto (veja abaixo)
X-B1-TimestampSegundos de época Unix quando o pedido foi enviado
User-AgentB1-Webhooks/1.0

O corpo envolve o recurso mudado em um pequeno envelope:

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

Para eventos *.destroyed, data contém apenas o id e churchId do registro deletado.

Eventos cujos payloads referenciam outros registros por id também carregam nomes legíveis para humanos, resolvidos no tempo de entrega: personName e groupName nos eventos de associação de grupo, personName em frequência, doação e eventos de associação de lista, groupName em session.created e formName (mais personName quando o envio é vinculado a uma pessoa) em form.submission.created.

Tipos de Conector

O formato de entrega padrão é o envelope JSON acima — connectorType: "standard". Para Slack e Discord o mecanismo de webhook em vez disso posta uma mensagem em formato de chat que esses serviços aceitam diretamente:

connectorTypeCorpo enviadoUse quando
"standard" (padrão){event, churchId, occurredAt, data} envelope, assinadoVocê está escrevendo sua própria integração, ou apontando para Zapier / Make / um servidor customizado
"slack"{ "text": "💝 Nova doação: $50.00" }Você está postando direto para uma URL de Webhook Incoming do Slack
"discord"{ "content": "💝 Nova doação: $50.00" }Você está postando direto para uma URL webhook de canal Discord
"mailchimp"n/a — o conector chama a API do Mailchimp em siVocê quer sincronização de público sem URL para hospedar

O tipo de conector é definido no dropdown Tipo de Conector no editor webhook, ou via connectorType no corpo POST /membership/webhooks. O cabeçalho assinado X-B1-Signature ainda é enviado para entregas Slack/Discord (eles o ignoram inofensivamente), então voltar um webhook para standard mais tarde não requer re-assinatura.

Slack e Discord são puras reformulações de corpo — o mecanismo ainda POSTs para a URL fornecida pela igreja. mailchimp é o primeiro conector que em vez disso possui sua troca HTTP: por evento ele emite pedidos de upsert/archive/tag autenticados contra a API do Mailchimp (MailchimpConnector.deliver), e suas credenciais ({apiKey, audienceId}) são armazenadas AES-criptografadas em webhooks.connectorConfig, apenas escrita através da API. Webhooks Mailchimp aceitam apenas eventos de pessoa, membro de grupo e membro de lista; a rota de salvamento verifica a chave e público contra Mailchimp antes de aceitar. Linhas de entrega armazenam o envelope padrão, para que o log de entrega mostre o que B1 viu ao lado da resposta do Mailchimp. Situações não mapeadas (pessoa sem email, evento sem mapeamento) são concluídas como bem-sucedidas com um corpo de resposta Skipped: em vez de queimar retentativas.

Entregas de Teste

Cada editor de webhook tem um botão Enviar Evento de Teste — a chamada de API correspondente é POST /membership/webhooks/:id/test. A rota de teste constrói um payload sintético para o primeiro evento inscrito, despacha-o sincronamente através do caminho de entrega real assinado (e através de formatForConnector para Slack/Discord) e retorna a linha de entrega resultante incluindo responseStatus e responseBody. Use-a para confirmar conectividade e manipulação de assinatura antes de ligar a integração para real. Para webhooks mailchimp o teste em vez disso verifica as credenciais armazenadas contra a API do Mailchimp (um evento sintético escreveria um assinante falso no público real da igreja) e retorna um resultado em formato de entrega sem criar uma linha.

Verificando Assinaturas

Sempre verifique X-B1-Signature antes de confiar um payload. A assinatura é sha256= seguido pelo hex HMAC-SHA256 do corpo da solicitação bruta codificado com seu segredo de assinatura. Compute-a sobre os bytes que você recebeu — não re-serialize o JSON analisado.

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

Rejeite qualquer solicitação cuja assinatura não corresponda. Opcionalmente também rejeite solicitações cujo X-B1-Timestamp tem mais de alguns minutos para limitar janelas de reprodução.

Suporte a SDK

Para Node.js, @churchapps/integration-sdk envia um verificador digitado e um middleware Express que manipula a captura de corpo bruto, verificação de assinatura e análise de envelope para você:

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

const app = express();
// Capture o corpo bruto antes do parse JSON — necessário para que a assinatura ainda se verifique.
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("novo presente", env.data.amount); break;
}
res.sendStatus(200);
});

O SDK também expõe WebhookVerifier.verify(secret, rawBody, signatureHeader) para runtimes não-Express (funções serverless, Fastify, etc.). Veja o pacote no npm.

Entrega e Retentativas

Seu endpoint deve responder com um status 2xx o mais rápido possível — idealmente depois de apenas filar o trabalho, não depois de processá-lo. Qualquer resposta não-2xx, uma falha de conexão ou uma resposta mais lenta que 10 segundos conta como uma entrega falhada.

Entregas falhadas são retentadas com backoff exponencial — 16 tentativas ao longo de aproximadamente 5 dias. O intervalo cresce de 1 minuto, através de horas, até gaps de 3 dias para as tentativas finais. Após a 16ª tentativa falhada a entrega é marcada exhausted e abandonada.

A entrega é pelo menos uma vez: uma entrega pode chegar mais de uma vez (por exemplo, se seu endpoint tem êxito mas a resposta é perdida). Use o cabeçalho X-B1-Delivery-Id para deduplicar — processe cada id apenas uma vez e trate repeats como no-ops.

Auto-desativação

Se um webhook produzir três entregas consecutivas esgotadas, B1 o desativa automaticamente. Corrija seu endpoint, então reative o webhook em B1Admin (ou via POST /membership/webhooks com "active": true).

Inspecionando e Re-entregando

O editor de webhook em B1Admin mostra uma tabela de Entregas Recentes — evento, status, contagem de tentativa, código de resposta e timestamp. Selecionar uma linha revela o payload completo que foi enviado e a resposta que voltou.

Use Re-entregar para re-fila qualquer entrega passada com seu payload original — útil após corrigir um bug em seu endpoint, ou para preencher retroativamente eventos que seu endpoint perdeu enquanto estava inativo.

Requisitos de URL

Porque URLs de webhook são fornecidas pela igreja, B1 aplica guardas contra falsificação de solicitação do lado do servidor. Uma URL de webhook é rejeitada — no registro e re-verificada antes de cada entrega — se:

  • não usa https
  • aponta para localhost, um hostname .local / .internal, ou
  • resolve para um IP privado, loopback, link-local ou cloud-metadata

Seu endpoint deve ser um serviço HTTPS publicamente acessível.