Webhook

Event delivery to your endpoint

Roster POSTs each subscribed event to your registered endpoint, signed with the endpoint's whsec_ secret:

Roster-Signature: t={unix_seconds},v1={hex hmac_sha256("{t}.{raw_body}", secret)}

Verify by recomputing the HMAC over {t}.{raw body} and comparing in constant time; reject t older than 300 seconds even when the HMAC matches.

Delivery is at-least-once with limited retries — deduplicate by event_id, and use GET /v1/events as the reconciliation source of truth. Answer 2xx quickly; anything else (or a timeout) counts as failure and triggers a retry, which may re-deliver events you already accepted.

Headers
  • Roster-Signature
    Type: string
    required

    t={unix_seconds},v1={hmac_sha256("{t}.{body}", secret)} — reject timestamps older than 300 seconds.

Body·
required
application/json

Event envelope — identical on the pull feed and on webhook deliveries. payload carries only the allowlisted fields of each type (for example enrollment.created{enrollment_id, church_id, mode, value}; enrollment.activated{enrollment_id}).

  • church_id
    Type: string
    required
  • event_id
    Type: string
    required

    Globally unique — the deduplication key.

  • event_type
    Type: string · enum
    required

    Event types visible to the partner (feed and webhooks).

    values
    • partner_session.created
    • partner_session.opened
    • partner_session.completed
    • partner_session.expired
    • church.created
  • occurred_at
    Type: string Format: date-time
    required

    Business time of the event.

  • payload
    Type: object
    required

    Field-allowlisted projection, per event type.

Responses
  • 200

    Return any 2xx to acknowledge the delivery.

Request Example for postpartnerEvent
{
  "event_id": "",
  "event_type": "partner_session.created",
  "occurred_at": "",
  "church_id": "",
  "payload": {}
}
No Body