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
| Pattern | Endpoint | Contact matching | Response | Best for |
|---|---|---|---|---|
| Transaction (recommended) | POST /api/v2/Gift/Transaction | Built-in: matches the embedded contact data against existing Contacts and creates a new Contact if no match found | Async — the real Gift is created during the nightly batch | All partner gift sync from external systems |
| Direct create | POST /api/Gift | None — requires an existing contactId | Sync — the Gift is created immediately with an ID in the response | Cases where you already have a verified Contact ID and need synchronous confirmation |
/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.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.
Required and recommended fields
| Field | Required | Why |
|---|---|---|
transactionSource + transactionId | Strongly recommended | Idempotency key. Without these, retries will create duplicate Gifts. |
contact.referenceId (and ideally referenceSource equivalent on Contact lookup) | Recommended | Strongest contact-match signal. Use your platform’s donor ID. |
giftDate | Yes | ISO 8601 date when the donation occurred. |
giftType | Yes | See Donations / Gifts for common values. |
amount | Yes | The full gift amount. |
currencyCode | Recommended | Defaults to organization currency if omitted; explicit is safer. |
giftDesignations[] | Yes | At least one designation is required. |
Designation amounts must balance
The sum of allamountDesignated values must equal the gift amount. The API validates this synchronously and rejects mismatched submissions:
Field typing — string vs. native
The spec typesamount, 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
A200 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:
| Status | Common causes | Fix |
|---|---|---|
400 | Malformed JSON, missing required fields | Inspect body, fix request |
400 | Designation amounts don’t sum to gift amount | Recalculate designations |
400 | Unknown projectCode | Confirm the Project exists and is active; see Funds, Campaigns, and Designations |
400 | Unknown giftType value | Use one of the documented gift types; see Donations / Gifts |
401 | Invalid API token | Refresh credentials |
429 | Rate limit exceeded | Back off per Retry-After; see Rate Limits |
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:Pattern A: webhook (recommended)
Subscribe to thegiftCreate 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
Pattern B: lookup by external reference
If you cannot use webhooks, look up the Gift by your transaction reference:cURL
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, usePOST /api/Gift:
POST /api/Gift returns 200 OK (not 201, per audit finding #22) with the created Gift in the response body, including the new id.
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, includerecurringGiftTransactionId:
Pledge payment
When a Gift is a payment toward a pledge, includepledgeTransactionId:
Non-cash gift (in-kind)
For donations of goods rather than money, setgiftType: "NonCash" and include the non-cash subtype and description:
GET /api/Gift/NonCashGiftTypes.
Stock gift
For donations of securities: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: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.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.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.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.”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.