⚡ Skip ahead — 9 ready-to-build integration templates
Mailchimp welcome list, donation → Google Sheet, Slack visitor alerts, pastoral dashboard, monthly giving summary, and more. Most take five minutes in Zapier.
Open the template gallery →
Getting started
- Upgrade to Kingdom (or buy the £19/month API access add-on on Sprout/Grow/Thrive) from Settings → Billing.
- Visit Settings → API keys and create a key. Copy the secret — you'll only see it once.
- Make your first call:
curl https://yourchurch.churchlinker.com/api/v1/people \ -H "Authorization: Bearer cl_live_..."Substitute
yourchurch.churchlinker.comwith your own subdomain or your custom domain.
Authentication
Every request needs an Authorization: Bearer cl_live_... header. Keys are tied to your tenant; they can't see or modify data outside your church. A key keeps working until you revoke it on Settings → API keys.
Treat keys like passwords. Anyone with one can act as your church via the API. Store them in your integration's secrets manager, not in source control or chat tools.
Per-resource scopes
By default, a key has full access — read and write every resource. To follow least-privilege practice (for example, when you give a key to a third-party Zap or contractor) pick specific scopes at creation time:
people:read·people:writeevents:read·events:writedonations:read(donations are read-only via the API)groups:read·groups:writesermons:read(sermons are read-only)
Scopes are fixed at creation — to change a key's scopes, issue a new key and migrate. A scoped key calling an operation it doesn't have permission for returns 403 with error.type = forbidden_scope and the missing scope name in the message.
Conventions & errors
- Content type: JSON request bodies for POST and PATCH. All responses are JSON.
- Dates: always ISO 8601 strings (e.g.
2026-05-20T09:30:00Z). - Pagination: list endpoints return
{ data: [...], pagination: { count, hasMore, nextCursor } }. Pass?cursor=...&limit=...on the next request. Default limit 50, max 100. - Errors: non-2xx responses return
{ error: { type, message } }. Common error types:unauthorized(401) — missing or bad keyplan_required(402) — tenant lost API accesstenant_inactive(403) — account suspendedforbidden_scope(403) — key doesn't have the scope this endpoint requiresnot_found(404) — resource ID doesn't exist in your tenantmissing_field,invalid_body,invalid_date,no_changes(400)duplicate(409) — unique constraintrate_limited(429) — retry afterRetry-Afterheader seconds
Rate limits
- Kingdom and API add-on: 600 requests/hour per key
- Enterprise: 5,000 requests/hour per key
Hit the limit and you get a 429 response with a Retry-After header (seconds until you can try again). Generate additional keys to scale read fan-out if needed; each gets its own bucket.
/v1/people
CRUD for the people directory. List, get one, create, update.
List
GET /api/v1/people?status=MEMBER&search=jane&limit=25
Authorization: Bearer cl_live_…Query params: status (filter by membershipStatus), search (firstName / lastName / email contains), limit, cursor.
Create
POST /api/v1/people
{
"firstName": "Jane",
"lastName": "Smith",
"email": "jane@example.org",
"mobilePhone": "+447700900123",
"membershipStatus": "MEMBER",
"dateOfBirth": "1985-04-12"
}Required: firstName, lastName. Returns 201 with the full record. Fires the person.created webhook.
Update
PATCH /api/v1/people/clxxxx...
{ "mobilePhone": "+447700900456" }Partial. Only fields you send are touched. Fires person.updated.
/v1/events
CRUD on events. GET /api/v1/events?upcoming=true returns only events whose startDate is in the future.
POST /api/v1/events
{
"title": "Sunday service",
"startDate": "2026-06-07T10:00:00Z",
"endDate": "2026-06-07T11:30:00Z",
"location": "Main hall",
"isPublic": true,
"showOnApp": true
}Required: title, startDate, endDate. Fires event.created.
/v1/donations
Read-only. Donations only enter the system through Stripe Checkout (so our 0% platform-fee promise and Gift Aid maths stay honest). For real-time donation events, subscribe to the donation.created webhook.
GET /api/v1/donations?since=2026-05-01&status=COMPLETED&limit=100Filters: since (ISO date), status (COMPLETED / PENDING / REFUNDED), fundId.
/v1/groups
CRUD on small groups, ministries and teams. Mirror of the People shape: list with cursor pagination; create with name required; update partial. Fires group.created.
/v1/sermons
Read-only. Returns each sermon's metadata, speaker, series, passage, links to audio / video / slides, and (when generated) the AI summary + key points. Sermon writes go through the dashboard's upload + transcript flow.
GET /api/v1/sermons?public=true&limit=20Webhooks
Get notified when things happen in your church. Subscribe an HTTPS endpoint at Settings → Webhooks and we'll POST a signed JSON payload to it.
Event catalogue
person.created— fires when a new directory entry is added (via API, dashboard, or visitor → member conversion).person.updated— fires on any PATCH-style change to a person row.event.created— fires when an event is created (any source).event.attendance.recorded— fires when a check-in or attendance row is added.donation.created— fires from the Stripe webhook handler after a successful gift lands.group.created— fires when a small group / ministry is created.
Payload shape
POST https://your-endpoint.example.com/webhooks/churchlinker
Content-Type: application/json
User-Agent: ChurchLinker-Webhooks/1.0
X-ChurchLinker-Event: donation.created
X-ChurchLinker-Signature: t=1716123456789,v1=4f3a8...
{
"event": "donation.created",
"createdAt": "2026-05-20T07:30:56.123Z",
"data": {
"id": "clxxxx...",
"amount": 50.00,
"currency": "GBP",
"method": "CARD",
"status": "COMPLETED",
"giftAid": true,
"giftAidAmount": 12.50,
"donatedAt": "2026-05-20T07:30:55.000Z"
}
}Signature verification
Every delivery carries an X-ChurchLinker-Signature header of the form t=<unix-ms>,v1=<hmac>. The hmac is HMAC-SHA256(secret, "{t}.{body}") where body is the raw request body. Reject any signature where the timestamp is more than 5 minutes old (replay-attack defence).
// Node.js example
import crypto from 'crypto'
function verify(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(',').map(p => p.split('='))
)
const t = Number(parts.t)
const v1 = parts.v1
if (!t || !v1) return false
if (Math.abs(Date.now() - t) > 5 * 60 * 1000) return false
const expected = crypto
.createHmac('sha256', secret)
.update(t + '.' + rawBody)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(v1)
)
}Test firing
On Settings → Webhooks every webhook has a ⚡ Test fire button. Clicking it POSTs a synthetic event to your endpoint with data.test = true and a real HMAC signature. Returns the subscriber's HTTP status and response body inline, and records the attempt in the deliveries panel alongside real fires. Use it to verify your signature verification and 2xx-return logic before any real event depends on it.
Delivery semantics
- 3 attempts with 5s and 30s backoff. 8-second per-attempt timeout. Each attempt records a row in the deliveries panel.
- Success = HTTP 2xx response from your endpoint.
- Order is not guaranteed. Different events for the same resource can arrive out of order under load; use the
data.updatedAtfield to order client-side if it matters. - At-least-once delivery. Network blips can cause a delivery to be retried after your endpoint already returned 2xx. Idempotency on your side (e.g. dedupe on
data.id) is recommended.
Need something not listed? Open the live chat (bottom right) or email support@churchlinker.com with a description of your use case. We add endpoints based on real demand.
OpenAPI 3.1 specification
Machine-readable spec at /api/v1/openapi.json. Import into Postman or Insomnia for an instant working collection. Use as the spec URL when setting up a Zapier or Make custom integration. Compatible with AI assistant function-calling layouts (GPT actions, Claude tools).