How to model your platform’s data inside Virtuous so it stays clean, reportable, and reconcilable over time — Contact types, designation structures, custom field design, and external reference conventions.
Most partner integrations look correct on day one and degrade slowly over months as data quality issues accumulate. The integrations that stay clean for years share a small set of data-modeling decisions made deliberately at the start. This page consolidates those decisions into a single reference — drawn from the Concepts and Workflows pages but distilled into actionable guidance.The audience is the engineering team designing a new integration, or the team responsible for an existing integration that’s starting to develop data quality issues. None of this is binding — every customer has their own preferences — but each recommendation reflects what tends to work across many partner integrations.
The contactType field — Household, Organization, or Foundation — determines how Virtuous treats the record throughout the platform. Get this right at creation time; changing it later is operationally awkward.
Contact type
Use for
Household
Individuals and family units. The default for most donor records. Includes one or more ContactIndividuals (spouses, partners, family members).
Organization
For-profit companies, businesses, corporate sponsors, vendor partners. Has organizational data (Tax ID, industry) rather than individual data.
Foundation
Grant-making foundations and family foundations. A specialized Organization with grant-tracking conventions.
The wrong choice causes downstream pain:
Creating an Organization as a Household means the organization shows up alongside individuals in donor reports, distorting per-household metrics.
Creating a Household as an Organization means the individual lacks ContactIndividuals to capture their name, birth date, or personal preferences.
Creating a Foundation as a generic Organization misses the foundation-specific reporting Virtuous provides.
Your source platform usually has enough signal to distinguish:
Signal
Likely Contact type
Source record has a business_name field populated, no individual first/last name
Organization
Source records carry IRS Form 990 / Schedule I data, or have grant-tracking conventions
Foundation
Default for everything else
Household
For ambiguous cases (a sole proprietor giving from a business name with their personal email), default to Household and tag the record "Business-Donor" for the customer’s team to review and reclassify if needed.
The referenceSource + referenceId pair is the single most important data-modeling decision for a partner integration. Every other matching, deduplication, and reconciliation pattern depends on it.Three rules for the reference:
Rule
Why
referenceSource is a fixed string per integration.
Use the same value (e.g., "Stripe", "Mailchimp", your platform’s name) for every record your integration writes. Never vary by customer or environment.
referenceId is the source platform’s stable primary key.
Use the Stripe Customer ID, the Mailchimp Subscriber Hash, your platform’s user ID. Not the email, not a generated UUID, not the order ID — the persistent identifier for the entity itself.
The pair is unique across all your integration’s writes.
One donor on your platform should have exactly one (source, id) pair in Virtuous, even if they exist in multiple places on your platform.
A reference is stable if it doesn’t change when the entity’s other attributes change. Specifically:
Email is not stable — donors change their email.
Phone is not stable — donors change their phone.
Name is not stable — donors change their name (marriage, transition).
Address is not stable — donors move.
Stripe Customer ID is stable — once issued, it identifies the same customer for their lifetime on Stripe.
Your platform’s user ID is stable — assuming you use a real primary key, not a hash of mutable data.
The reference must survive every other field changing on the record.
A common partner integration mistake: using the donation’s order ID as the Contact’s referenceId. The order ID is unique per order, so the same donor giving twice produces two Contacts. Use the donor’s stable identifier (Customer ID, User ID) for Contact references; use the order or charge ID for Gift transactionId.
Some source platforms (older systems, CSV imports, manual data entry) don’t expose a stable identifier. Two patterns:
Generate one once and persist it. On first encounter, generate a UUID and store it alongside the source record. Use the UUID as referenceId on every subsequent submission. This works for any source with a writable database on your side.
Use a deterministic hash. Hash the most stable subset of fields (e.g., lowercase(email) + lowercase(lastname)) to produce a reproducible ID. Less robust because the inputs can still change, but workable for read-only sources where you have no writable storage.
Project codes are the API’s primary key for designations
Projects are the destination for Gift designations. Their projectCode field is the partner-friendly API identifier — short, human-readable, stable.Three best practices:
Practice
Why
Use upper-case alphanumeric codes with hyphens.
CLEAN-WATER, GEN-FUND, ANNUAL-2024. Easy to type, easy to read in logs, doesn’t collide with anything else.
Avoid embedding the year unless the Project is genuinely year-specific.
GEN-FUND is reusable; GEN-FUND-2024 becomes stale next year. Year-specific codes belong on Campaign-tied Projects (e.g., MARATHON-2024).
Document the mapping from your platform’s destinations to Virtuous Project codes at integration setup.
Cache the mapping in your integration’s configuration. Validate against GET /api/Project/Query at startup and after any customer-initiated change.
A single Gift can be split across multiple Projects via the giftDesignations[] array. Use a split when:
The donor explicitly specifies multiple destinations.
The source platform records a multi-Project intent (e.g., “10% to admin, 90% to programs”).
The customer’s policy automatically allocates portions to specific funds (e.g., “5% of every gift to an endowment”).
Don’t use a split as a workaround for missing designations. If you don’t know which Project the gift belongs to, don’t split it across multiple defaults — flag it for the customer’s team to resolve.
When your integration writes tags, use a consistent prefix that identifies the source:
Prefix
Meaning
MC:
From Mailchimp
CC:
From Constant Contact
Stripe:
From Stripe
Auto:
Automated tag added by any integration
No prefix
Customer-managed tags
The prefix lets the customer’s team visually distinguish “tags my staff applied” from “tags some integration applied” — and lets your integration code filter for “my tags” when synchronizing back to the source.
A frequent partner integration mistake: re-deriving source-of-record fields from data the integration doesn’t authoritatively own. For example, computing a donor’s giving total in your integration’s database when Virtuous already calculates lifeToDateGiving.The rule: store what your platform owns; query what Virtuous owns.
Your platform owns
Virtuous owns
The donor’s identity on your platform (Stripe Customer ID, your User ID)
The donor’s identity inside Virtuous (Contact ID)
The specific donation event metadata (Stripe charge ID, payment method)
The Gift record, its designations, premiums, and tax-deductible status
Your platform’s tags, lists, segments
The Contact’s tags inside Virtuous
Your platform’s calculated metrics for your platform’s purposes
When the customer wants a report that mixes both — “donors on the Mailchimp Gold list who gave more than $1,000 last year” — pull the giving totals from Virtuous on demand rather than syncing them into your platform and risking staleness.
In a multi-integration customer environment, multiple systems may write to the same Contact. Without explicit field ownership, two integrations will overwrite each other’s data.The pattern: document which fields each integration owns, both within your integration’s documentation and (ideally) in the customer’s runbook for managing their Virtuous setup.A typical breakdown for a fundraising-platform integration:
Field
Owned by
Notes
name, firstName, lastName
Virtuous UI (the customer’s staff)
Donor name corrections happen in Virtuous.
email, phone (primary)
Source platform
Updates flow in from the source.
email, phone (additional)
Virtuous UI
The customer’s staff curates these.
tags (with MC: prefix)
Mailchimp integration
Round-tripped via the Mailchimp recipe.
tags (other)
Virtuous UI
Not touched by integrations.
customFields["External Donor ID"]
Source platform
Set on creation, never modified.
customFields["Donor Segment Tier"]
Virtuous UI
Customer staff sets this during cultivation.
Your integration’s code enforces this — outbound writes only modify fields you own. Inbound reads (webhook handlers) apply changes only to fields the source platform owns.See Build a Two-Way Sync — Pattern 2: per-field ownership for the implementation.
Capture audit metadata at creation, not in narration
Several pieces of metadata are useful for diagnostics later: when did the integration first sync this record, which version of the integration code wrote it, what was the source event. The natural impulse is to put these in ContactNotes — but notes are designed for human-readable observations, not machine-readable diagnostics.Better practice: capture diagnostic metadata as custom fields, not notes.
These fields are queryable, exportable, and don’t clutter the Notes view that the customer’s staff actually reads. Reserve ContactNotes for content that a human would want to see — interaction history, payment failures, manual flags.
Use Relationships sparingly and only when configured
Virtuous Relationships connect two Contacts (donor and spouse, parent and child, donor and recruiting fundraiser). They’re powerful but easy to misuse.Two rules:
Rule
Why
Only create Relationships your integration genuinely owns.
Don’t auto-create “Spouse” relationships from address sharing — let the customer’s staff make those calls.
Only create Relationships whose type is configured in Virtuous.
Discovered via GET /api/Relationship/Types. If the customer hasn’t configured the type, skip the relationship and log it.
Relationships are a “two-way edge in the social graph” — a wrong relationship is more confusing than no relationship. When in doubt, capture the linkage in a custom field instead and let the customer’s team formalize it as a Relationship if they want.
The customer’s team will eventually want to build reports against the data your integration writes. Three things make their work easier:
Consistent tags and custom field values. “Major Donor” and “Major-donor” and “major_donor” are three different segments to Virtuous Query, even though they’re the same intent to a human.
Predictable Project codes. The customer should be able to write parameter: "Project", operator: "Is", value: "CLEAN-WATER" and know it returns the right gifts. Cryptic codes (P-2024-0317) frustrate this.
Useful originSegmentCode values. This field captures “how did this Contact first enter Virtuous?” — set it to a stable, descriptive value ("FUNDRAISING-PLATFORM", "STRIPE-DONATION", "MAILCHIMP-SIGNUP") so reports can split donors by acquisition channel.
Confirm the customer’s preferred values for these during onboarding. Document them in your integration’s configuration so they’re explicit, not buried in code.