Skip to main content
POST
/
api
/
donations
Create Donation
curl --request POST \
  --url https://api.example.com/api/donations \
  --header 'Content-Type: application/json' \
  --data '
{
  "campaignId": "<string>",
  "amount": 123,
  "currency": "<string>",
  "frequency": "<string>",
  "paymentMethod": "<string>",
  "coverFees": true,
  "donor": {
    "email": "<string>",
    "firstName": "<string>",
    "lastName": "<string>",
    "anonymous": true
  },
  "dedication": {
    "type": "<string>",
    "name": "<string>",
    "message": "<string>",
    "notifyName": "<string>",
    "notifyEmail": "<string>"
  },
  "publicMessage": "<string>",
  "emailOptIn": true,
  "customFieldResponses": [
    {
      "customFieldId": "<string>",
      "value": "<string>"
    }
  ],
  "givingLevelId": "<string>",
  "ticketCount": 123,
  "utmSource": "<string>",
  "utmMedium": "<string>",
  "utmCampaign": "<string>",
  "referrerUrl": "<string>"
}
'
{
  "donation": {
    "id": "<string>",
    "amountCents": 123,
    "status": "<string>",
    "frequency": "<string>"
  },
  "clientSecret": "<string>",
  "fees": {
    "donationAmount": 123,
    "processingFee": 123,
    "platformFee": 123,
    "totalCharged": 123,
    "netToOrg": 123
  }
}

Create Donation

Creates a donation record, calculates fees, and returns a Stripe PaymentIntent client secret for completing the payment on the frontend.
The campaign must be in ACTIVE status. The org must have completed Stripe Connect onboarding before donations can be processed.

Request Body

campaignId
string
required
The ID of the campaign to attribute this donation to.
amount
integer
required
Donation amount in cents. Minimum 100 ($1.00). For event campaigns with a fixed ticket price, this is the per-ticket amount multiplied by ticketCount.
currency
string
default:"usd"
Three-letter ISO currency code. Currently only "usd" is supported.
frequency
string
required
Giving frequency. One of: "one_time", "monthly", "quarterly", "annual".
paymentMethod
string
required
Payment method type. One of: "card", "ach", "apple_pay", "google_pay".
coverFees
boolean
required
Whether the donor is covering processing and platform fees. When true, the total charged to the donor is grossed up so the nonprofit receives the full amount.
donor
object
required
Donor contact information.
dedication
object
Optional tribute dedication. If provided, type and name are required.
publicMessage
string
A public message from the donor, shown on the campaign’s donor roll. Max 280 characters.
emailOptIn
boolean
default:"false"
Whether the donor opts in to future email communications from the organization.
customFieldResponses
array
Responses to campaign-specific custom fields.
givingLevelId
string
The ID of a pre-configured giving level to associate with this donation. Optional.
ticketCount
integer
For event campaigns with a fixed ticket price, the number of tickets being purchased (1–20). The total amount is derived from ticketCount × campaign.ticketPriceCents.
utmSource
string
UTM source parameter for attribution tracking (e.g., "email", "facebook"). Max 100 characters.
utmMedium
string
UTM medium parameter (e.g., "newsletter", "cpc"). Max 100 characters.
utmCampaign
string
UTM campaign parameter (e.g., "year-end-2026"). Max 100 characters.
referrerUrl
string
The full URL of the referring page. Max 500 characters.

Response

donation
object
The created donation record.
clientSecret
string
The Stripe PaymentIntent client secret. Pass this to Stripe Elements or the Stripe.js confirmPayment() method on the frontend to complete payment collection.
fees
object
The calculated fee breakdown for this donation.

Example

curl -X POST https://givelink-api-production.up.railway.app/api/donations \
  -H "Content-Type: application/json" \
  -d '{
    "campaignId": "clx9876543210",
    "amount": 5000,
    "currency": "usd",
    "frequency": "monthly",
    "paymentMethod": "card",
    "coverFees": true,
    "donor": {
      "email": "jane@example.com",
      "firstName": "Jane",
      "lastName": "Smith",
      "anonymous": false
    },
    "publicMessage": "Happy to support this cause!",
    "emailOptIn": true,
    "utmSource": "email",
    "utmMedium": "newsletter",
    "utmCampaign": "spring-2026"
  }'
{
  "donation": {
    "id": "clx1111111111",
    "amountCents": 5000,
    "status": "PENDING",
    "frequency": "MONTHLY"
  },
  "clientSecret": "pi_1234_secret_5678",
  "fees": {
    "donationAmount": 5000,
    "processingFee": 142,
    "platformFee": 51,
    "totalCharged": 5193,
    "netToOrg": 5000
  }
}
Use the clientSecret with Stripe Elements on the frontend to collect payment details and confirm the PaymentIntent. The donation status remains PENDING until Stripe confirms payment via webhook.

Error Responses

StatusErrorDescription
400Campaign not foundThe campaignId does not exist.
400Campaign is not activeThe campaign must be in ACTIVE status.
400Minimum donation is $XThe amount is below the campaign’s minimum.
400Organization has not completed payment setupStripe Connect onboarding is incomplete.
400Sorry, not enough tickets availableEvent is sold out or insufficient capacity.
400Validation failedOne or more fields failed schema validation.