Skip to main content
EcclesiaOne

Help centreFor developersWebhooks: tell your other systems when something happens

All articles

Webhooks: tell your other systems when something happens

Send a short signed message to your accounting package, mailing list or own software when a parishioner joins, a gift arrives, a request comes in or someone books an event.

For
Parish priest, Bishop or Archbishop
Updated
31 October 2026
Read
6 min
Sections
8

Who this page is for

This page is for whoever builds or looks after your other systems, such as your accounting package, mailing list or own software. If you're a parish priest, secretary or treasurer, you don't need it: pass it to the person who looks after those systems.

What webhooks do

A webhook is a web address (an "endpoint") that belongs to one of your other systems. You add it in the console and choose the events you care about. Each time one of those happens, EcclesiaOne sends a short signed message to that address, so the other system can react straight away and nobody has to copy data by hand.

Webhooks come with Pro for parishes and with every diocese plan. The parish priest sets them up for a parish, and the bishop for a diocese.

The events

| Event | When it is sent | Parish | Diocese | |---|---|---|---| | member.created | A person is added to the parish register by staff, or a self-registration is approved | Yes | No | | gift.received | An online gift is paid, or a recorded contribution is confirmed | Yes | Diocesan gifts only | | request.received | A parishioner sends a request | Yes | No | | request.updated | A request's status changes | Yes | No | | event_booking.created | Someone books a place at a parish event | Yes | No | | event_booking.updated | A booking changes or is cancelled | Yes | No |

A diocese receives events for its own diocesan gifts only, never a parish's events. Bulk imports of people do not send member.created; to copy your existing register into another system, use the API for a first sync.

Setting one up

  1. Open Settings, then Webhooks, then Add endpoint.
  2. Give it a name and paste the address. It must start with https://.
  3. Tick the events you want.
  4. If the other system needs a name or an email, open Also include under that event and tick only what it needs. You'll be asked to confirm that your parish (or diocese) is responsible for how those details are kept and used there.
  5. Save. The signing secret is shown once, so copy it into the other system straight away.

Testing

On the endpoint's page, choose an event and press Send test event. We send made-up data marked "test": true straight away and show you the answer code and how long it took. You can send 10 test events an hour to each endpoint.

What we never send

Whatever you switch on, we never send:

  • anything about a child: no event at all for a person under 18 (by birth date, a child account, or a son, daughter, ward or grandchild in a household with no birth date recorded), no booking made by or for a child, and never a child's name on a gift;
  • Confession times people have chosen, safeguarding, tribunal matters, private prayer intentions or Mass intention text;
  • requests for Holy Communion for the sick, and vocation enquiries;
  • request form answers, staff notes and replies, gift notes, card or bank details;
  • the donor's name or email on an anonymous gift.

By default a message carries only ids, plain facts and a link to the record in the console. The link opens only for someone signed in with access.

When we stop sending

If an endpoint keeps failing, we keep trying for a while. After a day of nothing but failures, we email you a warning. After three days (or 50 failed attempts in a row) we stop sending to it, cancel what was waiting, email you, and the page says: "We stopped sending to [name] because it has not answered since [date]." Fix the address, then press Switch back on. You can send events again from the delivery log one at a time, or with Resend all from the last 3 days.

If the address answers 410 Gone, we stop sending at once: that answer means it no longer wants these messages.

If your plan stops including webhooks, your endpoints are paused, not deleted.

Developer reference

The message

Every message is an HTTP POST with a JSON body of at most 16 KB:

{
  "id": "evt_clx...",
  "type": "gift.received",
  "apiVersion": "2026-10-15",
  "createdAt": "2026-10-15T09:12:44Z",
  "test": false,
  "tenant": { "kind": "parish", "id": "clp...", "name": "St Mary's, Bethabara" },
  "data": {
    "object": "gift",
    "id": "cld...",
    "url": "https://www.ecclesiaone.com/parishes/clp.../giving?donation=cld...",
    "amount": "50.00",
    "currency": "USD",
    "source": "online",
    "designation": "PROJECT_FUND",
    "fund": "Church roof",
    "receiptNo": "R-2026-00412",
    "paidAt": "2026-10-15T09:12:40Z",
    "fields": { "donorName": "Sam Taylor" }
  }
}

Amounts are strings. Named fields appear only under data.fields, and only when you switched them on.

Each event's data

  • member.created: object "member", id, url, createdAt. Named fields: givenNames, surname, email, phone.
  • gift.received: object "gift", id, url, amount, currency, source ("online" or "recorded"), designation, fund, receiptNo, paidAt (and confirmedAt for a recorded gift). Named fields: donorName, donorEmail.
  • request.received and request.updated: object "request", id, url, requestType, reference, status, createdAt; an update adds previousStatus and updatedAt. Named fields: requesterName, requesterEmail.
  • event_booking.created and event_booking.updated: object "event_booking", id, url, eventId, eventTitle, startAt, bookingStatus ("GOING", "MAYBE", "NOT_GOING" or "CANCELLED"). Named fields: attendeeName, attendeeEmail.

Headers

  • EcclesiaOne-Webhook-Id: the event id. It is the same on every retry, so drop a message whose id you have already handled.
  • EcclesiaOne-Webhook-Timestamp: Unix seconds when this attempt was sent.
  • EcclesiaOne-Webhook-Signature: v1= and the hex HMAC-SHA256 of timestamp.body, keyed with your secret. For 24 hours after you rotate the secret there are two values separated by a space, the new one first; accept either.
  • EcclesiaOne-Webhook-Event: the event type.
  • User-Agent: EcclesiaOne-Webhooks/1.

Check the signature on the raw body, before parsing it. Reject a timestamp more than five minutes from your clock, and remember event ids for a day.

A check value: with the secret whsec_test, the timestamp 1760000000 and the body {"id":"evt_1"}, the signature is 66e880d7175fffb43ce10c4e14db1cfb230c8804b5aafb116affbc9a836c7690.

Checking the signature in Node

const crypto = require("crypto");

function verify(secret, rawBody, headers) {
  const timestamp = Number(headers["ecclesiaone-webhook-timestamp"]);
  if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(timestamp + "." + rawBody).digest();
  return String(headers["ecclesiaone-webhook-signature"] || "")
    .split(" ")
    .filter((part) => part.startsWith("v1="))
    .some((part) => {
      const got = Buffer.from(part.slice(3), "hex");
      return got.length === expected.length && crypto.timingSafeEqual(got, expected);
    });
}

Checking the signature in PHP

function verify(string $secret, string $rawBody, array $server): bool {
    $timestamp = (int) ($server['HTTP_ECCLESIAONE_WEBHOOK_TIMESTAMP'] ?? 0);
    if ($timestamp === 0 || abs(time() - $timestamp) > 300) return false;
    $expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
    foreach (explode(' ', $server['HTTP_ECCLESIAONE_WEBHOOK_SIGNATURE'] ?? '') as $part) {
        if (str_starts_with($part, 'v1=') && hash_equals($expected, substr($part, 3))) return true;
    }
    return false;
}

$raw = file_get_contents('php://input');
if (!verify(getenv('ECCLESIAONE_WEBHOOK_SECRET'), $raw, $_SERVER)) { http_response_code(400); exit; }

Answering, retries and order

Answer with any 2xx code within 10 seconds; do slow work afterwards. Anything else, a timeout or a network error is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours: eight attempts over about 45 hours. We never follow redirects; a 3xx answer counts as a failure. We only send to public https:// addresses.

Delivery is at least once and the order is not guaranteed, so use the event id to drop duplicates and the timestamps in data to order things.

Versions

Each endpoint is pinned to the apiVersion it was created with. Adding events or fields is not a new version. Removing or renaming something is, and the old version is kept for 12 months with an Upgrade button on the endpoint's page.

Was this article helpful?

Read nextSetting up online giving

Still stuck?