Stripe webhooks
Stripe sends donation, payout, and subscription events to Ministrium via webhook. This page explains how those are processed and how to verify the signature if you choose to consume webhooks from Ministrium to your backend.
Stripe → Ministrium webhooks (managed)
When you connect Stripe (via Stripe Connect), Ministrium automatically creates an endpoint in your Stripe account:
https://api.ministrium.com/v1/webhooks/stripe/<tenant>Subscribed events: see Stripe Connect → Webhooks.
These webhooks are internal — you don’t configure them. They let Ministrium react to Stripe changes in real time.
Ministrium → your backend webhooks (outbound)
If you want Ministrium to notify your backend when something happens (donation, new member, etc.):
- Settings → API → Webhooks → New.
- Endpoint URL (HTTPS required).
- Events to subscribe to (multiple allowed).
- Save → Ministrium generates a signing secret
whsec_....
Available events
member.created member.updated member.deleted
prospect.created prospect.converted
attendance.recorded
donation.created donation.refunded donation.disputed
recurring.created recurring.updated recurring.canceled recurring.charge_failed
event.created event.registration event.canceled
group.created group.multiplied group.closed
prayer_request.created prayer_request.answered
campus.created
tag.added tag.removed
device.registeredPayload structure
{
"id": "evt_xyz789",
"type": "donation.created",
"created": 1714165800,
"tenant": "la-roca",
"api_version": "2026-01-01",
"data": {
"object": {
"id": "don_abc123",
"amount": 50000,
"currency": "MXN",
"fund": { "id": "fnd_tithes", "name": "Tithes" },
"donor": { "id": "mem_456", "first_name": "John" },
"campus": { "id": "cmp_downtown", "name": "Downtown" },
"created_at": "2026-04-26T14:30:00Z"
}
}
}tenant always present to identify which church it belongs to (useful if you operate for several).
Signature verification
Ministrium signs each webhook with HMAC-SHA256:
Headers:
Ministrium-Signature: t=1714165800,v1=abcd1234...Node.js verification:
import crypto from 'crypto';
function verify(payload, header, secret) {
const [tPart, vPart] = header.split(',');
const timestamp = tPart.split('=')[1];
const signature = vPart.split('=')[1];
// 5-minute tolerance to prevent replay
if (Math.abs(Date.now()/1000 - timestamp) > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${payload}`)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}> Important: use the raw body (not the parsed JSON) to compute the signature.
Retries and idempotency
If your endpoint doesn’t respond 2xx in < 3 s, Ministrium retries with backoff (see Rate limits).
Each event has a unique id. Your backend must be idempotent: store processed IDs and discard duplicates.
if (await db.events.exists({ id: payload.id })) return res.status(200).end();
await db.events.insert({ id: payload.id, ... });Replay and backfill
In Settings → API → Webhooks → [Endpoint] you can:
- Resend a single event (useful if your backend had a bug).
- Backfill a date range (all events of type X between date A and B).
You can filter the send to not receive everything. E.g.: only donation.created with amount > 100000 (≥ $1,000).
Best practices
- Always verify the signature. Replay attacks are real.
- Respond
200quickly and process async. - Store the raw event before processing (debugging).
- Monitor failures: if your endpoint goes down, webhooks deactivate after 5 retries.
- Have a health-check endpoint (
GET /webhooks/ping) Ministrium can call to validate before sending.