Every event Folio sends to a webhook endpoint, with its fields, privacy class and a complete example body. Generated from the API.
57 events, payload version 2026-12-01. Subscribe an endpoint to any of them, or to * for all. Receiving, verifying and replaying them is in webhooks. Ignore fields you do not recognise: events gain fields without a new version.
The delivered body
Every delivery is one event. data.object is the full resource as it was when the event happened; data.previous_attributes appears on updates. The event id is the same on every retry and endpoint; the webhook-id header identifies one delivery.
Fields of the event envelope
Field
Type
Description
actor required
object | null
The team member who caused the event, null when a person did not (jobs, automations, Folio).
id required
string
The team member id.
object required
enum: user
Always user.
api_version required
string
The event schema version.
church
object
The church the event is about. Sent to partner endpoints.
id required
string
The church id.
name required
string
The church name.
object required
enum: church
Always church.
slug required
string
The church web address.
created required
string (date-time)
When the event happened.
data required
object
The event payload.
object required
object
The full resource as it was when the event happened, in the same shape the REST API returns it.
previous_attributes
object
On update events, the earlier values of the fields that changed.
id required
string
The event id. The same on every retry and on every endpoint that receives the event; use it to deduplicate. The webhook-id header identifies one delivery.
livemode required
boolean
False for sandbox churches, test keys and test events.
object required
enum: event
Always event.
ordering_key
string
A stable key per church; events with the same key are best processed in order.
partner
object
The partner that receives the event.
id required
string
The partner church id.
request required
object | null
The API request that caused the event, null when Folio caused it.
id required
string
The request id, the same value as the request_id in API responses.
idempotency_key required
string | null
The Idempotency-Key the caller sent, if any.
test
boolean
True on events sent from the test button or the test endpoint.
type required
string
The event type from the catalogue.
Example event
{
"actor": null,
"api_version": "2026-12-01",
"created": "2026-10-13T09:12:10Z",
"data": {
"object": {
"approval_kind": "human",
"approved_at": "2026-10-13T09:00:00Z",
"church": "Grace Place",
"created_at": "2026-10-13T09:12:10Z",
"failure_reason": "",
"id": "note_0192f7a8-3b1c-7d2e-9a40-5c6d7e8f9012",
"model_id": "gemini-2.5-pro",
"needs_person_reason": "",
"object": "note",
"prompt_version": "notes.v14",
"published_at": "2026-10-13T09:12:10Z",
"returned_reason": "",
"row_version": 5,
"sections": [
{
"body": "Paul reminds us that nothing in our hardest seasons is wasted.",
"h": "Called by purpose"
}
],
"share_path": "/s/romans-8-nothing-can-separate-us",
"share_slug": "romans-8-nothing-can-separate-us",
"speaker": "Pastor Ada Okafor",
"speaker_evidence": "",
"speaker_inferred": false,
"status": "published",
"summary": "God works all things together for good for those who love him.",
"teaching": {
"id": "tch_0192f7a8-3b1c-7d2e-9a40-5c6d7e8f9012",
"name": "Romans 8: Nothing can separate us",
"object": "teaching",
"recorded_at": "2026-10-11T10:00:00Z",
"speaker": "Pastor Ada Okafor",
"title": "Romans 8: Nothing can separate us"
},
"updated_at": "2026-10-13T09:12:10Z",
"version": 3,
"warnings": []
},
"previous_attributes": {
"status": "draft"
}
},
"id": "evt_0192f7a8-3b1c-7d2e-9a40-5c6d7e8f9012",
"livemode": false,
"object": "event",
"request": {
"id": "req_5f3c9a1e7b2d4c68a0e1f2d3c4b5a697",
"idempotency_key": null
},
"type": "note.published"
}
Privacy classes
Each event is marked with what it carries, so you can subscribe each endpoint to only what it needs. Carries content events carry what the REST read of the same resource returns. Metadata only events carry ids, kinds, states and times only. Account data events carry facts about the church, its keys and connections. Financial data events carry amounts in minor units and at most a card brand and last four digits. No event carries a secret, key value or token.
Verify the signature
Verify every delivery before you trust it. The signed content is <webhook-id>.<webhook-timestamp>.<raw body> and the signature is its base64 HMAC-SHA256. The key is the whole whsec_ secret string as bytes, not base64-decoded. The webhook-signature header holds one or two space-separated v1,<signature> values, two while a rotated secret overlaps: accept the request if any one matches. Compare in constant time, hash the raw body bytes rather than a re-serialised object, and reject a webhook-timestamp more than five minutes from your clock.
Deduplicate on the event id (evt_…). It is the same on every retry, on every endpoint and on a replay. The webhook-id header (msg_…) names one delivery: a retry reuses it and a replay gets a new one.
Deliveries about one church share an ordering_key, the church id, on partner endpoints. Sort by created within a key. Different churches have no order relative to each other, and a retry can arrive after a later event, so fetch the current state with the API when order matters.
Answer with any 2xx within the timeout and do the work afterwards. Any other answer, or a timeout, is retried on a backoff, and webhook-will-retry-after on a failed attempt gives the next delay in seconds. folio-attempt counts attempts.
An endpoint that fails 20 times in a row is disabled with disabled_reasonconsecutive_failures. Enable it again after you fix the receiver. A body over 256 KB is not sent and the delivery fails with payload too large.
Every attempt, with your receiver’s status, latency and the first 2 KB of its answer, is kept for 30 days. Replay a delivery with POST /v1/webhook-endpoints/{endpoint}/deliveries/{delivery}/replay. The replay carries the original event, unchanged.
Deletion and purged events
partner.church.deleted is sent twice for one church. The first has data.object.deletion.stage set to scheduled when deletion is requested, while the church can still cancel it. The second has purged once its data is removed, and carries only the summary captured before removal: no teachings, notes or people. Treat purged as the signal to delete what you hold for that church. The two share neither an event id nor an ordering guarantee beyond created.
output.unpublishedCarries contentA published output was taken back to review. Remove it from wherever you posted it. stale_reason says why when the source changed.
output.staleCarries contentThe note an output was written from changed, was returned or was replaced, so the output may no longer match it. stale_reason says which.
Example: output.createdExample output.created body
{
"actor": null,
"api_version": "2026-12-01",
"created": "2026-10-13T09:12:10Z",
"data": {
"object": {
"body_md": "The Spirit leads gently, and we follow.",
"changes_requested": false,
"id": "out_0192f7a8-3b1c-7d2e-9a40-5c6d7e8f9012",
"kind": "devotional",
"language": "en",
"meta": {
"subject": "Walk in step with the Spirit"
},
"model_id": "gemini-2.5-pro",
"note": {
"id": "note_0192f7a8-3b1c-7d2e-9a40-5c6d7e8f9012",
"object": "note",
"status": "approved"
},
"note_id": "note_0192f7a8-3b1c-7d2e-9a40-5c6d7e8f9012",
"object": "output",
"prompt_version": "outputs.v9",
"review_comment": "",
"row_version": 4,
"sections": [
{
"body": "The Spirit leads gently, and we follow.",
"heading": "Reflection",
"key": "reflection"
}
],
"source_note_id": "note_0192f7a8-3b1c-7d2e-9a40-5c6d7e8f9012",
"source_version": 3,
"stale": false,
"stale_acknowledged": false,
"stale_reason": "",
"status": "published",
"status_detail": "",
"teaching": {
"id": "tch_0192f7a8-3b1c-7d2e-9a40-5c6d7e8f9012",
"name": "Romans 8: Nothing can separate us",
"object": "teaching",
"recorded_at": "2026-10-11T10:00:00Z",
"speaker": "Pastor Ada Okafor",
"title": "Romans 8: Nothing can separate us"
},
"teaching_id": "tch_0192f7a8-3b1c-7d2e-9a40-5c6d7e8f9012",
"title": "Walk in step with the Spirit",
"updated_at": "2026-10-13T09:12:10Z",
"verses": [
{
"ref": "Romans 8:14",
"status": "verified"
}
],
"warnings": [],
"word_count": 412
}
},
"id": "evt_0192f7a8-3b1c-7d2e-9a40-5c6d7e8f9012",
"livemode": false,
"object": "event",
"request": {
"id": "req_5f3c9a1e7b2d4c68a0e1f2d3c4b5a697",
"idempotency_key": null
},
"type": "output.created"
}
conversation.handoff_createdMetadata onlyA visitor or team member asked for a person to follow up on a conversation. The question and contact details are never included.
Example: conversation.handoff_createdExample conversation.handoff_created body
billing.subscription_changedFinancial dataThe plan changed, an add-on was bought, or a cancellation was undone. previous_attributes holds what changed.