Operation Charity API

Webhooks

Webhooks tell your system when something changes in Operation Charity: a new contact, a gift, a receipt. We send an HTTPS POST with a signed JSON body to each endpoint that asked for that event.

Set up an endpoint

  1. An admin opens Settings → Webhooks and adds your HTTPS URL with the events you want (or calls POST /v1/webhook-subscriptions).
  2. Copy the signing secret. It is shown once; rotating it shows a new one.
  3. Send a test delivery and check it arrives. Each delivery, with its attempts and the last response, is listed under the endpoint.

Answer with any 2xx status within 10 seconds. Do slow work after you answer. Redirects are not followed.

What we send

Signed delivery envelope. Deliveries are at-least-once; consumers dedupe on id. Failed deliveries (non-2xx or timeout after 10 s) are retried with exponential backoff (1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h) and then dropped; a subscription that fails every delivery for 3 days is set inactive. Verify X-OC-Signature before trusting the body.

Event body fields
FieldTypeDescription
idstring (uuid)Event id, stable across retries.
typeone of: organization.updated, user.created, user.updated, contact.created, contact.updated, contact.deleted, tag.created, tag.updated, tag.deleted, custom_field_definition.created, custom_field_definition.updated, custom_field_definition.deleted, saved_list.created, saved_list.updated, saved_list.deleted, saved_list_snapshot.created, household.created, household.updated, household.deleted, contact_relationship.created, contact_relationship.updated, contact_relationship.deleted, fund.created, fund.updated, fund.deleted, campaign.created, campaign.updated, campaign.deleted, appeal.created, appeal.updated, appeal.deleted, soft_credit.created, soft_credit.updated, soft_credit.deleted, grant.created, grant.updated, grant.deleted, communication.created, communication.updated, communication.deleted, task.created, task.updated, task.deleted, gift.created, gift.updated, gift_adjustment.created, gift_batch.created, gift_batch.updated, gift_batch.deleted, gift_batch.closed, gift_batch.reopened, receipt.issued, receipt.sent, import_job.committed, import_job.rolled_back, import_job.discarded, donation_form.created, donation_form.updated, donation_form.deleted, recurring_donation.created, recurring_donation.updated, welcome_enrollment.created, welcome_enrollment.updated, email_template.created, email_template.updated, email_template.deleted, email_send.created, email_send.updated, email_send.deleted, webhook.testEvery event the platform emits. See the `webhooks` section for payloads.
created_atstring (date-time)
organization_idstring (uuid)
dataobjectThe resource after the change (for deleted, its last state).

Headers

X-OC-Signature

Webhook signature: t=<unix seconds>,v1=<hex HMAC-SHA256>. The HMAC key is the subscription secret and the signed string is ${t}.${raw body}. Reject deliveries whose t is more than 5 minutes old. For 24 hours after a secret rotation the header carries two v1= entries (new secret first, then the previous one); accept the delivery when any entry matches.

Example: t=1759674600,v1=5f1c7e0f6a1d0f3c7e2a9b4d8c6e1f0a2b3c4d5e6f708192a3b4c5d6e7f80912

X-OC-Event-Id

The event id, the same as the body's id and stable across retries. Dedupe on it (C4, ADR-011).

Example: 3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e39

X-OC-Event-Type, X-OC-Delivery-Attempt

The event type, and which attempt this is (1 for the first). Handy for logs; the signature does not cover headers.

Check the signature

Every delivery is signed with your endpoint's secret: HMAC-SHA256 over ${t}.${raw body}, where t is the Unix time in the header. Check it on the raw body before you parse it, and reject a t more than 5 minutes away from your clock. For 24 hours after you rotate the secret, the header carries two v1= entries (new secret first, then the old one), so either secret works while you switch.

Node.js
import crypto from "node:crypto";

// rawBody: the request body exactly as received (a string), before any JSON parsing.
export function verify(header, secret, rawBody) {
  const parts = header.split(",").map((p) => p.trim());
  const t = Number(parts.find((p) => p.startsWith("t="))?.slice(2));
  // Reject old or future timestamps (5 minutes) so a captured request cannot be replayed later.
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = Buffer.from(crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"));
  // During a secret rotation there are two v1= entries; any match is enough.
  return parts
    .filter((p) => p.startsWith("v1="))
    .some((p) => {
      const got = Buffer.from(p.slice(3));
      return got.length === expected.length && crypto.timingSafeEqual(got, expected);
    });
}

// Express example: keep the raw body for verification.
app.post("/operation-charity", express.text({ type: "application/json" }), (req, res) => {
  if (!verify(req.get("X-OC-Signature") ?? "", process.env.OC_WEBHOOK_SECRET, req.body)) return res.sendStatus(400);
  const event = JSON.parse(req.body);
  // Deliveries can repeat: skip event ids you have already handled.
  res.sendStatus(200);
});
Python
import hashlib
import hmac
import time


def verify(header: str, secret: str, raw_body: bytes) -> bool:
    parts = [p.strip() for p in header.split(",")]
    try:
        t = int(next(p[2:] for p in parts if p.startswith("t=")))
    except (StopIteration, ValueError):
        return False
    # Reject old or future timestamps (5 minutes) so a captured request cannot be replayed later.
    if abs(time.time() - t) > 300:
        return False
    signed = str(t).encode() + b"." + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    # During a secret rotation there are two v1= entries; any match is enough.
    return any(hmac.compare_digest(p[3:], expected) for p in parts if p.startswith("v1="))


# Flask example: request.get_data() is the raw body.
import os

from flask import Flask, request

app = Flask(__name__)

: request.get_data() is the raw body.
@app.post("/operation-charity")
def webhook():
    if not verify(request.headers.get("X-OC-Signature", ""), os.environ["OC_WEBHOOK_SECRET"], request.get_data()):
        return "", 400
    event = request.get_json()
    # Deliveries can repeat: skip event ids you have already handled.
    return "", 200

Retries and duplicates

If your endpoint does not answer with a 2xx, we try again after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours, 24 hours. After the eighth attempt the delivery is marked failed; an admin can retry it from the deliveries list.

An endpoint whose deliveries have all failed for 3 days is paused. Turn it back on in Settings → Webhooks once it is fixed.

A delivery can arrive more than once. Use the event id (also in X-OC-Event-Id) to skip events you have already handled. Events can arrive out of order; compare created_at or fetch the record when order matters.

We only deliver to public HTTPS addresses. URLs that point to private, local or reserved network addresses are refused.

Events

70 event types. Changes to API keys and webhook endpoints are recorded in the audit log but send no events. See the API reference for each record's fields.

organization

organization.updated

data is the full organization as returned by GET.

Example body
organization.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e01",
  "type": "organization.updated",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
    "name": "Maple Street Food Bank",
    "ein": "84-1234567",
    "timezone": "America/Denver",
    "fiscal_year_start_month": 7,
    "mailing_address": "1200 Maple Street\nDenver, CO 80202",
    "receipt_signature_name": "Rosa Diaz",
    "receipt_signature_title": "Executive director",
    "receipt_footer": "Questions about your gift? Call 303-555-0100.",
    "intangible_religious_benefits": false,
    "receipt_logo_url": null,
    "require_two_step": false,
    "created_at": "2026-01-15T16:00:00Z",
    "updated_at": "2026-10-05T14:30:00Z"
  }
}

user

user.created

data is the full user as returned by GET.

Example body
user.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e02",
  "type": "user.created",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d03",
    "email": "luis@maplestreetfoodbank.org",
    "full_name": "Luis Ortega",
    "role": "staff",
    "created_at": "2026-02-02T18:12:00Z",
    "updated_at": "2026-02-02T18:12:00Z",
    "deactivated_at": null,
    "deactivated_by": null,
    "invitation": null
  }
}

user.updated

data is the full user as returned by GET.

Example body
user.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e03",
  "type": "user.updated",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d03",
    "email": "luis@maplestreetfoodbank.org",
    "full_name": "Luis Ortega",
    "role": "staff",
    "created_at": "2026-02-02T18:12:00Z",
    "updated_at": "2026-02-02T18:12:00Z",
    "deactivated_at": null,
    "deactivated_by": null,
    "invitation": null
  }
}

contact

contact.created

data is the full contact as returned by GET.

Example body
contact.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e04",
  "type": "contact.created",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "kind": "person",
    "first_name": "Aiko",
    "last_name": "Nakamura",
    "org_name": null,
    "display_name": "Aiko Nakamura",
    "email": "aiko.nakamura@example.com",
    "phone": "+1 303 555 0142",
    "household_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d04",
    "address_line1": "1420 Spruce St",
    "address_line2": null,
    "city": "Denver",
    "region": "CO",
    "postal_code": "80205",
    "country": "US",
    "employer": "Front Range Dental",
    "do_not_contact": false,
    "email_opt_in": true,
    "sms_opt_in": false,
    "source": "form",
    "source_ref": "donation-form",
    "consented": true,
    "custom_fields": {
      "volunteer": true,
      "t_shirt_size": "M"
    },
    "tags": [
      {
        "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7a01",
        "name": "Volunteer",
        "color": "green"
      }
    ],
    "created_at": "2026-03-01T15:00:00Z",
    "updated_at": "2026-10-05T14:30:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

contact.updated

data is the full contact as returned by GET.

Example body
contact.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e05",
  "type": "contact.updated",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "kind": "person",
    "first_name": "Aiko",
    "last_name": "Nakamura",
    "org_name": null,
    "display_name": "Aiko Nakamura",
    "email": "aiko.nakamura@example.com",
    "phone": "+1 303 555 0142",
    "household_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d04",
    "address_line1": "1420 Spruce St",
    "address_line2": null,
    "city": "Denver",
    "region": "CO",
    "postal_code": "80205",
    "country": "US",
    "employer": "Front Range Dental",
    "do_not_contact": false,
    "email_opt_in": true,
    "sms_opt_in": false,
    "source": "form",
    "source_ref": "donation-form",
    "consented": true,
    "custom_fields": {
      "volunteer": true,
      "t_shirt_size": "M"
    },
    "tags": [
      {
        "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7a01",
        "name": "Volunteer",
        "color": "green"
      }
    ],
    "created_at": "2026-03-01T15:00:00Z",
    "updated_at": "2026-10-05T14:30:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

contact.deleted

data is the row with deleted_at set (soft delete, ADR-005). A restore emits contact.updated with deleted_at null.

Example body
contact.deleted example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e06",
  "type": "contact.deleted",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "kind": "person",
    "first_name": "Aiko",
    "last_name": "Nakamura",
    "org_name": null,
    "display_name": "Aiko Nakamura",
    "email": "aiko.nakamura@example.com",
    "phone": "+1 303 555 0142",
    "household_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d04",
    "address_line1": "1420 Spruce St",
    "address_line2": null,
    "city": "Denver",
    "region": "CO",
    "postal_code": "80205",
    "country": "US",
    "employer": "Front Range Dental",
    "do_not_contact": false,
    "email_opt_in": true,
    "sms_opt_in": false,
    "source": "form",
    "source_ref": "donation-form",
    "consented": true,
    "custom_fields": {
      "volunteer": true,
      "t_shirt_size": "M"
    },
    "tags": [
      {
        "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7a01",
        "name": "Volunteer",
        "color": "green"
      }
    ],
    "created_at": "2026-03-01T15:00:00Z",
    "updated_at": "2026-10-05T16:00:00Z",
    "deleted_at": "2026-10-05T16:00:00Z",
    "deleted_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02"
  }
}

tag

tag.created

data is the full tag as returned by GET (ADR-018).

Example body
tag.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e49",
  "type": "tag.created",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7a01",
    "name": "Volunteer",
    "color": "green",
    "contact_count": 23,
    "created_at": "2026-09-01T16:00:00Z",
    "updated_at": "2026-09-01T16:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

tag.updated

data is the full tag as returned by GET, after a rename or a new color. Every contact with the tag now shows it so; no contact.updated is sent for them.

Example body
tag.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e50",
  "type": "tag.updated",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7a02",
    "name": "Board",
    "color": "blue",
    "contact_count": 7,
    "created_at": "2026-09-01T16:00:00Z",
    "updated_at": "2026-10-06T09:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

tag.deleted

data is the row with deleted_at set (soft delete, ADR-005). The tag leaves every contact's tags until it is restored; no contact.updated is sent for them. A restore emits tag.updated with deleted_at null, and the tag is back on every contact that had it.

Example body
tag.deleted example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e51",
  "type": "tag.deleted",
  "created_at": "2026-10-05T16:00:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7a02",
    "name": "Board",
    "color": "blue",
    "contact_count": 7,
    "created_at": "2026-09-01T16:00:00Z",
    "updated_at": "2026-10-05T16:00:00Z",
    "deleted_at": "2026-10-05T16:00:00Z",
    "deleted_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02"
  }
}

custom field definition

custom_field_definition.created

data is the full custom field as returned by GET (ADR-018).

Example body
custom_field_definition.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e53",
  "type": "custom_field_definition.created",
  "created_at": "2026-09-01T16:00:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7b01",
    "key": "t_shirt_size",
    "label": "T-shirt size",
    "type": "choice",
    "choices": [
      "S",
      "M",
      "L",
      "XL"
    ],
    "position": 0,
    "value_count": 41,
    "mismatch_count": 2,
    "archived_at": null,
    "archived_by": null,
    "created_at": "2026-09-01T16:00:00Z",
    "updated_at": "2026-09-01T16:00:00Z"
  }
}

custom_field_definition.updated

data is the full custom field as returned by GET, after a new label, type, choices or position (a field moved by another's move included), or an unarchive (archived_at null).

Example body
custom_field_definition.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e54",
  "type": "custom_field_definition.updated",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7b02",
    "key": "volunteer",
    "label": "Volunteers with us",
    "type": "yes_no",
    "choices": null,
    "position": 1,
    "value_count": 23,
    "mismatch_count": 0,
    "archived_at": null,
    "archived_by": null,
    "created_at": "2026-09-01T16:05:00Z",
    "updated_at": "2026-09-01T16:05:00Z"
  }
}

custom_field_definition.deleted

The field was archived, this resource's delete: data is the field with archived_at set. Every saved value stays on its contacts. Unarchiving emits custom_field_definition.updated with archived_at null, as a restore does (ADR-005).

Example body
custom_field_definition.deleted example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e55",
  "type": "custom_field_definition.deleted",
  "created_at": "2026-10-05T16:00:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7b03",
    "key": "newsletter_format",
    "label": "Newsletter format",
    "type": "text",
    "choices": null,
    "position": 2,
    "value_count": 12,
    "mismatch_count": 0,
    "archived_at": "2026-10-05T16:00:00Z",
    "archived_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "created_at": "2026-09-01T16:00:00Z",
    "updated_at": "2026-10-05T16:00:00Z"
  }
}

saved list

saved_list.created

data is the saved list as GET returns it, without member_count (ADR-020). Membership changes emit nothing.

Example body
saved_list.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e70",
  "type": "saved_list.created",
  "created_at": "2026-10-01T16:00:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7c01",
    "name": "Lapsed shelter donors",
    "description": "Gave to the shelter last fiscal year, not yet this year, and can be mailed.",
    "filter": {
      "version": 1,
      "match": "all",
      "conditions": [
        {
          "field": "gave",
          "period": {
            "basis": "fiscal",
            "from": {
              "type": "year_start",
              "years_ago": 1
            },
            "to": {
              "type": "year_end",
              "years_ago": 1
            }
          },
          "fund_ids": [
            "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09"
          ]
        },
        {
          "field": "gave",
          "period": {
            "basis": "fiscal",
            "from": {
              "type": "year_start",
              "years_ago": 0
            },
            "to": null
          },
          "not": true
        },
        {
          "field": "has_mailing_address"
        },
        {
          "field": "do_not_contact",
          "not": true
        },
        {
          "field": "tags",
          "match": "none",
          "tag_ids": [
            "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7a02"
          ]
        }
      ]
    },
    "version": 3,
    "problems": [],
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "created_at": "2026-10-01T16:00:00Z",
    "updated_at": "2026-10-05T14:30:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

saved_list.updated

data is the saved list as GET returns it, after a rename, a new description or a new filter (with its new version), or a restore (deleted_at null).

Example body
saved_list.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e71",
  "type": "saved_list.updated",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7c01",
    "name": "Lapsed shelter donors",
    "description": "Gave to the shelter last fiscal year, not yet this year, and can be mailed.",
    "filter": {
      "version": 1,
      "match": "all",
      "conditions": [
        {
          "field": "gave",
          "period": {
            "basis": "fiscal",
            "from": {
              "type": "year_start",
              "years_ago": 1
            },
            "to": {
              "type": "year_end",
              "years_ago": 1
            }
          },
          "fund_ids": [
            "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09"
          ]
        },
        {
          "field": "gave",
          "period": {
            "basis": "fiscal",
            "from": {
              "type": "year_start",
              "years_ago": 0
            },
            "to": null
          },
          "not": true
        },
        {
          "field": "has_mailing_address"
        },
        {
          "field": "do_not_contact",
          "not": true
        },
        {
          "field": "tags",
          "match": "none",
          "tag_ids": [
            "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7a02"
          ]
        }
      ]
    },
    "version": 3,
    "problems": [],
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "created_at": "2026-10-01T16:00:00Z",
    "updated_at": "2026-10-05T14:30:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

saved_list.deleted

data is the row with deleted_at set (soft delete, ADR-005). A restore emits saved_list.updated with deleted_at null.

Example body
saved_list.deleted example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e72",
  "type": "saved_list.deleted",
  "created_at": "2026-10-05T16:00:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7c02",
    "name": "Major donors in Denver",
    "description": null,
    "filter": {
      "version": 1,
      "match": "all",
      "conditions": [
        {
          "field": "lifetime_giving",
          "min_cents": 100000
        },
        {
          "field": "city",
          "values": [
            "Denver"
          ]
        },
        {
          "field": "tags",
          "match": "none",
          "tag_ids": [
            "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7a02"
          ]
        }
      ]
    },
    "version": 1,
    "problems": [],
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "created_at": "2026-10-02T16:00:00Z",
    "updated_at": "2026-10-05T16:00:00Z",
    "deleted_at": "2026-10-05T16:00:00Z",
    "deleted_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02"
  }
}

saved list snapshot

saved_list_snapshot.created

data is the snapshot as GET returns it, without its members (ADR-020); GET /v1/contacts?snapshot_id= lists them.

Example body
saved_list_snapshot.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e73",
  "type": "saved_list_snapshot.created",
  "created_at": "2026-10-13T15:00:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7d01",
    "saved_list_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7c01",
    "list_version": 3,
    "name": "November appeal",
    "filter": {
      "version": 1,
      "match": "all",
      "conditions": [
        {
          "field": "gave",
          "period": {
            "basis": "fiscal",
            "from": {
              "type": "date",
              "date": "2025-07-01"
            },
            "to": {
              "type": "date",
              "date": "2026-06-30"
            }
          },
          "fund_ids": [
            "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09"
          ]
        },
        {
          "field": "gave",
          "period": {
            "basis": "fiscal",
            "from": {
              "type": "date",
              "date": "2026-07-01"
            },
            "to": null
          },
          "not": true
        },
        {
          "field": "has_mailing_address"
        },
        {
          "field": "do_not_contact",
          "not": true
        },
        {
          "field": "tags",
          "match": "none",
          "tag_ids": [
            "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7a02"
          ]
        }
      ]
    },
    "as_of": "2026-10-13",
    "member_count": 112,
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "api_key_id": null,
    "created_at": "2026-10-13T15:00:00Z"
  }
}

household

household.created

data is the full household as returned by GET.

Example body
household.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e07",
  "type": "household.created",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d04",
    "name": "The Nakamura household",
    "address_line1": "1420 Spruce St",
    "address_line2": null,
    "city": "Denver",
    "region": "CO",
    "postal_code": "80205",
    "country": "US",
    "created_at": "2026-03-01T15:00:00Z",
    "updated_at": "2026-03-01T15:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

household.updated

data is the full household as returned by GET.

Example body
household.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e08",
  "type": "household.updated",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d04",
    "name": "The Nakamura household",
    "address_line1": "1420 Spruce St",
    "address_line2": null,
    "city": "Denver",
    "region": "CO",
    "postal_code": "80205",
    "country": "US",
    "created_at": "2026-03-01T15:00:00Z",
    "updated_at": "2026-03-01T15:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

household.deleted

data is the row with deleted_at set (soft delete, ADR-005). A restore emits household.updated with deleted_at null.

Example body
household.deleted example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e09",
  "type": "household.deleted",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d04",
    "name": "The Nakamura household",
    "address_line1": "1420 Spruce St",
    "address_line2": null,
    "city": "Denver",
    "region": "CO",
    "postal_code": "80205",
    "country": "US",
    "created_at": "2026-03-01T15:00:00Z",
    "updated_at": "2026-10-05T16:00:00Z",
    "deleted_at": "2026-10-05T16:00:00Z",
    "deleted_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02"
  }
}

contact relationship

contact_relationship.created

data is the full contact relationship as returned by GET.

Example body
contact_relationship.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e10",
  "type": "contact_relationship.created",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d23",
    "from_contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "to_contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d06",
    "relationship": "spouse",
    "created_at": "2026-03-01T15:05:00Z",
    "updated_at": "2026-03-01T15:05:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

contact_relationship.updated

data is the full contact relationship as returned by GET.

Example body
contact_relationship.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e11",
  "type": "contact_relationship.updated",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d23",
    "from_contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "to_contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d06",
    "relationship": "spouse",
    "created_at": "2026-03-01T15:05:00Z",
    "updated_at": "2026-03-01T15:05:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

contact_relationship.deleted

data is the row with deleted_at set (soft delete, ADR-005). A restore emits contact_relationship.updated with deleted_at null.

Example body
contact_relationship.deleted example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e12",
  "type": "contact_relationship.deleted",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d23",
    "from_contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "to_contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d06",
    "relationship": "spouse",
    "created_at": "2026-03-01T15:05:00Z",
    "updated_at": "2026-10-05T16:00:00Z",
    "deleted_at": "2026-10-05T16:00:00Z",
    "deleted_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02"
  }
}

fund

fund.created

data is the full fund as returned by GET.

Example body
fund.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e13",
  "type": "fund.created",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09",
    "name": "Winter Shelter Fund",
    "code": "SHELTER",
    "restriction": "purpose",
    "purpose": "Overnight shelter operations from November through March",
    "restriction_release_date": null,
    "is_default": false,
    "active": true,
    "created_at": "2026-09-01T16:00:00Z",
    "updated_at": "2026-09-01T16:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

fund.updated

data is the full fund as returned by GET.

Example body
fund.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e14",
  "type": "fund.updated",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09",
    "name": "Winter Shelter Fund",
    "code": "SHELTER",
    "restriction": "purpose",
    "purpose": "Overnight shelter operations from November through March",
    "restriction_release_date": null,
    "is_default": false,
    "active": true,
    "created_at": "2026-09-01T16:00:00Z",
    "updated_at": "2026-09-01T16:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

fund.deleted

data is the row with deleted_at set (soft delete, ADR-005). A restore emits fund.updated with deleted_at null.

Example body
fund.deleted example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e15",
  "type": "fund.deleted",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09",
    "name": "Winter Shelter Fund",
    "code": "SHELTER",
    "restriction": "purpose",
    "purpose": "Overnight shelter operations from November through March",
    "restriction_release_date": null,
    "is_default": false,
    "active": true,
    "created_at": "2026-09-01T16:00:00Z",
    "updated_at": "2026-10-05T16:00:00Z",
    "deleted_at": "2026-10-05T16:00:00Z",
    "deleted_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02"
  }
}

campaign

campaign.created

data is the full campaign as returned by GET.

Example body
campaign.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e16",
  "type": "campaign.created",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d10",
    "name": "Winter 2026",
    "goal_cents": 5000000,
    "starts_on": "2026-10-01",
    "ends_on": "2026-12-31",
    "default_fund_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09",
    "created_at": "2026-09-15T16:00:00Z",
    "updated_at": "2026-09-15T16:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

campaign.updated

data is the full campaign as returned by GET.

Example body
campaign.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e17",
  "type": "campaign.updated",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d10",
    "name": "Winter 2026",
    "goal_cents": 5000000,
    "starts_on": "2026-10-01",
    "ends_on": "2026-12-31",
    "default_fund_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09",
    "created_at": "2026-09-15T16:00:00Z",
    "updated_at": "2026-09-15T16:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

campaign.deleted

data is the row with deleted_at set (soft delete, ADR-005). A restore emits campaign.updated with deleted_at null.

Example body
campaign.deleted example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e18",
  "type": "campaign.deleted",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d10",
    "name": "Winter 2026",
    "goal_cents": 5000000,
    "starts_on": "2026-10-01",
    "ends_on": "2026-12-31",
    "default_fund_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09",
    "created_at": "2026-09-15T16:00:00Z",
    "updated_at": "2026-10-05T16:00:00Z",
    "deleted_at": "2026-10-05T16:00:00Z",
    "deleted_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02"
  }
}

appeal

appeal.created

data is the full appeal as returned by GET.

Example body
appeal.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e19",
  "type": "appeal.created",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d11",
    "campaign_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d10",
    "name": "September letter",
    "channel": "letter",
    "sent_on": "2026-09-28",
    "cost_cents": 84000,
    "created_at": "2026-09-15T17:00:00Z",
    "updated_at": "2026-09-28T16:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

appeal.updated

data is the full appeal as returned by GET.

Example body
appeal.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e20",
  "type": "appeal.updated",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d11",
    "campaign_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d10",
    "name": "September letter",
    "channel": "letter",
    "sent_on": "2026-09-28",
    "cost_cents": 84000,
    "created_at": "2026-09-15T17:00:00Z",
    "updated_at": "2026-09-28T16:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

appeal.deleted

data is the row with deleted_at set (soft delete, ADR-005). A restore emits appeal.updated with deleted_at null.

Example body
appeal.deleted example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e21",
  "type": "appeal.deleted",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d11",
    "campaign_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d10",
    "name": "September letter",
    "channel": "letter",
    "sent_on": "2026-09-28",
    "cost_cents": 84000,
    "created_at": "2026-09-15T17:00:00Z",
    "updated_at": "2026-10-05T16:00:00Z",
    "deleted_at": "2026-10-05T16:00:00Z",
    "deleted_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02"
  }
}

soft credit

soft_credit.created

data is the full soft credit as returned by GET.

Example body
soft_credit.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e22",
  "type": "soft_credit.created",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d15",
    "gift_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d13",
    "contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d06",
    "contact_name": "Kenji Nakamura",
    "amount_cents": 5000,
    "role": "household_member",
    "reason": null,
    "created_at": "2026-10-04T22:16:00Z",
    "updated_at": "2026-10-04T22:16:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

soft_credit.updated

data is the full soft credit as returned by GET.

Example body
soft_credit.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e23",
  "type": "soft_credit.updated",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d15",
    "gift_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d13",
    "contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d06",
    "contact_name": "Kenji Nakamura",
    "amount_cents": 5000,
    "role": "household_member",
    "reason": null,
    "created_at": "2026-10-04T22:16:00Z",
    "updated_at": "2026-10-04T22:16:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

soft_credit.deleted

data is the row with deleted_at set (soft delete, ADR-005). A restore emits soft_credit.updated with deleted_at null.

Example body
soft_credit.deleted example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e24",
  "type": "soft_credit.deleted",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d15",
    "gift_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d13",
    "contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d06",
    "contact_name": "Kenji Nakamura",
    "amount_cents": 5000,
    "role": "household_member",
    "reason": null,
    "created_at": "2026-10-04T22:16:00Z",
    "updated_at": "2026-10-05T16:00:00Z",
    "deleted_at": "2026-10-05T16:00:00Z",
    "deleted_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02"
  }
}

grant

grant.created

data is the full grant as returned by GET.

Example body
grant.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e25",
  "type": "grant.created",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d12",
    "funder_contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d07",
    "fund_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09",
    "name": "Cedar Ridge winter shelter grant 2026",
    "status": "awarded",
    "awarded_cents": 2500000,
    "indirect_rate_bps": 1500,
    "period_start": "2026-11-01",
    "period_end": "2027-03-31",
    "allowed_uses": "Shelter staffing, utilities and bedding. No capital purchases.",
    "report_due_on": "2027-04-30",
    "next_payment_due_on": "2027-01-15",
    "notes": "Second payment on receipt of the interim report.",
    "created_at": "2026-09-20T16:00:00Z",
    "updated_at": "2026-10-01T16:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

grant.updated

data is the full grant as returned by GET.

Example body
grant.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e26",
  "type": "grant.updated",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d12",
    "funder_contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d07",
    "fund_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09",
    "name": "Cedar Ridge winter shelter grant 2026",
    "status": "awarded",
    "awarded_cents": 2500000,
    "indirect_rate_bps": 1500,
    "period_start": "2026-11-01",
    "period_end": "2027-03-31",
    "allowed_uses": "Shelter staffing, utilities and bedding. No capital purchases.",
    "report_due_on": "2027-04-30",
    "next_payment_due_on": "2027-01-15",
    "notes": "Second payment on receipt of the interim report.",
    "created_at": "2026-09-20T16:00:00Z",
    "updated_at": "2026-10-01T16:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

grant.deleted

data is the row with deleted_at set (soft delete, ADR-005). A restore emits grant.updated with deleted_at null.

Example body
grant.deleted example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e27",
  "type": "grant.deleted",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d12",
    "funder_contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d07",
    "fund_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09",
    "name": "Cedar Ridge winter shelter grant 2026",
    "status": "awarded",
    "awarded_cents": 2500000,
    "indirect_rate_bps": 1500,
    "period_start": "2026-11-01",
    "period_end": "2027-03-31",
    "allowed_uses": "Shelter staffing, utilities and bedding. No capital purchases.",
    "report_due_on": "2027-04-30",
    "next_payment_due_on": "2027-01-15",
    "notes": "Second payment on receipt of the interim report.",
    "created_at": "2026-09-20T16:00:00Z",
    "updated_at": "2026-10-05T16:00:00Z",
    "deleted_at": "2026-10-05T16:00:00Z",
    "deleted_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02"
  }
}

communication

communication.created

data is the full communication as returned by GET. Also emitted when an email this system sent is accepted by Postmark (source: system, email_message_id set) and when a BCC-captured email is filed to a contact (source: postmark_inbound, inbound_email_id set), once per contact (ADR-014).

Example body
communication.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e28",
  "type": "communication.created",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d18",
    "contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "channel": "email",
    "direction": "outbound",
    "subject": "Thank you for keeping the shelter warm",
    "body_text": "Aiko, your gift of $50.00 to the Winter Shelter Fund means two more nights of warm beds. Thank you.",
    "body_html": null,
    "message_id": "<thanks-6d13@maplestreetfoodbank.org>",
    "thread_id": null,
    "from_address": "luis@maplestreetfoodbank.org",
    "to_addresses": [
      "aiko.nakamura@example.com"
    ],
    "occurred_at": "2026-10-05T15:00:00Z",
    "source": "manual",
    "logged_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d03",
    "inbound_email_id": null,
    "email_message_id": null,
    "attachment_count": null,
    "created_at": "2026-10-05T15:00:00Z",
    "updated_at": "2026-10-05T15:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

communication.updated

data is the full communication as returned by GET.

Example body
communication.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e29",
  "type": "communication.updated",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d18",
    "contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "channel": "email",
    "direction": "outbound",
    "subject": "Thank you for keeping the shelter warm",
    "body_text": "Aiko, your gift of $50.00 to the Winter Shelter Fund means two more nights of warm beds. Thank you.",
    "body_html": null,
    "message_id": "<thanks-6d13@maplestreetfoodbank.org>",
    "thread_id": null,
    "from_address": "luis@maplestreetfoodbank.org",
    "to_addresses": [
      "aiko.nakamura@example.com"
    ],
    "occurred_at": "2026-10-05T15:00:00Z",
    "source": "manual",
    "logged_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d03",
    "inbound_email_id": null,
    "email_message_id": null,
    "attachment_count": null,
    "created_at": "2026-10-05T15:00:00Z",
    "updated_at": "2026-10-05T15:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

communication.deleted

data is the row with deleted_at set (soft delete, ADR-005). A restore emits communication.updated with deleted_at null.

Example body
communication.deleted example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e30",
  "type": "communication.deleted",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d18",
    "contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "channel": "email",
    "direction": "outbound",
    "subject": "Thank you for keeping the shelter warm",
    "body_text": "Aiko, your gift of $50.00 to the Winter Shelter Fund means two more nights of warm beds. Thank you.",
    "body_html": null,
    "message_id": "<thanks-6d13@maplestreetfoodbank.org>",
    "thread_id": null,
    "from_address": "luis@maplestreetfoodbank.org",
    "to_addresses": [
      "aiko.nakamura@example.com"
    ],
    "occurred_at": "2026-10-05T15:00:00Z",
    "source": "manual",
    "logged_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d03",
    "inbound_email_id": null,
    "email_message_id": null,
    "attachment_count": null,
    "created_at": "2026-10-05T15:00:00Z",
    "updated_at": "2026-10-05T16:00:00Z",
    "deleted_at": "2026-10-05T16:00:00Z",
    "deleted_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02"
  }
}

task

task.created

data is the full task as returned by GET.

Example body
task.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e31",
  "type": "task.created",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d19",
    "contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "grant_id": null,
    "kind": "thank_you",
    "title": "Thank Aiko Nakamura for her $50.00 gift",
    "due_on": "2026-10-06",
    "status": "open",
    "assigned_to": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d03",
    "generated": true,
    "snoozed_until": null,
    "completed_at": null,
    "created_at": "2026-10-04T22:15:00Z",
    "updated_at": "2026-10-04T22:15:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

task.updated

data is the full task as returned by GET.

Example body
task.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e32",
  "type": "task.updated",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d19",
    "contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "grant_id": null,
    "kind": "thank_you",
    "title": "Thank Aiko Nakamura for her $50.00 gift",
    "due_on": "2026-10-06",
    "status": "open",
    "assigned_to": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d03",
    "generated": true,
    "snoozed_until": null,
    "completed_at": null,
    "created_at": "2026-10-04T22:15:00Z",
    "updated_at": "2026-10-04T22:15:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

task.deleted

data is the row with deleted_at set (soft delete, ADR-005). A restore emits task.updated with deleted_at null.

Example body
task.deleted example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e33",
  "type": "task.deleted",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d19",
    "contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "grant_id": null,
    "kind": "thank_you",
    "title": "Thank Aiko Nakamura for her $50.00 gift",
    "due_on": "2026-10-06",
    "status": "open",
    "assigned_to": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d03",
    "generated": true,
    "snoozed_until": null,
    "completed_at": null,
    "created_at": "2026-10-04T22:15:00Z",
    "updated_at": "2026-10-05T16:00:00Z",
    "deleted_at": "2026-10-05T16:00:00Z",
    "deleted_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02"
  }
}

gift

gift.created

data is the full gift as returned by GET. Gifts paid through a donation form (ADR-013) arrive once Stripe reports the money paid, with donation_form_id (and recurring_donation_id for each charge of a recurring donation) set and created_by null.

Example body
gift.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e34",
  "type": "gift.created",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d13",
    "contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "contact_name": "Aiko Nakamura",
    "fund_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09",
    "fund_name": "Winter Shelter Fund",
    "campaign_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d10",
    "campaign_name": "Winter 2026",
    "appeal_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d11",
    "appeal_name": "September letter",
    "grant_id": null,
    "kind": "one_time",
    "status": "posted",
    "amount_cents": 5000,
    "fair_market_value_cents": 0,
    "fee_covered_cents": 0,
    "received_on": "2026-10-04",
    "contribution_date": "2026-10-04",
    "payment_method": "card",
    "external_ref": "pi_3Q8kLm2eZvKYlo2C1aBcDeFg",
    "in_kind_description": null,
    "property_description": null,
    "book_fair_value_cents": null,
    "valuation_method": null,
    "nonfinancial_category": null,
    "pledge_id": null,
    "tribute_type": null,
    "tribute_name": null,
    "notify_contact_id": null,
    "notes": "Gave through the website after the September letter.",
    "receipt_required": false,
    "donation_form_id": null,
    "donation_form_title": null,
    "recurring_donation_id": null,
    "recurring_interval": null,
    "batch_id": null,
    "batch_name": null,
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d03",
    "created_at": "2026-10-04T22:15:00Z",
    "updated_at": "2026-10-04T22:15:00Z"
  }
}

gift.updated

Sent when an in-kind gift's valuation or printed description changes (PATCH /v1/gifts/{id}/valuation). Status changes from a void or full refund arrive as gift_adjustment.created. data is the full gift as returned by GET.

Example body
gift.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e40",
  "type": "gift.updated",
  "created_at": "2026-11-20T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d23",
    "contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d07",
    "fund_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d08",
    "kind": "in_kind",
    "status": "posted",
    "amount_cents": null,
    "book_fair_value_cents": 42000,
    "valuation_method": "independent appraisal"
  }
}

gift adjustment

gift_adjustment.created

data is the full gift adjustment as returned by GET.

Example body
gift_adjustment.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e35",
  "type": "gift_adjustment.created",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d14",
    "gift_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d13",
    "kind": "reallocate",
    "amount_cents": 5000,
    "from_fund_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09",
    "to_fund_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d08",
    "effective_on": "2026-10-05",
    "reason": "Donor asked that the gift be unrestricted.",
    "exception_reason": null,
    "consent_reference": "Donor email of 2026-10-03, filed in the donor record",
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "created_at": "2026-10-05T14:30:00Z",
    "updated_at": "2026-10-05T14:30:00Z"
  }
}

gift batch

gift_batch.created

A deposit batch was started (ADR-019). data is the batch as returned by GET. Gifts entered into it arrive as gift.created with batch_id; they emit no batch event.

Example body
gift_batch.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e51",
  "type": "gift_batch.created",
  "created_at": "2026-10-12T16:00:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d71",
    "name": "Sunday collection, Oct 12",
    "deposit_date": "2026-10-12",
    "expected_count": 3,
    "expected_total_cents": 22500,
    "entered_count": 0,
    "entered_total_cents": 0,
    "count_difference": 3,
    "total_difference_cents": 22500,
    "balanced": false,
    "status": "open",
    "notes": "Plate and envelopes from the 9:30 and 11:00 services.",
    "closed_at": null,
    "closed_by": null,
    "reopened_at": null,
    "reopened_by": null,
    "reopen_reason": null,
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d03",
    "created_at": "2026-10-12T16:00:00Z",
    "updated_at": "2026-10-12T16:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

gift_batch.updated

An open batch's name, deposit date, expected count or total, or notes changed, or a deleted batch was restored (ADR-001, ADR-005: a restore emits .updated). Closing, reopening and deleting have their own events. data is the batch as returned by GET.

Example body
gift_batch.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e52",
  "type": "gift_batch.updated",
  "created_at": "2026-10-12T16:06:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d71",
    "name": "Sunday collection, Oct 12",
    "deposit_date": "2026-10-12",
    "expected_count": 3,
    "expected_total_cents": 22500,
    "entered_count": 2,
    "entered_total_cents": 15000,
    "count_difference": 1,
    "total_difference_cents": 7500,
    "balanced": false,
    "status": "open",
    "notes": "Plate and envelopes from the 9:30 and 11:00 services.",
    "closed_at": null,
    "closed_by": null,
    "reopened_at": null,
    "reopened_by": null,
    "reopen_reason": null,
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d03",
    "created_at": "2026-10-12T16:00:00Z",
    "updated_at": "2026-10-12T16:06:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

gift_batch.deleted

An empty batch was soft-deleted. data is the batch with deleted_at set.

Example body
gift_batch.deleted example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e53",
  "type": "gift_batch.deleted",
  "created_at": "2026-10-12T15:10:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d72",
    "name": "Bake sale cash",
    "deposit_date": "2026-10-12",
    "expected_count": 1,
    "expected_total_cents": 4000,
    "entered_count": 0,
    "entered_total_cents": 0,
    "count_difference": 1,
    "total_difference_cents": 4000,
    "balanced": false,
    "status": "open",
    "notes": null,
    "closed_at": null,
    "closed_by": null,
    "reopened_at": null,
    "reopened_by": null,
    "reopen_reason": null,
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d03",
    "created_at": "2026-10-12T15:00:00Z",
    "updated_at": "2026-10-12T15:10:00Z",
    "deleted_at": "2026-10-12T15:10:00Z",
    "deleted_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d03"
  }
}

gift_batch.closed

A batch closed because it balanced. data is the batch as returned by GET, with closed_at set; its figures no longer change unless an admin reopens it.

Example body
gift_batch.closed example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e55",
  "type": "gift_batch.closed",
  "created_at": "2026-10-12T16:20:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d71",
    "name": "Sunday collection, Oct 12",
    "deposit_date": "2026-10-12",
    "expected_count": 3,
    "expected_total_cents": 22500,
    "entered_count": 3,
    "entered_total_cents": 22500,
    "count_difference": 0,
    "total_difference_cents": 0,
    "balanced": true,
    "status": "closed",
    "notes": "Plate and envelopes from the 9:30 and 11:00 services.",
    "closed_at": "2026-10-12T16:20:00Z",
    "closed_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d03",
    "reopened_at": null,
    "reopened_by": null,
    "reopen_reason": null,
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d03",
    "created_at": "2026-10-12T16:00:00Z",
    "updated_at": "2026-10-12T16:20:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

gift_batch.reopened

An admin reopened a closed batch. data is the batch as returned by GET, with reopened_at, reopened_by and reopen_reason set.

Example body
gift_batch.reopened example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e56",
  "type": "gift_batch.reopened",
  "created_at": "2026-10-13T15:00:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d71",
    "name": "Sunday collection, Oct 12",
    "deposit_date": "2026-10-12",
    "expected_count": 3,
    "expected_total_cents": 22500,
    "entered_count": 3,
    "entered_total_cents": 22500,
    "count_difference": 0,
    "total_difference_cents": 0,
    "balanced": true,
    "status": "open",
    "notes": "Plate and envelopes from the 9:30 and 11:00 services.",
    "closed_at": null,
    "closed_by": null,
    "reopened_at": "2026-10-13T15:00:00Z",
    "reopened_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "reopen_reason": "The bank's deposit receipt shows four checks; one was left out of the batch.",
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d03",
    "created_at": "2026-10-12T16:00:00Z",
    "updated_at": "2026-10-13T15:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

receipt

receipt.issued

data is the full receipt as returned by GET.

Example body
receipt.issued example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e36",
  "type": "receipt.issued",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d16",
    "contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "kind": "gift",
    "gift_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d13",
    "tax_year": null,
    "contribution_date": "2026-10-04",
    "total_cents": 5000,
    "deductible_cents": 5000,
    "goods_services_statement": "No goods or services were provided in exchange for this contribution.",
    "rendered_text": "Maple Street Food Bank\n1200 Maple Street\nDenver, CO 80202\nEIN 84-1234567\n\nContribution receipt\n\nReceipt number: 3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d16\nDate issued: October 4, 2026\n\nDonor\nAiko Nakamura\n\nContribution\nContribution date: October 4, 2026\nAmount: $50.00\nDesignated to: General fund (unrestricted)\nDeductible amount: $50.00\n\nNo goods or services were provided in exchange for this contribution.\n\nThis receipt is your written acknowledgment of this contribution for federal income tax purposes. Please keep it with your tax records.\n",
    "supersedes_receipt_id": null,
    "superseded_at": null,
    "reissue_reason": null,
    "sent_at": "2026-10-04T22:20:00Z",
    "sent_via": "email",
    "created_at": "2026-10-04T22:20:00Z",
    "updated_at": "2026-10-04T22:20:00Z"
  }
}

receipt.sent

Postmark accepted a receipt for delivery to its donor (C2, ADR-014): the receipt's sent_at and sent_via are set. Emitted for every send, a resend included. data is the full receipt as returned by GET.

Example body
receipt.sent example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e46",
  "type": "receipt.sent",
  "created_at": "2026-10-04T22:20:41Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d16",
    "contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "kind": "gift",
    "gift_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d13",
    "tax_year": null,
    "contribution_date": "2026-10-04",
    "total_cents": 5000,
    "deductible_cents": 5000,
    "goods_services_statement": "No goods or services were provided in exchange for this contribution.",
    "rendered_text": "Maple Street Food Bank\n1200 Maple Street\nDenver, CO 80202\nEIN 84-1234567\n\nContribution receipt\n\nReceipt number: 3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d16\nDate issued: October 4, 2026\n\nDonor\nAiko Nakamura\n\nContribution\nContribution date: October 4, 2026\nAmount: $50.00\nDesignated to: General fund (unrestricted)\nDeductible amount: $50.00\n\nNo goods or services were provided in exchange for this contribution.\n\nThis receipt is your written acknowledgment of this contribution for federal income tax purposes. Please keep it with your tax records.\n",
    "supersedes_receipt_id": null,
    "superseded_at": null,
    "reissue_reason": null,
    "sent_at": "2026-10-04T22:20:00Z",
    "sent_via": "email",
    "created_at": "2026-10-04T22:20:00Z",
    "updated_at": "2026-10-04T22:20:00Z"
  }
}

import job

import_job.committed

data is the import job with created_ids; one contact.created or gift.created follows per record.

Example body
import_job.committed example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e37",
  "type": "import_job.committed",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d20",
    "source_name": "donors-2025.csv",
    "status": "committed",
    "column_mapping": {
      "First Name": "contact.first_name",
      "Last Name": "contact.last_name",
      "Email": "contact.email",
      "Amount": "gift.amount",
      "Date": "gift.received_on",
      "Fund": "gift.fund"
    },
    "row_count": 412,
    "error_count": 3,
    "created_ids": {
      "contacts": [
        "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d31"
      ],
      "gifts": [
        "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d32"
      ]
    },
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "created_at": "2026-10-05T13:00:00Z",
    "updated_at": "2026-10-05T13:10:00Z",
    "discarded_at": null,
    "discarded_by": null,
    "discard_reason": null
  }
}

import_job.rolled_back

data is the import job; its gifts were voided and its contacts removed.

Example body
import_job.rolled_back example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e38",
  "type": "import_job.rolled_back",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d20",
    "source_name": "donors-2025.csv",
    "status": "committed",
    "column_mapping": {
      "First Name": "contact.first_name",
      "Last Name": "contact.last_name",
      "Email": "contact.email",
      "Amount": "gift.amount",
      "Date": "gift.received_on",
      "Fund": "gift.fund"
    },
    "row_count": 412,
    "error_count": 3,
    "created_ids": {
      "contacts": [
        "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d31"
      ],
      "gifts": [
        "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d32"
      ]
    },
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "created_at": "2026-10-05T13:00:00Z",
    "updated_at": "2026-10-05T13:10:00Z",
    "discarded_at": null,
    "discarded_by": null,
    "discard_reason": null
  }
}

import_job.discarded

An import that was never imported was discarded, when someone asked (discard_reason: requested) or 30 days after its last change (expired), and its rows were cleared (E7 fix round, ADR-007). data is ImportJobDiscardedEvent: the import's id, status, reason and time only, never its file name or rows.

Example body
import_job.discarded example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e39",
  "type": "import_job.discarded",
  "created_at": "2026-10-05T13:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d21",
    "status": "discarded",
    "discard_reason": "requested",
    "discarded_at": "2026-10-05T13:30:00Z"
  }
}

donation form

donation_form.created

data is the full donation form as returned by GET (ADR-013).

Example body
donation_form.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e41",
  "type": "donation_form.created",
  "created_at": "2026-10-01T16:00:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d51",
    "slug": "winter-shelter",
    "title": "Keep the shelter warm this winter",
    "intro": "Every $35 gives one guest a warm bed, dinner and breakfast.",
    "logo_url": "https://maplestreetfoodbank.org/logo.png",
    "accent_color": "#1f6f4a",
    "preset_amounts_cents": [
      2500,
      5000,
      10000,
      25000
    ],
    "default_amount_cents": 5000,
    "allow_custom_amount": true,
    "min_amount_cents": 500,
    "max_amount_cents": 2500000,
    "frequencies": [
      "one_time",
      "month"
    ],
    "fund_ids": [
      "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09",
      "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d08"
    ],
    "campaign_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d10",
    "appeal_id": null,
    "thank_you_message": "Thank you for keeping the shelter warm.",
    "allow_tributes": true,
    "active": true,
    "created_at": "2026-10-01T16:00:00Z",
    "updated_at": "2026-10-02T16:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

donation_form.updated

data is the full donation form as returned by GET. Also sent for every active form when Stripe stops allowing charges on the organization's account and the form is turned off (active: false).

Example body
donation_form.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e42",
  "type": "donation_form.updated",
  "created_at": "2026-10-02T16:00:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d51",
    "slug": "winter-shelter",
    "title": "Keep the shelter warm this winter",
    "intro": "Every $35 gives one guest a warm bed, dinner and breakfast.",
    "logo_url": "https://maplestreetfoodbank.org/logo.png",
    "accent_color": "#1f6f4a",
    "preset_amounts_cents": [
      2500,
      5000,
      10000,
      25000
    ],
    "default_amount_cents": 5000,
    "allow_custom_amount": true,
    "min_amount_cents": 500,
    "max_amount_cents": 2500000,
    "frequencies": [
      "one_time",
      "month"
    ],
    "fund_ids": [
      "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09",
      "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d08"
    ],
    "campaign_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d10",
    "appeal_id": null,
    "thank_you_message": "Thank you for keeping the shelter warm.",
    "allow_tributes": true,
    "active": true,
    "created_at": "2026-10-01T16:00:00Z",
    "updated_at": "2026-10-02T16:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

donation_form.deleted

data is the row with deleted_at set (soft delete, ADR-005). A restore emits donation_form.updated with deleted_at null.

Example body
donation_form.deleted example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e43",
  "type": "donation_form.deleted",
  "created_at": "2026-10-05T16:00:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d51",
    "slug": "winter-shelter",
    "title": "Keep the shelter warm this winter",
    "intro": "Every $35 gives one guest a warm bed, dinner and breakfast.",
    "logo_url": "https://maplestreetfoodbank.org/logo.png",
    "accent_color": "#1f6f4a",
    "preset_amounts_cents": [
      2500,
      5000,
      10000,
      25000
    ],
    "default_amount_cents": 5000,
    "allow_custom_amount": true,
    "min_amount_cents": 500,
    "max_amount_cents": 2500000,
    "frequencies": [
      "one_time",
      "month"
    ],
    "fund_ids": [
      "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09",
      "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d08"
    ],
    "campaign_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d10",
    "appeal_id": null,
    "thank_you_message": "Thank you for keeping the shelter warm.",
    "allow_tributes": true,
    "active": true,
    "created_at": "2026-10-01T16:00:00Z",
    "updated_at": "2026-10-05T16:00:00Z",
    "deleted_at": "2026-10-05T16:00:00Z",
    "deleted_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02"
  }
}

recurring donation

recurring_donation.created

Sent when a donor starts a monthly or yearly gift on a donation form (ADR-013). data is the full recurring donation as returned by GET. Each paid charge then arrives as gift.created with its recurring_donation_id.

Example body
recurring_donation.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e44",
  "type": "recurring_donation.created",
  "created_at": "2026-10-04T22:15:03Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d52",
    "contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "contact_name": "Aiko Nakamura",
    "fund_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09",
    "fund_name": "Winter Shelter Fund",
    "campaign_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d10",
    "appeal_id": null,
    "donation_form_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d51",
    "amount_cents": 2500,
    "interval": "month",
    "status": "active",
    "cancel_at_period_end": false,
    "current_period_end": "2026-12-04T22:15:00Z",
    "started_at": "2026-10-04T22:15:00Z",
    "canceled_at": null,
    "stripe_subscription_id": "sub_1QAbCd2eZvKYlo2CfGhIjKlM",
    "stripe_customer_id": "cus_QAbCdEfGhIjKlM",
    "tribute_type": "in_memory_of",
    "tribute_name": "Grace Okafor",
    "created_at": "2026-10-04T22:15:03Z",
    "updated_at": "2026-11-04T22:15:04Z"
  }
}

recurring_donation.updated

Sent on cancel and resume, and when Stripe reports a change: past due, canceled, a new period end, amount or interval. data is the full recurring donation as returned by GET.

Example body
recurring_donation.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e45",
  "type": "recurring_donation.updated",
  "created_at": "2026-11-04T22:15:04Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d52",
    "contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "contact_name": "Aiko Nakamura",
    "fund_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d09",
    "fund_name": "Winter Shelter Fund",
    "campaign_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d10",
    "appeal_id": null,
    "donation_form_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d51",
    "amount_cents": 2500,
    "interval": "month",
    "status": "active",
    "cancel_at_period_end": false,
    "current_period_end": "2026-12-04T22:15:00Z",
    "started_at": "2026-10-04T22:15:00Z",
    "canceled_at": null,
    "stripe_subscription_id": "sub_1QAbCd2eZvKYlo2CfGhIjKlM",
    "stripe_customer_id": "cus_QAbCdEfGhIjKlM",
    "tribute_type": "in_memory_of",
    "tribute_name": "Grace Okafor",
    "created_at": "2026-10-04T22:15:03Z",
    "updated_at": "2026-11-04T22:15:04Z"
  }
}

welcome enrollment

welcome_enrollment.created

A new donor was enrolled in the welcome series (ADR-015). data is the enrolment as returned by GET.

Example body
welcome_enrollment.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e47",
  "type": "welcome_enrollment.created",
  "created_at": "2026-10-05T14:45:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d70",
    "contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "first_gift_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d13",
    "status": "active",
    "next_step": "impact_story",
    "next_step_at": "2026-10-19T16:00:00Z",
    "stop_reason": null,
    "enrolled_at": "2026-10-05T14:45:00Z",
    "completed_at": null,
    "stopped_at": null,
    "steps": [
      {
        "step": "thank_you",
        "outcome": "queued",
        "reason": null,
        "email_message_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d71",
        "email_status": "sent",
        "email_sent_at": "2026-10-05T14:45:41Z",
        "email_error": null,
        "created_at": "2026-10-05T14:45:00Z"
      }
    ],
    "created_at": "2026-10-05T14:45:00Z",
    "updated_at": "2026-10-05T14:45:00Z"
  }
}

welcome_enrollment.updated

An enrolment moved on: a step was queued or skipped, it completed, or it was stopped (by staff or a check at send time) or resumed. data is the enrolment as returned by GET, with its step outcomes.

Example body
welcome_enrollment.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e48",
  "type": "welcome_enrollment.updated",
  "created_at": "2026-10-05T14:45:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d70",
    "contact_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d05",
    "first_gift_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d13",
    "status": "active",
    "next_step": "impact_story",
    "next_step_at": "2026-10-19T16:00:00Z",
    "stop_reason": null,
    "enrolled_at": "2026-10-05T14:45:00Z",
    "completed_at": null,
    "stopped_at": null,
    "steps": [
      {
        "step": "thank_you",
        "outcome": "queued",
        "reason": null,
        "email_message_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d71",
        "email_status": "sent",
        "email_sent_at": "2026-10-05T14:45:41Z",
        "email_error": null,
        "created_at": "2026-10-05T14:45:00Z"
      }
    ],
    "created_at": "2026-10-05T14:45:00Z",
    "updated_at": "2026-10-05T14:45:00Z"
  }
}

email template

email_template.created

data is the template as GET returns it.

Example body
email_template.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c8e01",
  "type": "email_template.created",
  "created_at": "2026-10-13T14:05:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c8a01",
    "name": "Spring newsletter",
    "content": {
      "subject": [
        {
          "type": "merge",
          "field": "first_name"
        },
        {
          "type": "text",
          "text": ", spring at the pantry"
        }
      ],
      "preheader": [
        {
          "type": "text",
          "text": "What your gifts did this winter"
        }
      ],
      "blocks": [
        {
          "type": "heading",
          "runs": [
            {
              "type": "text",
              "text": "Spring at the pantry"
            }
          ]
        },
        {
          "type": "text",
          "runs": [
            {
              "type": "text",
              "text": "Thank you for "
            },
            {
              "type": "merge",
              "field": "last_gift_amount",
              "fallback": "your support"
            },
            {
              "type": "text",
              "text": ". Read "
            },
            {
              "type": "text",
              "text": "the whole story",
              "link": "https://maplestreetfoodbank.org/spring"
            },
            {
              "type": "text",
              "text": "."
            }
          ]
        },
        {
          "type": "image",
          "src": "https://maplestreetfoodbank.org/img/volunteers.jpg",
          "alt": "Volunteers sorting cans",
          "width": 600
        },
        {
          "type": "donation_link",
          "label": "Give again",
          "donation_form_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e10",
          "audience": "donors"
        }
      ]
    },
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "updated_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "created_at": "2026-10-12T15:00:00Z",
    "updated_at": "2026-10-12T15:30:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

email_template.updated

data is the template as GET returns it, after a change or a restore.

Example body
email_template.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c8e02",
  "type": "email_template.updated",
  "created_at": "2026-10-13T14:05:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c8a01",
    "name": "Spring newsletter",
    "content": {
      "subject": [
        {
          "type": "merge",
          "field": "first_name"
        },
        {
          "type": "text",
          "text": ", spring at the pantry"
        }
      ],
      "preheader": [
        {
          "type": "text",
          "text": "What your gifts did this winter"
        }
      ],
      "blocks": [
        {
          "type": "heading",
          "runs": [
            {
              "type": "text",
              "text": "Spring at the pantry"
            }
          ]
        },
        {
          "type": "text",
          "runs": [
            {
              "type": "text",
              "text": "Thank you for "
            },
            {
              "type": "merge",
              "field": "last_gift_amount",
              "fallback": "your support"
            },
            {
              "type": "text",
              "text": ". Read "
            },
            {
              "type": "text",
              "text": "the whole story",
              "link": "https://maplestreetfoodbank.org/spring"
            },
            {
              "type": "text",
              "text": "."
            }
          ]
        },
        {
          "type": "image",
          "src": "https://maplestreetfoodbank.org/img/volunteers.jpg",
          "alt": "Volunteers sorting cans",
          "width": 600
        },
        {
          "type": "donation_link",
          "label": "Give again",
          "donation_form_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e10",
          "audience": "donors"
        }
      ]
    },
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "updated_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "created_at": "2026-10-12T15:00:00Z",
    "updated_at": "2026-10-12T15:30:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

email_template.deleted

data is the template as GET returns it, deleted_at set.

Example body
email_template.deleted example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c8e03",
  "type": "email_template.deleted",
  "created_at": "2026-10-13T14:05:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c8a01",
    "name": "Spring newsletter",
    "content": {
      "subject": [
        {
          "type": "merge",
          "field": "first_name"
        },
        {
          "type": "text",
          "text": ", spring at the pantry"
        }
      ],
      "preheader": [
        {
          "type": "text",
          "text": "What your gifts did this winter"
        }
      ],
      "blocks": [
        {
          "type": "heading",
          "runs": [
            {
              "type": "text",
              "text": "Spring at the pantry"
            }
          ]
        },
        {
          "type": "text",
          "runs": [
            {
              "type": "text",
              "text": "Thank you for "
            },
            {
              "type": "merge",
              "field": "last_gift_amount",
              "fallback": "your support"
            },
            {
              "type": "text",
              "text": ". Read "
            },
            {
              "type": "text",
              "text": "the whole story",
              "link": "https://maplestreetfoodbank.org/spring"
            },
            {
              "type": "text",
              "text": "."
            }
          ]
        },
        {
          "type": "image",
          "src": "https://maplestreetfoodbank.org/img/volunteers.jpg",
          "alt": "Volunteers sorting cans",
          "width": 600
        },
        {
          "type": "donation_link",
          "label": "Give again",
          "donation_form_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e10",
          "audience": "donors"
        }
      ]
    },
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "updated_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "created_at": "2026-10-12T15:00:00Z",
    "updated_at": "2026-10-12T15:30:00Z",
    "deleted_at": "2026-10-13T09:00:00Z",
    "deleted_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02"
  }
}

email send

email_send.created

data is the send as GET returns it.

Example body
email_send.created example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c8e04",
  "type": "email_send.created",
  "created_at": "2026-10-13T14:05:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c8b01",
    "name": "Spring newsletter 2026",
    "topic": "newsletter",
    "content": {
      "subject": [
        {
          "type": "merge",
          "field": "first_name"
        },
        {
          "type": "text",
          "text": ", spring at the pantry"
        }
      ],
      "preheader": [
        {
          "type": "text",
          "text": "What your gifts did this winter"
        }
      ],
      "blocks": [
        {
          "type": "heading",
          "runs": [
            {
              "type": "text",
              "text": "Spring at the pantry"
            }
          ]
        },
        {
          "type": "text",
          "runs": [
            {
              "type": "text",
              "text": "Thank you for "
            },
            {
              "type": "merge",
              "field": "last_gift_amount",
              "fallback": "your support"
            },
            {
              "type": "text",
              "text": ". Read "
            },
            {
              "type": "text",
              "text": "the whole story",
              "link": "https://maplestreetfoodbank.org/spring"
            },
            {
              "type": "text",
              "text": "."
            }
          ]
        },
        {
          "type": "image",
          "src": "https://maplestreetfoodbank.org/img/volunteers.jpg",
          "alt": "Volunteers sorting cans",
          "width": 600
        },
        {
          "type": "donation_link",
          "label": "Give again",
          "donation_form_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e10",
          "audience": "donors"
        }
      ]
    },
    "render_version": 1,
    "template_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c8a01",
    "saved_list_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7c01",
    "snapshot_id": null,
    "campaign_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d07",
    "appeal_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d08",
    "household_option": "per_person",
    "track_clicks": false,
    "attribution_code": "q4m2x7k3p9ab",
    "status": "draft",
    "content_hash": null,
    "members_hash": null,
    "review": null,
    "reviewed_at": null,
    "reviewed_by": null,
    "approved_at": null,
    "approved_by": null,
    "scheduled_for": null,
    "started_at": null,
    "completed_at": null,
    "paused_at": null,
    "pause_reason": null,
    "resumed_at": null,
    "canceled_at": null,
    "canceled_by": null,
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "api_key_id": null,
    "created_at": "2026-10-12T16:00:00Z",
    "updated_at": "2026-10-12T16:00:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

email_send.updated

data is the send as GET returns it, after an edit, a review, an approval, a status change (started, paused, resumed, canceled, sent) or a restore. Outbox rows and click counts emit nothing.

Example body
email_send.updated example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c8e05",
  "type": "email_send.updated",
  "created_at": "2026-10-13T14:05:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c8b01",
    "name": "Spring newsletter 2026",
    "topic": "newsletter",
    "content": {
      "subject": [
        {
          "type": "merge",
          "field": "first_name"
        },
        {
          "type": "text",
          "text": ", spring at the pantry"
        }
      ],
      "preheader": [
        {
          "type": "text",
          "text": "What your gifts did this winter"
        }
      ],
      "blocks": [
        {
          "type": "heading",
          "runs": [
            {
              "type": "text",
              "text": "Spring at the pantry"
            }
          ]
        },
        {
          "type": "text",
          "runs": [
            {
              "type": "text",
              "text": "Thank you for "
            },
            {
              "type": "merge",
              "field": "last_gift_amount",
              "fallback": "your support"
            },
            {
              "type": "text",
              "text": ". Read "
            },
            {
              "type": "text",
              "text": "the whole story",
              "link": "https://maplestreetfoodbank.org/spring"
            },
            {
              "type": "text",
              "text": "."
            }
          ]
        },
        {
          "type": "image",
          "src": "https://maplestreetfoodbank.org/img/volunteers.jpg",
          "alt": "Volunteers sorting cans",
          "width": 600
        },
        {
          "type": "donation_link",
          "label": "Give again",
          "donation_form_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e10",
          "audience": "donors"
        }
      ]
    },
    "render_version": 1,
    "template_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c8a01",
    "saved_list_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7c01",
    "snapshot_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7d01",
    "campaign_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d07",
    "appeal_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d08",
    "household_option": "per_person",
    "track_clicks": false,
    "attribution_code": "q4m2x7k3p9ab",
    "status": "scheduled",
    "content_hash": "5b2f0a9c4d8e1f3a6b7c9d0e2f4a6b8c0d1e3f5a7b9c2d4e6f8a0b1c3d5e7f90",
    "members_hash": "0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "review": {
      "list_name": "Lapsed shelter donors",
      "list_version": 3,
      "snapshot_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7d01",
      "member_count": 1240,
      "eligible_count": 1102,
      "skipped": [
        {
          "reason": "not_subscribed",
          "words": "Not emailed: the contact hasn't subscribed to email updates.",
          "count": 120
        },
        {
          "reason": "no_address",
          "words": "Not emailed: the contact has no email address.",
          "count": 18
        }
      ],
      "subscribed_since_last_send": {
        "since": "2026-09-01T15:00:00Z",
        "imports": 0,
        "staff": 4
      },
      "address_changed_since_last_send": {
        "since": "2026-09-01T15:00:00Z",
        "staff": 2,
        "api_key": 0,
        "import": 0
      },
      "link_hosts": [
        "maplestreetfoodbank.org"
      ],
      "image_hosts": [
        "maplestreetfoodbank.org"
      ],
      "sending_domain": "mail.maplestreetfoodbank.org",
      "missing_settings": [],
      "dmarc_warning": null,
      "largest_email_bytes": 5120
    },
    "reviewed_at": "2026-10-13T14:00:00Z",
    "reviewed_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "approved_at": "2026-10-13T14:05:00Z",
    "approved_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d03",
    "scheduled_for": "2026-10-13T14:15:00Z",
    "started_at": null,
    "completed_at": null,
    "paused_at": null,
    "pause_reason": null,
    "resumed_at": null,
    "canceled_at": null,
    "canceled_by": null,
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "api_key_id": null,
    "created_at": "2026-10-12T16:00:00Z",
    "updated_at": "2026-10-13T14:05:00Z",
    "deleted_at": null,
    "deleted_by": null
  }
}

email_send.deleted

data is the draft as GET returns it, deleted_at set.

Example body
email_send.deleted example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c8e06",
  "type": "email_send.deleted",
  "created_at": "2026-10-13T14:05:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c8b01",
    "name": "Spring newsletter 2026",
    "topic": "newsletter",
    "content": {
      "subject": [
        {
          "type": "merge",
          "field": "first_name"
        },
        {
          "type": "text",
          "text": ", spring at the pantry"
        }
      ],
      "preheader": [
        {
          "type": "text",
          "text": "What your gifts did this winter"
        }
      ],
      "blocks": [
        {
          "type": "heading",
          "runs": [
            {
              "type": "text",
              "text": "Spring at the pantry"
            }
          ]
        },
        {
          "type": "text",
          "runs": [
            {
              "type": "text",
              "text": "Thank you for "
            },
            {
              "type": "merge",
              "field": "last_gift_amount",
              "fallback": "your support"
            },
            {
              "type": "text",
              "text": ". Read "
            },
            {
              "type": "text",
              "text": "the whole story",
              "link": "https://maplestreetfoodbank.org/spring"
            },
            {
              "type": "text",
              "text": "."
            }
          ]
        },
        {
          "type": "image",
          "src": "https://maplestreetfoodbank.org/img/volunteers.jpg",
          "alt": "Volunteers sorting cans",
          "width": 600
        },
        {
          "type": "donation_link",
          "label": "Give again",
          "donation_form_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e10",
          "audience": "donors"
        }
      ]
    },
    "render_version": 1,
    "template_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c8a01",
    "saved_list_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c7c01",
    "snapshot_id": null,
    "campaign_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d07",
    "appeal_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d08",
    "household_option": "per_person",
    "track_clicks": false,
    "attribution_code": "q4m2x7k3p9ab",
    "status": "draft",
    "content_hash": null,
    "members_hash": null,
    "review": null,
    "reviewed_at": null,
    "reviewed_by": null,
    "approved_at": null,
    "approved_by": null,
    "scheduled_for": null,
    "started_at": null,
    "completed_at": null,
    "paused_at": null,
    "pause_reason": null,
    "resumed_at": null,
    "canceled_at": null,
    "canceled_by": null,
    "created_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02",
    "api_key_id": null,
    "created_at": "2026-10-12T16:00:00Z",
    "updated_at": "2026-10-12T16:00:00Z",
    "deleted_at": "2026-10-13T09:00:00Z",
    "deleted_by": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d02"
  }
}

webhook

webhook.test

Sent by POST /v1/webhook-subscriptions/{id}/test.

Example body
webhook.test example
{
  "id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6e39",
  "type": "webhook.test",
  "created_at": "2026-10-05T14:30:00Z",
  "organization_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d01",
  "data": {
    "subscription_id": "3f9d2c1a-7b4e-4f6a-9c2d-1e5b8a7c6d22",
    "message": "This is a test delivery from Operation Charity."
  }
}