Webhooks

Infinite Giving sends HTTP POSTs to your webhook URL when donations are created or updated for connected organizations. Setup (OAuth + connect) is covered in Partnerships.

Events

Event When it fires
donation:created A donation record is created.
donation:updated Status or material fields change after creation (e.g. processing, complete, failed, canceled).

There is no separate donation:completed event — completion arrives as donation:updated with status: "complete".

Delivery format

{
  "id": "A1B2C3D4E",
  "event": "donation:created",
  "created_at": "2026-06-23T21:00:00.000Z",
  "data": { }
}

Headers

Header Description
X-IG-Webhook-Id Unique delivery id (same as envelope id). Use to dedupe.
X-IG-Event Event type, e.g. donation:created.
X-IG-Timestamp Unix timestamp (seconds) used in the signature.
X-IG-Signature sha256=<hex> HMAC signature.

Payload (data)

Both events use the same donation snapshot:

Field Type Description
id string Donation id
object string Always "donation"
organization object { id, name }
type string | null Donation type (e.g. stock, crypto)
status string | null e.g. pledged, processing, complete, failed, canceled
identifier string | null Ticker, asset symbol, or similar
quantity number | null Units donated
tax_deductible_amount number | null Tax-deductible amount
recognition string | null How the donor wishes to be credited: public, private, or anonymous. Not a name.
memo string | null Memo
source string | null Meaning depends on type: the donor's brokerage for stock, the Stripe PaymentIntent id for currency, the wallet address for crypto.
cancelation_reason string | null Set when canceled
campaign object | null { id, name }
designation object | null { id, name }
trade object | null Sale details when available (see below)
donor object { first_name, last_name, email }
created_at string | null ISO-8601
updated_at string | null ISO-8601

trade object

Field Type Description
executed_at string | null ISO-8601
settled_at string | null ISO-8601
fill_price number | null Price per share of the sale. null until the sale fills.
fill_quantity number | null Shares sold. null until the sale fills.
sec_fee number
postedge_fee number
liquidity_fee number
orf_fee number
nasd_fee number
misc_fee number

Gross proceeds are fill_price * fill_quantity; subtract the fee fields for the net. This will not equal tax_deductible_amount, which for a stock donation is valued on the date the shares were received rather than the date they were sold, and is null until the donation reaches processing. Timing differs by type: currency donations carry a tax_deductible_amount from creation, while stock, crypto and DAF donations stay null until valued.

Example: donation:created

{
  "id": "A1B2C3D4E",
  "event": "donation:created",
  "created_at": "2026-06-23T21:00:00.000Z",
  "data": {
    "id": "DON1234567",
    "object": "donation",
    "organization": { "id": "ORGABCDEFG", "name": "Example Nonprofit" },
    "type": "stock",
    "status": "pledged",
    "identifier": "AAPL",
    "quantity": 10,
    "tax_deductible_amount": null,
    "recognition": "public",
    "memo": null,
    "source": "Fidelity",
    "cancelation_reason": null,
    "campaign": { "id": "CAM1234567", "name": "Annual Fund" },
    "designation": null,
    "trade": null,
    "donor": {
      "first_name": "Jane",
      "last_name": "Donor",
      "email": "jane@example.com"
    },
    "created_at": "2026-06-23T21:00:00.000Z",
    "updated_at": "2026-06-23T21:00:00.000Z"
  }
}

Example: donation:updated (complete)

{
  "id": "F6G7H8I9J",
  "event": "donation:updated",
  "created_at": "2026-06-24T14:30:00.000Z",
  "data": {
    "id": "DON1234567",
    "object": "donation",
    "organization": { "id": "ORGABCDEFG", "name": "Example Nonprofit" },
    "type": "stock",
    "status": "complete",
    "identifier": "AAPL",
    "quantity": 10,
    "tax_deductible_amount": 2143.5,
    "recognition": "public",
    "memo": null,
    "source": "Fidelity",
    "cancelation_reason": null,
    "campaign": { "id": "CAM1234567", "name": "Annual Fund" },
    "designation": null,
    "trade": {
      "executed_at": "2026-06-24T13:00:00.000Z",
      "settled_at": "2026-06-24T14:00:00.000Z",
      "fill_price": 215.0,
      "fill_quantity": 10,
      "sec_fee": 0.02,
      "postedge_fee": 0,
      "liquidity_fee": 0,
      "orf_fee": 0,
      "nasd_fee": 0,
      "misc_fee": 0
    },
    "donor": {
      "first_name": "Jane",
      "last_name": "Donor",
      "email": "jane@example.com"
    },
    "created_at": "2026-06-23T21:00:00.000Z",
    "updated_at": "2026-06-24T14:30:00.000Z"
  }
}

Verify the signature

Compute HMAC-SHA256 of `${timestamp}.${rawBody}` with your signing secret (from kickoff). Compare to X-IG-Signature (sha256= + hex digest).

const crypto = require('crypto')

function verify(secret, timestamp, rawBody, signatureHeader) {
  const expected =
    'sha256=' +
    crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')
  return expected === signatureHeader
}

Use the raw request body bytes — do not re-serialize JSON before verifying.

Retries and idempotency

  • Respond with 2xx quickly; process asynchronously.
  • Failed deliveries retry up to 5 times with backoff (~1m, 5m, 30m, 2h, 6h).
  • Treat X-IG-Webhook-Id as an idempotency key — the same delivery may be sent more than once.