> ## Documentation Index
> Fetch the complete documentation index at: https://docs.virtuous.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a Donation

> Record a Gift in CRM+ end-to-end — choosing between the Transaction endpoint and direct creation, structuring designations correctly, and confirming the resulting Gift record.

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](/crm/workflows/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](/crm/concepts/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](/crm/authentication).
* The donor's details (or their existing Virtuous Contact ID) — see [Create a Contact](/crm/workflows/create-a-contact).
* The Project code(s) the gift should be designated to — see [Funds, Campaigns, and Designations](/crm/concepts/funds-campaigns-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 |

<Tip>
  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.
</Tip>

Note the version segment: the Gift Transaction endpoint is `/api/v2/Gift/Transaction`, not `/api/Gift/Transaction`. See [Base URLs and Environments](/crm/base-urls#base-url).

***

## Step 2: assemble the Transaction request

The request includes the gift data, the embedded contact data (used for matching), and the designation breakdown.

<CodeGroup>
  ```bash cURL theme={null}
  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" }
      ]
    }'
  ```

  ```javascript JavaScript theme={null}
  async function submitGiftTransaction(donation) {
    const response = await fetch(
      'https://api.virtuoussoftware.com/api/v2/Gift/Transaction',
      {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${process.env.VIRTUOUS_API_TOKEN}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          transactionSource: 'YourPlatform',
          transactionId: donation.platformId,
          contact: {
            referenceId: donation.donor.platformId,
            type: donation.donor.isOrganization ? 'Organization' : 'Household',
            firstname: donation.donor.firstName,
            lastname: donation.donor.lastName,
            emailType: 'Home Email',
            email: donation.donor.email,
            phoneType: 'Mobile Phone',
            phone: donation.donor.phone,
            address: donation.donor.address && {
              address1: donation.donor.address.line1,
              city: donation.donor.address.city,
              state: donation.donor.address.state,
              postal: donation.donor.address.postal,
              country: donation.donor.address.country ?? 'US',
            },
          },
          giftDate: donation.date,           // ISO 8601 date string
          giftType: donation.type,           // 'Cash', 'EFT', 'Credit', etc.
          amount: donation.amount.toFixed(2),
          currencyCode: donation.currency ?? 'USD',
          batch: donation.batchLabel,
          giftDesignations: donation.designations.map((d) => ({
            projectCode: d.projectCode,
            amountDesignated: d.amount.toFixed(2),
          })),
        }),
      }
    );

    if (!response.ok) {
      throw new Error(`Gift Transaction failed: ${response.status}`);
    }
    return response.status;
  }
  ```
</CodeGroup>

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.

### 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](/crm/concepts/gifts#gift-types) 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 all `amountDesignated` values must equal the gift `amount`. The API validates this synchronously and rejects mismatched submissions:

```json theme={null}
// ✅ 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:

```json theme={null}
{
  "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:

| 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](/crm/concepts/funds-campaigns-designations#reading-projects) |
| `400`  | Unknown `giftType` value                     | Use one of the documented gift types; see [Donations / Gifts](/crm/concepts/gifts#gift-types)                                                   |
| `401`  | Invalid API token                            | Refresh credentials                                                                                                                             |
| `429`  | Rate limit exceeded                          | Back off per `Retry-After`; see [Rate Limits](/crm/rate-limits)                                                                                 |

See [Error Handling](/crm/error-handling) for the full error envelope structure.

<Warning>
  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.
</Warning>

***

## 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 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 JavaScript theme={null}
// 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](/crm/webhooks/overview) and [Event Types](/crm/webhooks/event-types#giftcreate--gift-created).

### Pattern B: lookup by external reference

If you cannot use webhooks, look up the Gift by your transaction reference:

```bash cURL theme={null}
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`:

<CodeGroup>
  ```bash cURL theme={null}
  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 }
      ]
    }'
  ```

  ```javascript JavaScript theme={null}
  async function createGiftDirect(donation) {
    const response = await fetch(
      'https://api.virtuoussoftware.com/api/Gift',
      {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${process.env.VIRTUOUS_API_TOKEN}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          contactId: donation.virtuousContactId,
          giftType: donation.type,
          giftDate: donation.date,
          amount: donation.amount,
          currencyCode: donation.currency ?? 'USD',
          transactionSource: 'YourPlatform',
          transactionId: donation.platformId,
          batch: donation.batchLabel,
          giftDesignations: donation.designations.map((d) => ({
            projectCode: d.projectCode,
            amountDesignated: d.amount,
          })),
        }),
      }
    );

    if (!response.ok) {
      throw new Error(`Gift create failed: ${response.status}`);
    }
    return await response.json(); // Contains the new Gift's id
  }
  ```
</CodeGroup>

`POST /api/Gift` returns `200 OK` (not `201`, per audit finding #22) with the created Gift in the response body, including the new `id`.

<Warning>
  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.
</Warning>

***

## 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`:

```json theme={null}
{
  "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`:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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.

<Note>
  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](/crm/concepts/gifts#field-typing) for the broader typing situation.
</Note>

***

## End-to-end walkthrough

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

<Steps>
  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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."
  </Step>

  <Step title="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](/crm/workflows/reconcile-failed-syncs).
  </Step>
</Steps>

***

## Where to go next

<CardGroup cols={2}>
  <Card title="Sync External Donations into Virtuous" icon="arrow-right-arrow-left" href="/crm/workflows/sync-external-donations">
    Scale this single-gift workflow into a continuous sync pipeline.
  </Card>

  <Card title="Create a Contact" icon="user-plus" href="/crm/workflows/create-a-contact">
    The companion workflow for creating Contacts independently of Gifts.
  </Card>

  <Card title="Query Donations by Date Range" icon="calendar" href="/crm/workflows/query-donations-by-date-range">
    Read Gifts back out for reporting or reconciliation.
  </Card>

  <Card title="Stripe to Virtuous CRM" icon="layer-group" href="/crm/recipes/stripe-to-virtuous">
    A complete integration recipe showing Stripe gift events flowing into Virtuous.
  </Card>
</CardGroup>
