Skip to main content

Donation Object Mapping

This document details the real mapping used by NeonIntegration::generateDonationParams().
The payment block is only sent on donation CREATE, never on UPDATE. generateDonationParams()’s payments array is gated by $transaction->neon_payment_id ? null : [...] — if a payment id is already stored (meaning this transaction was previously created in Neon), the entire payments key is sent as null on every subsequent update call. Updating a donation’s tribute, fee, or campaign never re-sends or corrects the associated payment/tender data.

Core Donation Mapping

Financial Information

There is no currency field sent at all — no USD default, no currency key in the payload whatsoever.

Timing Information

receivedDate is not actually a documented top-level key in the params array — the code computes $renderedData['payout']['paid_at'] on the rendered webhook resource, which only feeds into the custom-field-mapping resolution step (see below), not a hardcoded top-level field. It only reaches Neon if a NeonMappingRule maps payout.paid_at to some Neon path.

Attribution

anonymousType sends the raw WeGive boolean value directly — not a translated "ANONYMOUS"/"NOT_ANONYMOUS" string as a prior version of this page and its JSON examples implied. Whether Neon’s API correctly interprets a raw boolean where it likely expects a string enum isn’t something WeGive verifies.
If a transaction’s campaign hasn’t itself synced to Neon yet (no neon_id on the Campaign record), the entire campaign key is sent as null — there’s no “sync campaign first” orchestration inside generateDonationParams() itself.

Tribute Information

tribute_type is not a real WeGive field consulted anywhere in this mapping — the value is hardcoded regardless of what kind of tribute (memorial vs. honor) the donation actually represents.

Integration Fields

Payment Processing

Tender Type Mapping

There is no distinct Cash, Stock/Securities, In-Kind, PayPal, Wire, or Gift Certificate tender type ever sent — only Credit Card Offline and Check, ever, regardless of what Neon’s platform itself supports.

Credit Card Processing (source_type = 'card', create only)

creditCardOffline.billingAddress is hardcoded null — no billing address is ever included on the payment object itself (a stripped copy exists separately, on the donor account push — see Account Mapping).

Bank Transfer Processing (source_type = 'bank', create only)

If source is null for any reason, getLastFour() returns the literal string '0000' rather than throwing — meaning a transaction with no attached payment source would push a donation with a fabricated-looking last-4 of 0000 rather than surfacing an error.

Custom Field Mapping

The default seeded NeonMappingRule records for donations:
These duplicate what generateDonationParams() already hardcodes directly — mapping rules only matter for custom additions beyond this default set, and (per the standing pull-side gap) only ever apply on push. generateDonationParams()’s own mapping-rule loop explicitly excludes level = 'import' rules (->where('level', '!=', 'import')) — so even the loop itself is intentionally push-scoped; it’s not that push accidentally also applies import-only rules. This is a known issue on the pull side.

Adding Custom Mappings

Synchronization Behavior

Donation Creation/Update (Push)

  1. If neon_id already set → PUT /donations/{neon_id} (update); otherwise → POST /donations (create)
  2. Payment data is only included on the very first push — see the warning at the top of this page
  3. There is no “verify donor account exists first” orchestration step inside donation push itself — if the donor’s neon_account_id isn’t set, the push simply sends accountId: null and Neon’s own API would reject it
  4. There is no “sync campaign first” step either — same pattern, campaign is sent null if the campaign hasn’t separately synced yet
There is no automatic retry, and no distinction between “modifiable” vs. “immutable” fields enforced by WeGive — a failed update simply fails with whatever error Neon’s API returns.

Import from Neon CRM (Pull)

  1. POST /donations/search with a last-modified-date filter and an Email NOT_BLANK filter
  2. Matches the donation’s Neon account to an already-imported WeGive donor
  3. Creates a new WeGive Transaction for any Neon donation not already correlated by neon_id
Pull does not use NeonMappingRule at all — same gap as accounts and campaigns. This is a known issue.

API Examples

Creating a Donation (real payload shape)

This reflects the real payload shape: anonymousType as a raw boolean, tribute.type always "Honor", no currency key, and campaign/check or creditCardOffline sent as null when not applicable rather than omitted.

Updating a Donation (payments omitted)

Searching Donations (Pull)

Error Handling

There is no automatic retry, amount range validation, or “account creation on missing account” recovery logic performed by WeGive for donation push. A failed push simply fails — check Sentry for the specific Neon API error.

Best Practices

  • Understand that editing a donation’s tribute/fee/campaign after initial creation will not re-push its payment/tender details — if the payment record needs correcting in Neon, it has to be corrected directly in Neon, not via a WeGive-side re-sync
  • Don’t expect tribute.type to distinguish memorial vs. honor gifts — it’s always "Honor"
  • A donation with no campaign correlation in Neon yet will push with campaign: null — sync/verify the campaign separately first if attribution matters