Skip to main content
This workflow walks through recording a single Gift in CRM+ from the partner integration perspective. The two paths covered are POST /api/v2/Gift/Transaction (the recommended path for partner integrations) and POST /api/Gift (the direct path). The structure is parallel to Create a Contact, but Gifts have additional considerations: designation amounts must sum to the gift total, idempotency through transactionSource/transactionId matters more, and gift types have organization-specific rules. If you have not read the Donations / Gifts concept page, start there.

Scenario

A donor on your platform has made a contribution — a one-time gift, a recurring payment, an event ticket purchase that includes a donation portion. You need to record the contribution as a Gift in your customer’s Virtuous organization with the correct Contact association, the right Project designation, and an external reference that lets you reconcile the Virtuous record back to your platform.

Prerequisites

  • A valid CRM+ API token — see Authentication.
  • The donor’s details (or their existing Virtuous Contact ID) — see Create a Contact.
  • The Project code(s) the gift should be designated to — see Funds, Campaigns, and Designations.
  • Your platform’s unique identifier for this specific donation event (used for transactionId).

Step 1: choose the creation pattern

PatternEndpointContact matchingResponseBest for
Transaction (recommended)POST /api/v2/Gift/TransactionBuilt-in: matches the embedded contact data against existing Contacts and creates a new Contact if no match foundAsync — the real Gift is created during the nightly batchAll partner gift sync from external systems
Direct createPOST /api/GiftNone — requires an existing contactIdSync — the Gift is created immediately with an ID in the responseCases where you already have a verified Contact ID and need synchronous confirmation
The Transaction pattern is strongly preferred for gift import. It bundles contact-matching, designation resolution by projectCode, recurring-gift linkage, and pledge-payment linkage into a single request — saving you from implementing each of those matching rules in your own code.
Note the version segment: the Gift Transaction endpoint is /api/v2/Gift/Transaction, not /api/Gift/Transaction. See Base URLs and Environments.

Step 2: assemble the Transaction request

The request includes the gift data, the embedded contact data (used for matching), and the designation breakdown.
curl -X POST https://api.virtuoussoftware.com/api/v2/Gift/Transaction \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionSource": "YourPlatform",
    "transactionId": "donation-9421",
    "contact": {
      "referenceId": "donor-bw-001",
      "type": "Household",
      "firstname": "Bruce",
      "lastname": "Wayne",
      "emailType": "Home Email",
      "email": "bruce@wayne.example",
      "phoneType": "Mobile Phone",
      "phone": "555-0100",
      "address": {
        "address1": "1007 Mountain Drive",
        "city": "Gotham",
        "state": "NJ",
        "postal": "07001",
        "country": "US"
      }
    },
    "giftDate": "2024-12-15",
    "giftType": "Cash",
    "amount": "500.00",
    "currencyCode": "USD",
    "batch": "Year-End-2024",
    "giftDesignations": [
      { "projectCode": "CLEAN-WATER", "amountDesignated": "500.00" }
    ]
  }'
A 200 OK response indicates the Transaction was accepted into the holding state. As with Contact Transactions, the endpoint does not return a Gift ID — the real Gift does not yet exist.
FieldRequiredWhy
transactionSource + transactionIdStrongly recommendedIdempotency key. Without these, retries will create duplicate Gifts.
contact.referenceId (and ideally referenceSource equivalent on Contact lookup)RecommendedStrongest contact-match signal. Use your platform’s donor ID.
giftDateYesISO 8601 date when the donation occurred.
giftTypeYesSee Donations / Gifts for common values.
amountYesThe full gift amount.
currencyCodeRecommendedDefaults to organization currency if omitted; explicit is safer.
giftDesignations[]YesAt least one designation is required.

Designation amounts must balance

The sum of all amountDesignated values must equal the gift amount. The API validates this synchronously and rejects mismatched submissions:
// ✅ Valid — designations sum to amount
{
  "amount": "500.00",
  "giftDesignations": [
    { "projectCode": "CLEAN-WATER", "amountDesignated": "300.00" },
    { "projectCode": "EDUCATION", "amountDesignated": "200.00" }
  ]
}

// ❌ Invalid — designations don't sum to amount
{
  "amount": "500.00",
  "giftDesignations": [
    { "projectCode": "CLEAN-WATER", "amountDesignated": "300.00" }
  ]
}
For single-Project gifts (the common case), submit one designation for the full amount:
{
  "amount": "500.00",
  "giftDesignations": [
    { "projectCode": "CLEAN-WATER", "amountDesignated": "500.00" }
  ]
}

Field typing — string vs. native

The spec types amount, amountDesignated, and giftDate as string (audit findings). The live API accepts the natural types in most cases, but the cURL example above uses strings to match the spec — passing numbers also typically works. For the JS example, toFixed(2) produces a string representation of the amount. This is the safest format for monetary values to avoid floating-point representation issues in the wire format.

Step 3: handle the response

A 200 OK confirms the Transaction was accepted. There is no Gift ID in the response — the Gift will be created during the nightly batch. If the response is non-2xx, inspect the body for validation details:
StatusCommon causesFix
400Malformed JSON, missing required fieldsInspect body, fix request
400Designation amounts don’t sum to gift amountRecalculate designations
400Unknown projectCodeConfirm the Project exists and is active; see Funds, Campaigns, and Designations
400Unknown giftType valueUse one of the documented gift types; see Donations / Gifts
401Invalid API tokenRefresh credentials
429Rate limit exceededBack off per Retry-After; see Rate Limits
See Error Handling for the full error envelope structure.
Idempotency depends on transactionSource + transactionId being stable across retries. If you regenerate transactionId on each retry (e.g., a fresh UUID), Virtuous will see each retry as a new gift and create duplicates. Use your platform’s stable identifier for the donation event — the Stripe charge ID, your platform’s internal donation ID, the Eventbrite registration ID — not a value you generate at submission time.

Step 4: confirm the Gift was created

The Transaction is asynchronous — the response confirms acceptance but not creation. Two patterns confirm the resulting Gift exists: Subscribe to the giftCreate event via POST /api/Webhook. When the nightly batch creates the real Gift, the event fires with the Gift’s full record, including the transactionSource and transactionId you submitted. Match incoming events to pending Transactions by that pair, capture the Gift id, and update your record.
JavaScript
// Inside your webhook handler
async function handleGiftCreated(event) {
  const gift = event.data;
  if (gift.transactionSource === 'YourPlatform') {
    // Match by transactionId to find the pending donation in your DB
    await db.donations.update(
      { transactionId: gift.transactionId },
      { virtuousGiftId: gift.id, status: 'confirmed', confirmedAt: new Date() }
    );
  }
}
See Webhooks Overview and Event Types.

Pattern B: lookup by external reference

If you cannot use webhooks, look up the Gift by your transaction reference:
cURL
curl https://api.virtuoussoftware.com/api/Gift/YourPlatform/donation-9421 \
  -H "Authorization: Bearer YOUR_API_TOKEN"
This endpoint returns 404 while the Transaction is in the holding state and 200 with the Gift once it has been resolved.

Direct creation (when synchronous is required)

For cases where you have a verified Virtuous Contact ID and need synchronous confirmation of Gift creation, use POST /api/Gift:
curl -X POST https://api.virtuoussoftware.com/api/Gift \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "contactId": 4821,
    "giftType": "Cash",
    "giftDate": "2024-12-15",
    "amount": 500.00,
    "currencyCode": "USD",
    "transactionSource": "YourPlatform",
    "transactionId": "donation-9421",
    "batch": "Year-End-2024",
    "giftDesignations": [
      { "projectCode": "CLEAN-WATER", "amountDesignated": 500.00 }
    ]
  }'
POST /api/Gift returns 200 OK (not 201, per audit finding #22) with the created Gift in the response body, including the new id.
Direct create bypasses contact-matching entirely. It also bypasses recurring-gift linkage and pledge-payment association — meaning a Gift created via POST /api/Gift will not automatically be linked to an existing RecurringGift schedule or Pledge for that donor, even if the linkage would be obvious. Set recurringGiftTransactionId and pledgeTransactionId explicitly on the request body when you need those linkages.

Recording specific gift types

The base workflow above covers cash donations. A few specific gift types have additional fields:

Recurring gift payment

When a Gift is a payment on an existing recurring schedule, include recurringGiftTransactionId:
{
  "transactionSource": "Stripe",
  "transactionId": "ch_3PXyz123",
  "recurringGiftTransactionId": "<the recurring gift's transaction id>",
  "amount": "50.00",
  "giftDate": "2024-12-15",
  "giftType": "Cash",
  "giftDesignations": [
    { "projectCode": "MONTHLY-GIVING", "amountDesignated": "50.00" }
  ]
}
The Transaction endpoint’s matching algorithm uses this field to associate the new Gift with the existing schedule.

Pledge payment

When a Gift is a payment toward a pledge, include pledgeTransactionId:
{
  "pledgeTransactionId": "<the pledge's transaction id>",
  "amount": "2500.00",
  "giftType": "Cash",
  "giftDesignations": [
    { "projectCode": "CAPITAL-CAMPAIGN", "amountDesignated": "2500.00" }
  ]
}

Non-cash gift (in-kind)

For donations of goods rather than money, set giftType: "NonCash" and include the non-cash subtype and description:
{
  "giftType": "NonCash",
  "nonCashGiftType": "Auction Item",
  "inKindDescription": "Signed first-edition book",
  "inKindValue": 250.00,
  "amount": 250.00,
  "giftDate": "2024-12-15",
  "giftDesignations": [
    { "projectCode": "GALA-2024", "amountDesignated": 250.00 }
  ]
}
Discover the valid non-cash subtypes for the organization via GET /api/Gift/NonCashGiftTypes.

Stock gift

For donations of securities:
{
  "giftType": "Stock",
  "stockTickerSymbol": "WAYNE",
  "stockNumberOfShares": 100,
  "amount": 5000.00,
  "giftDate": "2024-12-15",
  "giftDesignations": [
    { "projectCode": "ENDOWMENT", "amountDesignated": 5000.00 }
  ]
}
The amount is the fair market value on the gift date — typically the average of high and low prices for that trading day.
The CRM+ spec types stockNumberOfShares and similar numeric fields as string. Send them as the natural type — the live API handles the conversion. See Donations / Gifts for the broader typing situation.

End-to-end walkthrough

The complete safe pattern, combining donor identification, Transaction submission, and webhook-based outcome detection:
1

Identify the donor

Resolve the donor either by looking up an existing Virtuous Contact (GET /api/Contact/Find with your reference, email, or both) or by including the contact data in the Transaction’s embedded contact block.
2

Resolve the designation

Confirm the projectCode you intend to designate to is active in the organization. Cache the list of active Projects at integration startup to validate before submission.
3

Submit the Gift Transaction

POST /api/v2/Gift/Transaction with the full payload. Record the submission on your side as “pending” with the transactionId you submitted.
4

Wait for the giftCreate webhook

The nightly batch processes the Transaction and fires giftCreate. Match by transactionSource + transactionId, capture the Gift id, and update your record from “pending” to “confirmed.”
5

Reconcile any pending submissions that didn't produce a webhook

For submissions still in “pending” after a reasonable window (a day plus the platform’s nightly batch window), poll GET /api/Gift/{transactionSource}/{transactionId}. A 404 means the Transaction is still in the holding state or has landed in needs-update — see Reconcile Failed Syncs.

Where to go next

Sync External Donations into Virtuous

Scale this single-gift workflow into a continuous sync pipeline.

Create a Contact

The companion workflow for creating Contacts independently of Gifts.

Query Donations by Date Range

Read Gifts back out for reporting or reconciliation.

Stripe to Virtuous CRM

A complete integration recipe showing Stripe gift events flowing into Virtuous.
Last modified on May 21, 2026