Skip to main content

Campaign Object Mapping

This document details the real mapping used by NeonIntegration::generateCampaignParams() (push) and SyncNeon::importCampaigns() (pull).

Core Campaign Mapping (Push)

URLs use the organization’s real donor-portal base URL (donorPortalBaseUrl()), not a hardcoded app.wegive.com — the actual domain varies by organization (custom domains are supported).
There is no description field sent — Neon’s pageContent field exists for this purpose but is commented out in the push code and never populated. Fund, purpose, parent-campaign (hierarchy), and social-fundraising configuration are all commented-out placeholders too — none of them are ever sent, despite Neon’s platform supporting all of them.

Statistics — Pull Behavior Is Much Narrower Than It Looks

importCampaigns() does not import statistics at all. It only reads id, name, and status from each Neon campaign in the response — statistics.donationAmount, donationCount, eventRegistrationAmount, eventRegistrationCount, grandTotal, pledgeAmount, pledgeCount are never read or written to the WeGive Campaign record by this method, despite Neon’s own campaign-list response including all of them. Any statistics fields you see referenced elsewhere (seeded NeonMappingRule defaults) have no actual consumer — this is a known issue, the same pull-never-applies-mapping-rules gap documented for accounts/donations.
importCampaigns() also never updates an existing campaign — it only creates a new WeGive Campaign when no neon_id match is found. If a campaign’s name, goal, dates, or status change in Neon after the initial import, those changes are never reflected back in WeGive on subsequent pulls.
Campaign push on the generateCampaignParams() side also has no statistics key sent at all (it’s a fully commented-out block) — statistics genuinely are Neon-computed-and-owned data, never overwritten by WeGive on push. The gap is entirely on the pull side, where the data that exists in Neon’s response simply isn’t read.

Status Mapping

Status Synchronization

  • Push: deleted campaigns push as INACTIVE
  • Pull: importCampaigns() skips any Neon campaign with status == 'INACTIVE' entirely — it’s not imported at all, not even as an inactive WeGive campaign
  • There is no WeGive-side reactivation flow driven by a Neon-side status flip back to ACTIVE — since pull never updates existing campaigns (see above), a status change in Neon on an already-imported campaign has no effect on the WeGive record either way

Custom Field Mapping

The default seeded NeonMappingRule records for campaigns include statistics fields (statistics.donationAmount → total_donated, etc.) — but since importCampaigns() doesn’t consult NeonMappingRule at all (same as every other Neon object type on pull), these defaults have no effect. This is a known issue.

Adding Custom Mappings

Even for a custom mapping added here, it would only ever apply on push (generateCampaignParams()) — the pull-side gap applies to custom mappings the same as the defaults.

Synchronization Behavior

Campaign Creation/Update (Push)

  1. If neon_id already set → PUT /campaigns/{neon_id} (update); otherwise → POST /campaigns (create)
  2. There is no name-uniqueness check or duplicate-prevention logic performed by WeGive before push — any rejection would come from Neon’s own API

Import from Neon CRM (Pull)

  1. GET /campaigns — a flat, unfiltered list fetch, not a search/pagination call
  2. Skip any campaign with status == 'INACTIVE'
  3. If no WeGive Campaign exists with a matching neon_id, create a new one with just name and neon_id — goal, dates, and status are not set on the newly created WeGive campaign either, only on the Neon side of the relationship
  4. If a matching campaign already exists, nothing happens — no field is ever updated on subsequent pulls

Campaign-Donation Relationship

  • Donation push includes campaign.id/campaign.name/campaign.status only if the campaign already has a neon_id — there is no “sync the campaign first if it’s missing” orchestration inside donation push. A donation for an unsynced campaign simply pushes with campaign: null.
  • Recurring-donation push has no campaign field at all — see Recurring Donation Mapping

Error Handling

There is no automatic retry, date-order validation, or goal-value validation performed by WeGive for campaign push. A failed push simply fails — check Sentry for the specific Neon API error. On pull, a failed GET /campaigns request is silently skipped ($campaignRequest->failed() → early return, no exception, no retry).

API Examples

Creating a Campaign (real payload shape)

No statistics, fund, purpose, parentCampaign, or socialFundraising keys are sent — confirmed absent from the real payload. The base URL reflects the organization’s own donor-portal domain, not a fixed WeGive domain.

Fetching Campaigns (Pull)

This is a flat list fetch with no search filters or pagination parameters — unlike donor/donation pull, which use POST .../search with date filters. A large Neon account with many campaigns would return them all in one call.

Best Practices

  • Don’t expect campaign statistics (donation totals, pledge counts) to ever appear on the WeGive side via this integration — they’re Neon-only data that never gets pulled in
  • Don’t expect a campaign edit made in Neon (renaming, changing goal/dates) to propagate back to WeGive — pull only creates, never updates
  • If a campaign needs its goal/dates corrected on both sides, make the change directly in WeGive and let it push — don’t rely on Neon as the source of truth for anything beyond the initial name