Webhooks
Inbound — that we receive
Genius Checkout
URL: POST /functions/v1/gc-webhook
GC posts events for every subscription state change (created, charged, paused, cancelled, payment_failed). Each post carries:
X-GC-Signature— HMAC-SHA256 over the raw body using your org'swebhook_secret.Idempotency-Key— GC retries unsigned 5xx responses for ~24 hours, so guard against duplicates on your side.
We verify the signature, look up the matching recurring_donations row by gateway_subscription_id, and update its state. The full payload is logged to donation_payment_webhook_log so you can replay if a write fails.
WAHA / WhatsApp inbound replies
URL: POST /functions/v1/wacrm-webhook
WAHA posts JSON for every inbound WhatsApp message + delivery receipt. We normalize the phone number (digits-only, no +), match it against members.primary_phone, and append the message to the existing conversation thread (or create a new one).
PowerTranz refunds + notifications
URL: POST /functions/v1/powertranz-webhook
Used for asynchronous refund confirmations + recurring-payment notifications. Same HMAC pattern as GC.
Outbound — that you can configure
Per-event webhooks
Go to Settings → Integrations → Webhooks and add a URL. Pick from:
| Event | Fires when |
|---|---|
member.created | A new member is added (manually, bulk, or via visitor form) |
member.lost | A member is marked lost |
donation.recorded | A donation row is inserted (manual or online) |
attendance.marked | An attendance row is inserted |
workflow.completed | A workflow run finishes |
Each post includes:
X-GCM-Signature— HMAC-SHA256 of the body using a secret you set.- A JSON body with the affected row + delta.
Zapier
We ship a Zapier integration (search "Genius Church Manager" in Zapier). Use it for low-code automations. Under the hood it's the same outbound webhook endpoint.
Verifying signatures
Pseudocode (the same algorithm works in Node, Python, PHP, Go):
expected = HMAC_SHA256(secret, raw_body).hex()
got = request_header('X-GCM-Signature')
if not constant_time_compare(expected, got):
return 401WARNING
Don't use == — timing-safe comparison only. Stripe and Slack both publish more detailed guides, both apply here.
Retries
We retry failed posts (status ≠ 2xx, or no response in 10s) with exponential backoff up to 24 hours, then mark the delivery as permanently_failed. Check delivery history under Settings → Integrations → Webhook Logs.
