> ## 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.

# Donor Data Mapping

> Detailed field mapping for donor profiles between WeGive and DonorPerfect systems.

# Donor Data Mapping

This document details the real field mapping for donor profiles between WeGive and DonorPerfect. It's a fixed, hardcoded mapping — there's no custom-field support or configurable sync scope.

## DonorPerfect Table Reference

**Primary Table**: `dp` (Donor Profile)
**WeGive Model**: `Donor`
**Sync Direction**: Bidirectional — push on create/update (real-time), pull once daily

## Core Identity Fields

| WeGive Field | DonorPerfect Field | Notes |
| - | - | - |
| `dp_id` | `donor_id` | The only correlation key — there's no email or name-based matching on push |
| `email_1` | `email` | Used as the match key on **pull** only (matches an existing WeGive `User` by exact email) |
| — | `user_id` | Always sent as `"WeGive"` on push, for tracking/dedup |

<Warning>
  **Email Requirement (pull only)**: A DonorPerfect donor row missing an `email`, with an empty email, or one that fails `filter_var(..., FILTER_VALIDATE_EMAIL)` is skipped entirely on pull. Push has no equivalent requirement — a WeGive donor with no email still pushes.
</Warning>

## Personal Information

### Name Fields and Donor Type

WeGive's real donor type field is `type` (enum: `individual`/`company`), not `donor_type`.

**Individual donors** (`type = 'individual'`):

| WeGive Field | DonorPerfect Field | Notes |
| - | - | - |
| `first_name` | `first_name` | Falls back to blank if not set — no length truncation in code |
| `last_name` | `last_name` | Same |

DonorPerfect `donor_type` sent: `'IN'`

**Company donors** (`type = 'company'`):

| WeGive Field | DonorPerfect Field | Notes |
| - | - | - |
| `name` | `last_name` | WeGive has no separate `organization_name` field — company donors use the same `name` column as individuals |
| — | `first_name` | Always `null` for company donors |

DonorPerfect `donor_type` sent: `'CO'`

<Note>
  There's no middle name, suffix, title, salutation, or professional-title field sent at all — `exportDonor()` sends `null` for all of them unconditionally, for both individual and company donors.
</Note>

### Phone Numbers

| WeGive Field | DonorPerfect Field |
| - | - |
| `mobile_phone` | `mobile_phone` |
| `home_phone` | `home_phone` |
| `office_phone` | `business_phone` |
| `fax` | `fax_phone` |

<Note>
  There's no phone-number formatting, digit-stripping, or format validation anywhere in this integration's code — whatever string is stored in WeGive is sent as-is. There's also no separate `other_phone` mapping despite WeGive having an `other_phone` column.
</Note>

### Email Addresses

| WeGive Field | DonorPerfect Field | Notes |
| - | - | - |
| `email_1` | `email` | The only email field sent on push |

<Note>
  `email_2`/`email_3` are not sent on push — DonorPerfect's donor record has one email field, and only `email_1` maps to it.
</Note>

## Address Information

### Mailing Address Mapping

| WeGive Field | DonorPerfect Field |
| - | - |
| `mailingAddress.address_1` | `address` |
| `mailingAddress.address_2` | `address2` |
| `mailingAddress.city` | `city` |
| `mailingAddress.state` | `state` |
| `mailingAddress.zip` | `zip` |
| — | `country` (always `"US"`, hardcoded) |

<Warning>
  **Country is always hardcoded to "US"** on push, regardless of the donor's actual address country — this isn't derived from any WeGive field.
</Warning>

<Note>
  There's no address length truncation, standardization, or partial-address validation logic in the code — whatever is in `mailingAddress` is sent as-is, including `null` for any missing component.
</Note>

## Data Flow Patterns

### WeGive to DonorPerfect (Push)

1. Push is triggered by donor create/update in WeGive (real-time, not scheduled)
2. A donor with no `dp_id` yet creates a new DonorPerfect record; one with an existing `dp_id` updates it
3. If updating a `dp_id` that no longer exists in DonorPerfect (deleted externally), the push is simply not retried as a create — `exportDonor()` returns early in that case (there's no automatic re-create-on-404 for donors, unlike some other WeGive CRM integrations)
4. The returned `donor_id` is stored back as `dp_id`

### DonorPerfect to WeGive (Pull)

1. Rows with a missing/invalid email are skipped
2. Match to an existing WeGive `User` by exact email string
3. If no `User` exists, one is created (with a random password) alongside the new `Donor`
4. If a `Donor` with that `dp_id` already exists, only `dp_id` and `name` are refreshed — the rest of the profile is not re-imported on subsequent pulls

## API Operations

### Create/Update Donor

**Action**: `dp_savedonor`

The full parameter set sent (from `exportDonor()`): `donor_id`, `first_name`, `last_name`, `donor_type`, `middle_name` (always null), `suffix` (always null), `title` (always null), `salutation` (always null), `prof_title` (always null), `opt_line` (always null), `address`, `address2`, `city`, `state`, `zip`, `country` (always `"US"`), `address_type` (always null), `email`, `mobile_phone`, `home_phone`, `business_phone`, `fax_phone`, `org_rec` (always `"N"`), `nomail` (always `"N"`), `nomail_reason` (always null), `narrative` (always null), `user_id` (always `"WeGive"`).

On update, any of these fields that are `null` are backfilled from DonorPerfect's own existing value for that donor before sending — so an update doesn't overwrite an existing DonorPerfect field with `null`.

### Pull Query Shape

Pull uses a literal SQL-style string, e.g.:

```
select * from dp where email is not null and donor_id > {id} and {created_date|modified_date} > 'M/D/YYYY' and {column} is not null
```

Run in two passes per sync — once filtering on `created_date`, once on `modified_date` — recursing forward by the last-seen `donor_id`.

## Error Handling

<Warning>
  There is no automatic retry, auto-correction, or configurable resolution for any of these — a failed request is reported to Sentry and the record is skipped for that run.
</Warning>

| Symptom | Real Cause |
| - | - |
| Donor never appears after pull | Missing/invalid email — hard skip, not a warning |
| Donor push throws an exception | DonorPerfect returned a logical error in its XML body (DP returns HTTP 200 even on errors) — check Sentry for the raw response body |
| Duplicate-looking donor records | No email/name-based dedup exists on push — a donor without a `dp_id` link always creates a new DonorPerfect record, even if a matching person already exists there |

## Data Quality Considerations

### Before Sync

* Link existing DonorPerfect donors to their WeGive counterpart (via `dp_id`) before enabling push, since there's no automatic matching beyond exact `dp_id`
* Ensure donor emails in DonorPerfect are valid — this is a hard requirement for pull, not a soft warning

### Ongoing Maintenance

* Check Sentry (not the WeGive dashboard) for donor sync failures — there's no sync-status dashboard for this integration

## Custom Field Mapping

<Note>
  There is no custom field-mapping mechanism for this integration at all — the field list above is the complete, fixed set. The legacy dashboard's "Mapping Rules" tab is commented out in source for DonorPerfect specifically.
</Note>

## Selective Sync

<Warning>
  There is no way to sync "new donors only," "contact info only," or any custom filter — every enabled donor push/pull moves the full field set above. The dashboard's sync toggles for this don't currently function; see [Configuration Options](/external/onboarding/donorperfect/configuration-options).
</Warning>

## Troubleshooting

### Donors Not Syncing

**Possible causes:**

* Missing or invalid email (pull only — push has no email requirement)
* API authentication failure
* The integration is disabled (`enabled = false`) — no other toggle stops donor sync

**Solutions:**

* Verify email validity in DonorPerfect for missing pulls
* Check API credentials
* Confirm the integration's `enabled` flag

### Duplicate Donor Records

**Possible cause:** A donor was pushed before being linked (via `dp_id`) to an existing DonorPerfect person — this always creates a new record since there's no email/name matching on push.

**Solution:** Link the donor's `dp_id` manually before its next push.

## Related Documentation

<CardGroup>
  <Card title="Transaction Mapping" href="/external/onboarding/donorperfect/data-mapping/transaction">
    Learn how donor transactions are mapped
  </Card>

  <Card title="Configuration Guide" href="/external/onboarding/donorperfect/configuration-options">
    Integration-level configuration (most sync-scope toggles are currently non-functional)
  </Card>
</CardGroup>

For additional help with donor data mapping, contact our support team at [support@wegive.com](mailto:support@wegive.com).


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