Developer guide
This page is for whoever builds your church’s own website or automations. HolyCRM offers two ways to connect:
- Public data feeds: your website reads your events, small groups, service times and ministries from HolyCRM, always up to date.
- Webhooks: HolyCRM sends announcements and what happens in your church (new visitors, members, prayer requests, registrations, serving answers) to an address of yours.
Both are part of the Pro plan, and only an Administrator can turn them on, in Settings → Integrations.
Public data feeds
Section titled “Public data feeds”Turning them on
Section titled “Turning them on”In Integrations → Public data feeds, an Administrator ticks the feeds to share and clicks Save. The address of each feed appears under it, with a Copy button. Untick a feed and save to stop it at once.
Each feed carries exactly what the church’s HolyCRM website shows, never more. Member records, children, notes, addresses of homes and group hosts are never included.
Addresses
Section titled “Addresses”Every church has its own addresses, built from its HolyCRM id (the screen shows them in full):
| Address | What it returns |
|---|---|
https://pb.holycrm.app/backend/api/public/feeds/<id> |
The church’s name and time zone, and the address of every feed that is on |
…/feeds/<id>/events |
Upcoming events |
…/feeds/<id>/groups |
Small groups marked “Show on the website” |
…/feeds/<id>/services |
Weekly service times marked “Show on the website” |
…/feeds/<id>/ministries |
Active ministries |
They need no key or login, and they can be called straight from a web page on any site (cross-origin requests are allowed). Responses may be cached for 5 minutes, so a change in HolyCRM shows up on your site within a few minutes.
What each feed returns
Section titled “What each feed returns”Every feed returns JSON with the church and a list named after the feed:
{ "church": { "name": "Grace Community Church", "timezone": "America/Chicago" }, "events": [ … ]}A field with nothing in it is null.
Events (events), soonest first:
| Field | |
|---|---|
id |
The event’s id |
title |
Name of the event |
starts_at |
Start, in UTC: "2026-11-01 15:00:00.000Z" |
location_place |
Where it is, as typed in the event |
description |
The public description for the website |
photo_url |
Full address of the website photo |
registration |
true when visitors can sign up on the church’s HolyCRM website |
It takes two optional settings: ?limit= how many events (1 to 100, default 20) and
?days= how far ahead (1 to 365 days, default 90). For example
…/events?limit=5&days=30.
Small groups (groups), by name:
| Field | |
|---|---|
id |
The group’s id |
name |
Name of the group |
description |
The public description for the website |
meeting_day |
mon, tue, wed, thu, fri, sat or sun |
meeting_time |
Time as typed, local to the church, e.g. "19:30" |
Service times (services), Sunday first:
| Field | |
|---|---|
title |
Name of the activity, e.g. “Sunday service” |
weekday |
sun … sat |
time |
Local time at the church, e.g. "10:00" |
duration_minutes |
Length, when set |
place |
Name of the location, when set |
Ministries (ministries), by name:
| Field | |
|---|---|
id |
The ministry’s id |
name |
Name of the ministry |
min_age, max_age |
Age range, when set (for children’s and youth ministries) |
description |
The public description for the website |
photo_url |
Full address of the website photo |
Times and time zones
Section titled “Times and time zones”Event start times (starts_at) are in UTC. Service and group times are wall-clock
times at the church. Show both in the church’s time zone, which comes in
church.timezone (for example America/Argentina/Buenos_Aires).
When a feed answers “not found”
Section titled “When a feed answers “not found””A feed answers 404 {"error": "not found"} when it is turned off, when the address has a
wrong id, or when the church is no longer on the Pro plan. A church that comes back to Pro
gets its feeds back at the same addresses.
Example
Section titled “Example”A list of the next five events on any web page:
<ul id="events"></ul><script> const FEED = "https://pb.holycrm.app/backend/api/public/feeds/YOUR_CHURCH_ID/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(undefined, { 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 (not innerHTML) for anything that comes from a feed, as above.
Webhooks
Section titled “Webhooks”Setting up a webhook, choosing what it receives and the ready-made formats for ntfy and Telegram are explained in Integrations. What matters to a developer:
- Each delivery is a
POSTwith a JSON body (or your custom format).X-HolyCRM-Eventsays what happened, for examplevisitor.created. X-HolyCRM-Deliverystays the same when a delivery is retried, so you can ignore duplicates.- Every request is signed.
X-HolyCRM-Signatureissha256=followed by the HMAC-SHA256 of<X-HolyCRM-Timestamp>.<raw body>, keyed with your webhook’s signing secret. - Answer with any 2xx status. Timeouts (15 seconds), 408, 429, 5xx and network errors are tried again (after 30 seconds, 2, 8 and 32 minutes, then hourly, 6 attempts in all). Any other answer, including a redirect, fails at once: redirects are never followed.
With the default format the body looks like this (data depends on the event; summary
is a ready-made line in the church’s language):
{ "event": "visitor.created", "version": 1, "id": "k3j2h1g0f9e8d7c", "created_at": "2026-10-08T14:00:00.000Z", "church": { "id": "abc123def456ghi", "name": "Grace Community Church" }, "data": { "first_name": "John", "last_name": "Smith", "phone": "…", "summary": "New visitor: John Smith" }}Checking the signature in Node.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; // older than 5 minutes 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 the raw request body, before any JSON parsing.
Roadmap
Section titled “Roadmap”There is no API with keys for reading or changing member data. If your church needs one, tell us what you’d use it for through the contact form on holycrm.app.