> ## 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.

# Customize the Donation Flow

> Patterns for customizing the donor's path through a Raise donation form — pre-populating donor data via URL parameters, generating personalized hosted pages, overriding designations, and customizing post-donation behavior.

The donation form's behavior is configured in the Raise admin UI, but partner integrations can still customize the donor's path through the form in meaningful ways. Pre-populating donor identity from URL parameters, generating fully personalized hosted pages, overriding designations per-link, and shaping the post-donation experience are all patterns that partner integrations use to lift conversion and produce a smoother donor experience.

This recipe covers the four main customization surfaces and the patterns that combine them into typical integration scenarios.

## What you can and can't customize

| Surface                                 | API-customizable? | Mechanism                                                                                                   |
| --------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------- |
| **Donor identity (pre-population)**     | Partially         | URL parameters on the form's landing URL; `POST /api/Donor/{id}/generate-page` for fully personalized pages |
| **Designation defaults**                | Yes               | `projectOverrideCode` on `POST /api/Raise/give`; URL parameter on the form's landing URL                    |
| **Campaign / Segment attribution**      | Yes               | URL parameters; explicit fields on `POST /api/Raise/give`                                                   |
| **Amount defaults / suggested amounts** | No (admin UI)     | Configured per-form in the Raise admin UI                                                                   |
| **Form fields and validation**          | No (admin UI)     | Configured per-form in the Raise admin UI                                                                   |
| **Branding and styling**                | No (admin UI)     | Configured per-form in the Raise admin UI                                                                   |
| **Success message / redirect URL**      | No (admin UI)     | Configured per-form in the Raise admin UI                                                                   |
| **UTM and marketing attribution**       | Yes               | Standard UTM URL parameters on the landing URL                                                              |

The pattern: partner integrations control the donor's *path to the form* and the *parameters they arrive with*, but the form's actual appearance and validation are admin-team concerns.

***

## Pattern 1: URL parameter pre-population

The simplest customization. Pass donor identity, designation defaults, or attribution data as query parameters on the form's landing URL. The form reads these on load and pre-fills the relevant fields.

### A renewal email link

```javascript JavaScript theme={null}
function buildRenewalUrl(donor, formLandingUrl) {
  const params = new URLSearchParams({
    firstName: donor.firstName,
    lastName: donor.lastName,
    email: donor.email,
    amount: donor.lastGiftAmount || 50,        // Suggested amount based on last gift
    utm_source: 'renewal-email',
    utm_campaign: '2025-annual-renewal',
  });
  return `${formLandingUrl}?${params.toString()}`;
}

// In your email templating
const renewalUrl = buildRenewalUrl(donor, 'https://customer.raisedonors.com/donate/general');
await emailService.send({
  to: donor.email,
  template: 'annual-renewal',
  data: { donor, renewalUrl },
});
```

The donor clicks the link, lands on the form with their identity already filled in, and only needs to confirm the amount and complete payment.

<Note>
  The exact URL parameter names the form accepts depend on the form's configuration in the Raise admin UI. Common conventions include `firstName`, `lastName`, `email`, `amount`, and the standard UTM parameters — but the form team controls which parameters are honored and how. Coordinate with the customer's admin team to confirm the supported parameters for each form.
</Note>

### Designation override

For donors arriving through a specific campaign or appeal, override the form's default Project designation:

```javascript JavaScript theme={null}
function buildCampaignUrl(formLandingUrl, projectCode) {
  const params = new URLSearchParams({
    projectOverrideCode: projectCode,
  });
  return `${formLandingUrl}?${params.toString()}`;
}

// A "Support the Gotham Outreach" link from a specific appeal email
const supportingUrl = buildCampaignUrl(
  'https://customer.raisedonors.com/donate/general',
  'GOTHAM-OUTREACH'
);
```

Combined with UTM parameters, this lets the customer track which channels drove which designations.

### Segment attribution

The `segmentOverrideCode` URL parameter sets the segment attribution for the resulting gift:

```javascript JavaScript theme={null}
const params = new URLSearchParams({
  segmentOverrideCode: 'EMAIL-Q1-2025',
  utm_source: 'email',
  utm_medium: 'newsletter',
  utm_campaign: 'q1-appeal',
});
```

The gift records the segment attribution in `gift.segment` (visible in the webhook payload and via API queries). Useful for per-channel reporting.

***

## Pattern 2: personalized hosted pages via `generate-page`

URL parameters are convenient but limited — they expose the donor's identity in the URL (visible in browser histories, server logs, etc.) and can produce ugly links. For higher-stakes outreach, the `POST /api/Donor/{donorId}/generate-page` endpoint creates a personalized page tied to the donor by an opaque token.

### When to use generate-page over URL parameters

| Use generate-page when                             | Use URL parameters when           |
| -------------------------------------------------- | --------------------------------- |
| Donor identity should not appear in the URL        | Identity in the URL is acceptable |
| You're sending a payment-update prompt             | First-time donor outreach         |
| The link should be donor-specific and not reusable | The link is broadly shareable     |
| You want shorter, cleaner URLs                     | URL ugliness is acceptable        |

The generate-page approach is the standard pattern for payment-method-update prompts (see [Handle Failed Payments: notifying the donor](/raise/workflows/handle-failed-payments#notifying-the-donor)) and for major-donor cultivation messages.

### Generating a personalized page

```javascript JavaScript theme={null}
async function generatePersonalizedPage(donorId, raiseToken) {
  const response = await fetch(
    `https://prod-api.raisedonors.com/api/Donor/${donorId}/generate-page`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${raiseToken}`,
        'Content-Type': 'application/json',
      },
    }
  );

  if (!response.ok) {
    throw new Error(`generate-page failed: ${response.status}`);
  }

  const result = await response.json();
  return result.pageUrl; // The URL to send to the donor
}
```

<Note>
  The exact request body shape for `POST /api/Donor/{donorId}/generate-page` is not detailed in the spec (the body schema is empty). The endpoint likely accepts parameters like `campaignId` or `formId` to identify which form to personalize. Confirm against the live API before relying on specific request body fields.
</Note>

### Embedding the personalized URL in an email

```javascript JavaScript theme={null}
async function sendPersonalizedRenewalEmail(donor, raiseToken) {
  const pageUrl = await generatePersonalizedPage(donor.id, raiseToken);

  await emailService.send({
    to: donor.email,
    template: 'personalized-renewal',
    data: {
      firstName: donor.firstName,
      lastGiftAmount: donor.lastGiftAmount,
      pageUrl,
    },
  });
}
```

The donor receives an email like:

> Hi Bruce,
>
> Last year you gave \$250 to our Gotham Outreach Program. Would you renew your support?
>
> [Renew now](https://customer.raisedonors.com/p/abc123token)
>
> Thank you for your generosity.

When the donor clicks the link, they land on a form fully pre-filled with their identity. They confirm the amount and complete payment — typically a much higher conversion rate than a generic "donate now" email.

***

## Pattern 3: explicit fields on `POST /api/Raise/give`

For integrations that submit donations directly (rather than embedding a form), the `DonatePaymentRequest` body fields provide the richest customization surface. Any field on the request shapes the resulting Gift record.

### Pre-attributed donations

For partner integrations that capture donations through their own UI and submit to Raise, set the attribution fields explicitly:

```javascript JavaScript theme={null}
async function processCustomDonation({ donor, amount, paymentMethodId, source }) {
  return fetch('https://prod-api.raisedonors.com/api/Raise/give', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.RAISE_API_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      amount,
      paymentMethodId,
      paymentMethodType: 'CreditCard',
      donor,
      segment: source.segment,
      segmentOverrideCode: source.segmentCode,
      motivationCodeId: source.motivationCodeId,
      googleUtmSource: source.utmSource,
      googleUtmCampaign: source.utmCampaign,
      googleUtmMedium: source.utmMedium,
      comments: source.donorComment,
    }),
  }).then((r) => r.json());
}
```

The resulting Gift carries all of this attribution in its fields — visible in subsequent queries and webhook payloads.

### Tribute giving

Donors giving in honor or memory of someone can include tribute information:

```javascript JavaScript theme={null}
const donation = {
  amount: 100.00,
  paymentMethodId: 'tok_abc123',
  paymentMethodType: 'CreditCard',
  donor: { firstName: 'Bruce', lastName: 'Wayne', email: 'bruce@wayne.example' },
  tribute: {
    type: 'memorial',          // Or 'honor' — confirm valid values against live API
    firstName: 'Thomas',
    lastName: 'Wayne',
    notifyName: 'Martha Wayne',
    notifyAddress: { /* ... */ },
    notifyEmail: 'martha@wayne.example',
  },
};
```

The resulting Gift carries the tribute information in `gift.tribute`. Useful for memorial giving workflows where the gift's purpose is to honor someone other than the donor.

### Recurring gift setup with custom designation

For recurring schedules created through `POST /api/Raise/give` with `isRecurring: true`, the designation applies to each subsequent payment:

```javascript JavaScript theme={null}
const recurringDonation = {
  amount: 25.00,
  paymentMethodId: 'tok_abc123',
  paymentMethodType: 'CreditCard',
  isRecurring: true,
  frequency: 12,                 // Monthly (per working hypothesis)
  donor: { /* ... */ },
  projects: [
    { projectId: 12, amount: 15.00 },   // 60% to Project A
    { projectId: 47, amount: 10.00 },   // 40% to Project B
  ],
};
```

Every monthly payment produced by this schedule splits the same way — `gift.projects[]` on each gift mirrors the schedule's split.

***

## Pattern 4: combining customizations for specific use cases

The four customization surfaces combine into a few common integration scenarios. Each scenario uses two or three of the surfaces.

### Major donor cultivation outreach

A high-touch personalized outreach for a previously-large donor:

```javascript JavaScript theme={null}
async function sendMajorDonorCultivation(donor, customerId) {
  const settings = customerSettings[customerId];

  // 1. Generate a personalized page (Pattern 2)
  const pageUrl = await generatePersonalizedPage(donor.id, settings.raiseApiToken);

  // 2. Fetch the donor's giving history for context (Donors page)
  const giftHistory = await fetchDonorGifts(donor.id, settings.raiseApiToken);
  const lastGift = giftHistory[0];

  // 3. Personalize the email with the donor's history
  await emailService.send({
    to: donor.email,
    template: 'major-donor-cultivation',
    data: {
      firstName: donor.firstName,
      lastGiftAmount: lastGift?.formattedAmount,
      lastGiftDate: lastGift?.date,
      totalLifetimeGiving: donor.giftsum,
      pageUrl,
      contactPersonName: settings.majorDonorContactName,
      contactPersonEmail: settings.majorDonorContactEmail,
    },
  });
}
```

The recipient sees an email with their actual giving history mentioned and a personalized link to give again. Combines pattern 2 (personalized page) with rich personalization data from the donor's API record.

### Multi-channel attribution

Different links for different channels — each producing distinct attribution in the Gift records:

```javascript JavaScript theme={null}
function buildAttributedUrl(formLandingUrl, channel, campaign) {
  const channelConfigs = {
    email: {
      segmentOverrideCode: `EMAIL-${campaign}`,
      utm_source: 'email',
      utm_medium: 'newsletter',
    },
    social: {
      segmentOverrideCode: `SOCIAL-${campaign}`,
      utm_source: 'social',
      utm_medium: 'organic',
    },
    paid: {
      segmentOverrideCode: `PAID-${campaign}`,
      utm_source: 'google',
      utm_medium: 'cpc',
    },
  };

  const params = new URLSearchParams({
    ...channelConfigs[channel],
    utm_campaign: campaign,
  });
  return `${formLandingUrl}?${params}`;
}

// Build channel-specific URLs
const emailUrl = buildAttributedUrl('https://customer.raisedonors.com/donate', 'email', 'q1-2025');
const socialUrl = buildAttributedUrl('https://customer.raisedonors.com/donate', 'social', 'q1-2025');
const paidUrl = buildAttributedUrl('https://customer.raisedonors.com/donate', 'paid', 'q1-2025');
```

Each URL produces gifts with distinct `segment` and UTM attribution — visible in per-channel reporting via `POST /api/Gift/query`.

### Pre-filled multi-step flow

A two-step flow where the partner captures donor details first, then redirects to the Raise form for payment:

<Steps>
  <Step title="Donor fills out the partner's pre-form">
    Captures name, email, and intent (one-time vs. recurring). Lets the partner do its own validation and styling.
  </Step>

  <Step title="Partner generates the Raise form URL with pre-populated parameters">
    URL parameters carry the captured details forward into the Raise form.
  </Step>

  <Step title="Donor redirects to the Raise form, identity already pre-filled">
    They only need to enter payment details and confirm the amount.
  </Step>

  <Step title="Form submits to Raise, completing the donation">
    The webhook fires with the captured attribution intact.
  </Step>
</Steps>

```javascript JavaScript theme={null}
// In the partner's pre-form handler
app.post('/start-donation', async (req, res) => {
  const { firstName, lastName, email, amount, recurring } = req.body;

  const formUrl = recurring
    ? customerSettings.recurringFormUrl
    : customerSettings.oneTimeFormUrl;

  const params = new URLSearchParams({
    firstName,
    lastName,
    email,
    amount,
    utm_source: 'partner-preform',
    utm_campaign: 'partner-integration',
  });

  res.redirect(`${formUrl}?${params}`);
});
```

This pattern is useful when the partner needs to capture additional information beyond what the Raise form supports — for example, a partner-specific source tracking field, or a custom consent step.

***

## Post-donation customization

The post-donation experience is largely admin-UI-configured (success page, redirect URL, etc.). What partner integrations can customize:

### Custom thank-you content via webhook

After the donation completes, the webhook fires and the partner integration can send custom thank-you content separate from the form's built-in success display:

```javascript JavaScript theme={null}
async function handleGiftCreated(customerId, gift) {
  // Send custom thank-you email matching the partner's branding
  await sendCustomThankYou(customerId, gift);

  // Display a custom message in the partner's UI on the donor's next visit
  await flagDonorForCustomMessage(gift.donor.email, {
    message: 'Thank you for your generous gift!',
    expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
  });
}
```

See [Post-Donation Thank-You Flows](/raise/recipes/post-donation-thank-you-flows) for the complete patterns.

### Form return URL customization

The form's "where to go after success" behavior is configured in the Raise admin UI. The partner integration can request specific redirect URLs from the customer:

> Coordinate with the customer's admin team to set the form's post-submission redirect to a partner-controlled URL, like `https://partner.example.com/thank-you?giftId={giftId}`.

Once configured, the partner can render a customized thank-you page when the donor lands there:

```javascript JavaScript theme={null}
app.get('/thank-you', async (req, res) => {
  const giftId = req.query.giftId;

  // Look up the gift for personalization
  const gift = await fetchGift(giftId);

  res.render('custom-thank-you', {
    firstName: gift.donor?.firstName,
    amount: gift.formattedAmount,
    project: gift.projects?.[0]?.projectName,
    nextStepCta: buildNextStepCta(gift),
  });
});
```

The donor sees a fully partner-branded thank-you page with their gift details — a smoother experience than a generic "Thank you!" message.

<Note>
  The exact mechanism by which the Raise form passes the gift ID to the redirect URL (query parameter, fragment, etc.) is configured per-form in the admin UI and not documented in the API spec. Confirm against the actual form configuration before parsing the URL.
</Note>

***

## Anti-patterns to avoid

A few customization patterns that look attractive but cause issues:

| Anti-pattern                                                            | Why it's bad                                                                                                                                             |
| ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Iframe-wrapping the form on a custom page**                           | Two layers of iframes typically break the form's resize behavior, payment processor integration, and 3DS authentication flows. Use the embed code as-is. |
| **Capturing card details on the partner's side and submitting via API** | Puts the partner in PCI DSS scope. Use Raise's tokenization.                                                                                             |
| **Hardcoding URL parameter names**                                      | The parameter names depend on the form's admin-UI configuration. Coordinate with the customer rather than hardcoding.                                    |
| **Storing donor identity in URL parameters across redirects**           | URL parameters appear in browser histories and server logs. Use the personalized-page endpoint for sensitive flows.                                      |
| **Trying to customize the form's appearance via partner-side CSS**      | The form is on Raise's domain — partner CSS can't reach into it. Coordinate styling with the customer's admin team.                                      |

The general principle: customize *around* the form (the donor's path to it, the post-donation flow) rather than *inside* it.

***

## Where to go next

<CardGroup cols={2}>
  <Card title="Post-Donation Thank-You Flows" icon="envelope-open-text" href="/raise/recipes/post-donation-thank-you-flows">
    The deep dive on customizing what happens after the donation completes.
  </Card>

  <Card title="Embed a Form on a Website" icon="globe" href="/raise/recipes/embed-a-form-on-a-website">
    The complete integration recipe that this customization layer fits into.
  </Card>

  <Card title="Process a Donation" icon="hand-holding-dollar" href="/raise/workflows/process-a-donation">
    The `POST /api/Raise/give` reference for direct-submission customization.
  </Card>

  <Card title="Recurring Giving Portal" icon="arrows-rotate" href="/raise/recipes/recurring-giving-portal">
    A donor-self-service portal that combines several customization patterns.
  </Card>
</CardGroup>
