Skip to main content

DonorPerfect Integration Nuances

Understanding the specific behaviors and limitations of the DonorPerfect integration will help you avoid common issues.
Several dashboard sync toggles (crm_sync, two_way_sync, track_donations, track_donors, track_recurring_donations, track_campaigns) are currently non-functional — see Configuration Options. This is a known issue. This page describes what the integration actually does regardless of what any toggle shows.

Platform-Specific Behaviors

DonorPerfect API Characteristics

Legacy API FormatDonorPerfect’s xmlrequest.asp endpoint is called via a single GET request per action, with the action and params encoded directly into the query string. Responses are XML; WeGive parses them with simplexml_load_string. There’s no JSON option.
Direct Query AccessPull operations use literal SQL-style select * from dpgift where .../select * from dp where ... strings built directly into the request — not a REST-style filtered endpoint. This is genuinely how DonorPerfect’s API works for this integration, not an abstraction WeGive built.
Contrary to what you might expectThere is no rate-limit handling, throttling, or automatic retry anywhere in this integration’s code. Every API call (makeRequest()) is a single unretried HTTP GET with a 120-second timeout. A failed call is reported to Sentry and the record it was attempting to sync is simply skipped for that run.

Data Format Requirements

Date Formatting

DonorPerfect dates are sent as MM/DD/YYYY (via PHP’s ->format('m/d/Y')) for gift dates and pledge start dates.
There’s no donor birth-date field mapped at all in this integration — exportDonor() doesn’t send one.

Currency Handling

Amount Conversion (WeGive → DonorPerfect):
  • Gift amount: (transaction.amount - transaction.fee) / 100 — fee is deducted before the cents-to-dollars conversion, not just before sending
  • Pledge amount: (scheduled_donation.amount + optional fee_amount) / 100
Currency field: the transaction’s own currency value is sent as-is on gift sync (@currency param) — there’s no currency conversion, exchange-rate handling, or USD-only restriction in the code. Whether DonorPerfect’s UI displays a non-USD value meaningfully is outside WeGive’s control.

Text Fields

There is no character-limit truncation logic anywhere in DonorPerfectIntegration.php — no substr, Str::limit, or similar. If DonorPerfect’s own API rejects an over-length field, that shows up as a sync failure (reported to Sentry), not a silent truncation.

Synchronization Behaviors

Incremental Pull Logic

  • Pull queries filter on created_date then modified_date as two separate passes, each starting from id > 0 and paging forward by recursing with the last-seen id as the new floor — not true pagination with page numbers
  • A DonorPerfect row is skipped (not partially synced) if: its email is missing/invalid (donors), or its amount is 0/'0' (gifts)
  • Records already recognized as WeGive-originated (via user_id = 'WeGive' or a WG:txn: reference prefix) are skipped on pull — this prevents WeGive from re-importing its own pushes as new records

Push Logic

  • Donor/gift/pledge push is triggered by the model being created or updated in WeGive — this part is genuinely real-time
  • A push with no existing dp_id creates a new DonorPerfect record; a push with an existing dp_id updates it
  • All pushed records are stamped user_id = 'WeGive' for tracking and later dedup

Duplicate Handling

The only dedup mechanism is DonorPerfect ID (dp_id)-based:
  • Import skips a gift already recorded against a donor with that exact dp_id
  • A WeGive-originated gift is recognized by user_id/reference prefix, not by amount/date matching
  • There’s a specific phantom-gift recovery path: if a dp_savegift push returns a 2xx response with no id field, the integration looks up the gift by its WeGive reference token before retrying, to avoid creating duplicate gift records on a lost-response retry — see the dedicated duplicate-gift-import doc for the full mechanics
There is no email-based or name-based duplicate donor detection — only dp_id correlation. A donor pushed before being matched to an existing DonorPerfect person will always create a new one.

Field Mapping Limitations

Hardcoded Values

Missing Field Support

Fields that don’t sync to DonorPerfect:
  • Custom donor profile fields (no custom-field mechanism exists at all)
  • Detailed payment method data (card type, last-4, etc.) — DonorPerfect gets amount/date/reference only
  • Middle name, suffix, title, salutation — all sent as null on push (see the donor data-mapping page)
  • Campaign attribution of any kind
DonorPerfect fields not imported to WeGive:
  • Fund goal amount, category, hierarchy
  • Volunteer tracking, relationship/household data
  • Custom code tables

Performance Considerations

Large Database Handling

Organizations with large DonorPerfect databases should expect longer initial pull times — the pull recurses through records via ascending id, one page of results per API call, with no configurable batch size.
There is no automatic batch-failure retry, no automatic memory-management optimization, and no real-time dashboard progress indicator specific to this integration beyond the generic pull-progress tracking shared by all WeGive CRM integrations (visible as overall pull status, not DonorPerfect-specific detail).

Best Practices

Data Preparation

1

Clean Existing Data

Remove duplicate donors before initial sync — there’s no automated dedup beyond dp_id correlation
2

Validate Email Addresses

Donors without a valid email are silently skipped on every pull, not just the first one
3

Set Up GL Codes First

A fund needs a dp_id (via syncFund(), which only fires automatically the first time a gift references that fund) before gift-fund attribution works

Ongoing Management

  • Check Sentry (not the WeGive dashboard) for DonorPerfect sync failures — there’s no customer-facing sync-status view for this integration
  • If a transaction later fails after already syncing successfully to DonorPerfect, resyncing from WeGive cannot correct it (the sync payload carries no status field) — see the dedicated status-field doc

Common Limitations

  • Recurring frequency: every WeGive recurring donation syncs to DonorPerfect as "M" (Monthly), regardless of its actual frequency
  • No fund pull: funds only push (GL code, one-way); DonorPerfect-side fund changes never flow back
  • No campaign sync: none at all, despite a dashboard toggle suggesting otherwise
  • Single currency assumption in UI messaging only: the code itself passes through whatever currency the transaction has; multi-currency correctness on DonorPerfect’s side is not something WeGive verifies

Troubleshooting Common Issues

Symptoms: DonorPerfect returns an <error> element in its response body (DP returns HTTP 200 even on logical errors)Solutions:
  • Verify the API key is correct and active
  • Check the API key’s permissions in DonorPerfect directly
Symptoms: A DonorPerfect donor never appears in WeGive after pullSolutions:
  • Check the donor has a valid, present email in DonorPerfect — this is a hard skip condition, not a warning
Symptoms: Two WeGive transactions both reference DonorPerfect, but only one gift is visible in DonorPerfectSolutions:
  • This usually indicates the two dp_ids were genuinely distinct at sync time, and one was later merged/deleted in DonorPerfect itself — not a WeGive-side import bug. See the dedicated duplicate-gift diagnosis doc for the full runbook.

Next Steps

Data Mapping Reference

Detailed field mapping documentation

Get Support

Contact our team for integration assistance