Skip to content

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's webhook_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:

EventFires when
member.createdA new member is added (manually, bulk, or via visitor form)
member.lostA member is marked lost
donation.recordedA donation row is inserted (manual or online)
attendance.markedAn attendance row is inserted
workflow.completedA 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):

text
expected = HMAC_SHA256(secret, raw_body).hex()
got = request_header('X-GCM-Signature')
if not constant_time_compare(expected, got):
    return 401

WARNING

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.