> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wegive.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Planning Center Data Mapping Overview

> Comprehensive mapping between Planning Center modules and WeGive donor platform data structures

# Data Mapping Overview

This document provides an overview of how the WeGive donor platform maps to Planning Center's People and Giving modules. The real mapping is narrower than a generic CRM sync — a handful of fields per object, no custom fields, no household/family sync, and no recurring-gift sync.

## Object Mapping Categories

The integration maps objects across two Planning Center modules:

1. **People Module** — Person, plus embedded email/phone/address contact info
2. **Giving Module** — Donation, Batch, Fund, PaymentSource, Designation

<Note>
  There is no Planning Center RecurringGift sync in this integration — `pushTransaction()` (the only place a Transaction touches the API) is a literal no-op; Planning Center recurring gifts are only ever read incidentally as a correlation reference on pulled donations, never pushed or independently synced.
</Note>

## Core Object Mappings

### People Module Mapping

| Planning Center Object | WeGive Object | Sync Direction |
| - | - | - |
| Person | Donor | Push (real-time) + Pull (daily) |
| Email / PhoneNumber / Address | Embedded contact fields on Donor | Push (create-only, on donor create/update) + Pull |

### Giving Module Mapping

| Planning Center Object | WeGive Object | Sync Direction |
| - | - | - |
| Donation | Transaction | Push (daily batch only) + Pull (daily) |
| Batch | (internal grouping only, no WeGive model) | Push Only |
| Fund | Fund | Push (find-or-create, real-time within batch build) + Pull (daily) |
| PaymentSource | (fixed single "WeGive" source, no WeGive model) | Push Only |
| Designation | (created inline per donation, no WeGive model) | Push Only, one per donation |

## Planning Center Object Details

### Person Object

**Fields actually synced:**

* `first_name`, `last_name` (falls back to literal `"FNU"`/`"LNU"` if blank on push)
* Up to 3 emails (position-based: `email_1`/`email_2`/`email_3`)
* Up to 2 phone numbers (position-based: `mobile_phone`/`office_phone`)
* One mailing address
* `status` (pulled only, maps to the donor's `enabled` flag)

<Note>
  There's no birthdate, anniversary, gender, middle-name-on-pull, suffix, or household/family-relationship mapping. Middle name is pushed but not pulled back.
</Note>

### Donation Object

**Fields actually synced:**

* `amount_cents`, `payment_method` (`card` or `ach`, from the transaction's `source_type`), `received_at`/`created_at`, `person_id`
* Exactly one `Designation` (fund + amount) per donation — split/multi-fund gifts aren't supported

### Batch Object

* `description` — the batch's own display name, format `WeGive Import MM/DD/YYYY`
* Batches are found-or-created once per push run (one batch per day, not one per donation) and auto-committed after processing

### Fund Object

**Fields actually synced:**

* `name` (push and pull), `description` (push on create only, pull), `visibility` (push always sets `everywhere`; pull's `hidden` value soft-deletes the WeGive fund)
* `color_identifier` is set to a fixed value of `1` on create, never read back

<Note>
  No fund goal amount, category, hierarchy/parent-child structure, `is_active`/active-status sync, or analytics fields are mapped — WeGive's `funds.active` column exists but isn't pushed to or read from Planning Center at all.
</Note>

### PaymentSource Object

A single Planning Center payment source named "WeGive" is found or created once and reused for every transaction — there's no per-payment-method or per-organization payment source.

## WeGive Object Mappings

### Donor ↔ Person

| WeGive Field | Planning Center Field | Direction | Notes |
| - | - | - | - |
| `planning_center_id` | `id` | Both | Correlation ID; the *only* matching key |
| `first_name` / `last_name` | `first_name` / `last_name` | Both | `"FNU"`/`"LNU"` placeholder on push if blank, restored to `null` on pull |
| `middle_name` | `middle_name` | Push only | |
| `email_1`/`email_2`/`email_3` | Email records (positional) | Both | |
| `mobile_phone`/`office_phone` | PhoneNumber records (positional) | Both | |
| mailing address fields | Address record | Both | |
| `enabled` | `status` (pulled only) | Pull only | |

### Transaction ↔ Donation

| WeGive Field | Planning Center Field | Direction | Notes |
| - | - | - | - |
| `planning_center_id` | `id` | Both | Correlation ID |
| `amount` | `amount_cents` | Both | |
| `source_type` | `payment_method` | Push only | `card` or `ach` only |
| `created_at`/`succeeded_at` | `received_at` | Both | |
| `owner` | `person_id` | Push; pulled donation requires a matched donor or the import throws `'No owner found'` | |
| `fund` | Designation → fund | Both | One fund per transaction, no split support |
| `scheduledDonation` | Planning Center recurring-donation correlation (pull only) | Pull only | Read-only reference; not an independent sync |

**Push side note:** a pulled donation whose transaction has a `correlation_id` (WeGive-processed payment) or is still future-dated-pending keeps its WeGive-owned `amount`/allocations — a Planning Center-side edit doesn't overwrite the real payment amount (this is a guarded, fixed behavior, not an open issue).

### Fund ↔ Fund

| WeGive Field | Planning Center Field | Direction | Notes |
| - | - | - | - |
| `planning_center_id` | `id` | Both | Correlation ID |
| `name` | `name` | Both | Push matches by name if no `planning_center_id` yet |
| `description` | `description` | Both | Only set on push-create, never updated after |
| — | `visibility` | Push always `everywhere`; pull's `hidden` soft-deletes the WeGive fund | Not truly bidirectional |

## Data Synchronization Rules

### Correlation ID Management

* `planning_center_id` is the **only** matching mechanism, on every object type (Donor, Fund, Transaction)
* There is no email-based or name-based matching fallback anywhere in this integration
* A push targeting a `planning_center_id` that returns 404 (record deleted externally) clears the stale ID and creates a new record instead of failing

### Sync Direction Rules

**Push (WeGive → Planning Center):** creates or updates by `planning_center_id`; existing Planning Center Person/Donation records are updated on push, but an existing Fund match is left untouched after initial creation (no update path).

**Pull (Planning Center → WeGive):** creates or updates WeGive records by `planning_center_id`; pull always overwrites the local record's mapped fields, with the one carve-out above for transactions already owned by a WeGive-processed payment.

## API Endpoint Reference

### Planning Center API Endpoints Actually Used

* `people/v2/people`, and per-person nested emails/phone\_numbers/addresses (via the `insert`/`update` helper methods)
* `giving/v2/batches`, `giving/v2/donations`, `giving/v2/payment_sources`, `giving/v2/funds`

<Note>
  There is no `giving/v2/recurring_gifts` write path used by this integration — recurring gifts are only referenced as a read-only relationship ID on a pulled donation.
</Note>

## Integration Notes

### Data Validation

* Planning Center requires a name — WeGive substitutes `"FNU"`/`"LNU"` placeholders rather than failing when both first and last name are blank
* A transaction with a non-positive amount is silently skipped on push (Planning Center requires positive amounts)
* A transaction that can't resolve any fund (no fund link, no `default_fund_id`, no fallback fund) throws `Unknown Fund Source` and is skipped — logged to Sentry, not surfaced to the customer

### Performance Considerations

* Transactions sync exclusively through the once-daily 8:01 AM batch job — there's no real-time transaction push
* Donor and fund pushes happen in real time as part of the batch build (`pushDonor()`/`pushFund()` called inline per transaction)
* Pulls (donors, funds, transactions) run on a separate once-daily schedule with no fixed time
* API calls are throttled to Planning Center's real limit (70 requests / 20 seconds) via a hardcoded internal counter, not a configurable setting

## Related Documentation

* [Person Mapping](./person)
* [Donation Mapping](./donation)

## Known Gaps (not implemented)

* Household/family relationship sync
* Recurring/scheduled-gift sync
* Custom fields
* Multi-fund/split-designation support
* Fund goal, category, hierarchy, or activity-status sync
* Performance/monitoring telemetry of any kind


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.