# Guia para programadores

Esta página é para quem cria o site próprio da sua igreja ou as automações dela. O HolyCRM
oferece duas formas de ligação:

- **Dados públicos em JSON**: o seu site *lê* do HolyCRM os seus eventos, grupos,
  horários de culto e ministérios, sempre atualizados.
- **Webhooks**: o HolyCRM *envia* os comunicados e o que acontece na sua igreja (novos
  visitantes, membros, pedidos de oração, inscrições, respostas às escalas) para um
  endereço seu.

Os dois fazem parte do plano Pro, e só um Administrador pode ativá-los, em
**Definições → [Integrações](#/integrations)**.

## Dados públicos em JSON

### Como ativar

Em **Integrações → Dados públicos em JSON**, um Administrador marca as fontes que quer
partilhar e clica em **Guardar**. O endereço de cada fonte aparece abaixo dela, com um
botão **Copiar**. Desmarque uma fonte e guarde para a parar de imediato.

Cada fonte leva **exatamente o que o site do HolyCRM da igreja mostra, nunca mais**. Fichas
de membros, crianças, notas e moradas de casas ou anfitriões de grupos nunca são
incluídas.

### Endereços

Cada igreja tem os próprios endereços, montados com o id dela no HolyCRM (o ecrã mostra os
endereços completos):

| Endereço | O que devolve |
|---|---|
| `https://pb.holycrm.app/backend/api/public/feeds/<id>` | O nome e o fuso horário da igreja, e o endereço de cada fonte ativada |
| `…/feeds/<id>/events` | Próximos eventos |
| `…/feeds/<id>/groups` | Grupos marcados com "Mostrar no site" |
| `…/feeds/<id>/services` | Horários semanais marcados com "Mostrar no site" |
| `…/feeds/<id>/ministries` | Ministérios ativos |

Não precisam de chave nem de sessão iniciada, e podem ser chamados diretamente de uma página web em
qualquer site (pedidos de outra origem são permitidos). As respostas podem ficar em cache
5 minutos, por isso uma alteração no HolyCRM aparece no seu site em poucos minutos.

### O que cada fonte devolve

Cada fonte devolve JSON com a igreja e uma lista com o nome da fonte:

```json
{
  "church": { "name": "Igreja Graça", "timezone": "Europe/Lisbon" },
  "events": [ … ]
}
```

Um campo vazio é `null`.

**Eventos** (`events`), do mais próximo ao mais distante:

| Campo | |
|---|---|
| `id` | O id do evento |
| `title` | Nome do evento |
| `starts_at` | Início, em UTC: `"2026-11-01 15:00:00.000Z"` |
| `location_place` | Onde é, como foi escrito no evento |
| `description` | A descrição pública para o site |
| `photo_url` | Endereço completo da foto do site |
| `registration` | `true` quando os visitantes se podem inscrever no site do HolyCRM da igreja |

Aceita duas opções: `?limit=` quantos eventos (de 1 a 100, por omissão 20) e `?days=` quantos
dias à frente (de 1 a 365, por omissão 90). Por exemplo `…/events?limit=5&days=30`.

**Grupos** (`groups`), por nome:

| Campo | |
|---|---|
| `id` | O id do grupo |
| `name` | Nome do grupo |
| `description` | A descrição pública para o site |
| `meeting_day` | `mon`, `tue`, `wed`, `thu`, `fri`, `sat` ou `sun` |
| `meeting_time` | Hora como foi escrita, local da igreja, por exemplo `"19:30"` |

**Horários de culto** (`services`), começando pelo domingo:

| Campo | |
|---|---|
| `title` | Nome da atividade, por exemplo "Culto de domingo" |
| `weekday` | `sun` … `sat` |
| `time` | Hora local da igreja, por exemplo `"10:00"` |
| `duration_minutes` | Duração, quando indicada |
| `place` | Nome do local, quando indicado |

**Ministérios** (`ministries`), por nome:

| Campo | |
|---|---|
| `id` | O id do ministério |
| `name` | Nome do ministério |
| `min_age`, `max_age` | Faixa etária, quando indicada (para ministérios infantis e de jovens) |
| `description` | A descrição pública para o site |
| `photo_url` | Endereço completo da foto do site |

### Horários e fusos horários

O início dos eventos (`starts_at`) está em **UTC**. Os horários de cultos e grupos são a
hora **na igreja**. Mostre os dois no fuso horário da igreja, que vem em
`church.timezone` (por exemplo `Europe/Lisbon`).

### Quando uma fonte responde "não encontrado"

Uma fonte responde `404 {"error": "not found"}` quando está desativada, quando o endereço
tem um id errado ou quando a igreja não está mais no plano Pro. Se a igreja voltar ao Pro,
recupera as fontes nos mesmos endereços.

### Exemplo

Uma lista dos próximos cinco eventos em qualquer página web:

```html
<ul id="events"></ul>
<script>
  const FEED = "https://pb.holycrm.app/backend/api/public/feeds/ID_DA_SUA_IGREJA/events?limit=5";
  fetch(FEED)
    .then((r) => r.json())
    .then(({ church, events }) => {
      const list = document.getElementById("events");
      for (const ev of events) {
        const when = new Date(ev.starts_at.replace(" ", "T")).toLocaleString("pt-PT", {
          timeZone: church.timezone || undefined,
          weekday: "long", day: "numeric", month: "long", hour: "2-digit", minute: "2-digit",
        });
        const li = document.createElement("li");
        li.textContent = `${ev.title} · ${when}${ev.location_place ? " · " + ev.location_place : ""}`;
        list.appendChild(li);
      }
    });
</script>
```

Use `textContent` (não `innerHTML`) para tudo o que vier de uma fonte, como acima.

## Webhooks

Como configurar um webhook, escolher o que ele recebe e os formatos prontos para ntfy e
Telegram está explicado em [Integrações](#/integrations). O que importa a um
programador:

- Cada envio é um `POST` com corpo JSON (ou o seu formato à medida).
  `X-HolyCRM-Event` diz o que aconteceu, por exemplo `visitor.created`.
- `X-HolyCRM-Delivery` não muda quando um envio é repetido, por isso pode ignorar
  duplicados.
- **Cada pedido é assinado.** `X-HolyCRM-Signature` é `sha256=` seguido do HMAC-SHA256
  de `<X-HolyCRM-Timestamp>.<corpo bruto>`, com o segredo de assinatura do seu webhook como
  chave.
- Responda com qualquer status 2xx. Tempos esgotados (15 segundos), 408, 429, 5xx e erros
  de rede são repetidos (após 30 segundos, 2, 8 e 32 minutos, depois de hora a hora, 6
  tentativas no total). Qualquer outra resposta, inclusive um redirecionamento, falha de
  imediato: redirecionamentos nunca são seguidos.

Com o formato padrão o corpo fica assim (`data` depende do evento; `summary` é uma linha
pronta na língua da igreja):

```json
{
  "event": "visitor.created",
  "version": 1,
  "id": "k3j2h1g0f9e8d7c",
  "created_at": "2026-10-08T14:00:00.000Z",
  "church": { "id": "abc123def456ghi", "name": "Igreja Graça" },
  "data": { "first_name": "João", "last_name": "Silva", "phone": "…", "summary": "Novo visitante: João Silva" }
}
```

Como verificar a assinatura em Node.js:

```js
const crypto = require("crypto");
function verify(rawBody, headers, secret) {
  const ts = headers["x-holycrm-timestamp"];
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // mais de 5 minutos
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(ts + "." + rawBody).digest("hex");
  const got = String(headers["x-holycrm-signature"] || "");
  return got.length === expected.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
}
```

Use o corpo **bruto** do pedido, antes de interpretar o JSON.

## Ainda não disponível

Não há uma API com chaves para ler ou alterar dados de membros. Se a sua igreja precisar,
diga-nos para que a usaria pelo formulário de contato em
[holycrm.app](https://holycrm.app).
