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

# Embed a Form

> The workflow for placing a Raise donation form on a partner-hosted website — embed code, post-embed responsibilities, and the patterns that handle the donor's return flow cleanly.

This workflow covers placing a Raise Donation Form on a website your integration controls — a customer's marketing site, a partner application, an event page. The embed mechanism uses HTML the customer obtains from the Raise admin UI; the partner integration's role is to deliver that HTML to the right page and handle what happens after the donor completes the form.

This is a different workflow from [Configure a Donation Form](/raise/workflows/configure-a-donation-form), which covers the partner-side wiring (webhooks, attribution capture) for any form regardless of embed surface. Use this page when the integration actually places a form on a website.

## Embed surfaces

There are three distinct embed surfaces, and which one applies depends on what kind of integration you're building:

| Surface                                            | Description                                                                                                                                             | Embed source                                                                                                 |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| **Customer's own website**                         | The customer hosts a marketing site; donations are taken on a "Donate" page that embeds a Raise form.                                                   | Embed code from the Raise admin UI, placed directly on the site.                                             |
| **Partner-hosted page in a multi-tenant app**      | The partner integration provides a page (e.g., a campaign microsite, a donation widget within a partner product) that embeds the customer's Raise form. | Embed code from the Raise admin UI, retrieved during customer onboarding, served by the partner application. |
| **Custom donation flow with the partner's own UI** | The partner builds a fully custom donation experience and calls `POST /api/Raise/give` directly from a backend.                                         | No Raise form embed — the partner UI handles the entire donor experience.                                    |

This page focuses on the first two — embedding the Raise-hosted form. For the third (fully custom flow), see [Process a Donation](/raise/workflows/process-a-donation).

<Note>
  The exact embed mechanism (iframe, JavaScript snippet, or other) the Raise admin UI provides is an admin-UI concern, not an API concern. Walk through it with the customer's Raise administrator when setting up the integration. The patterns on this page apply regardless of the specific embed shape, since all of them deliver HTML/JS to the donor's browser and POST to `/api/Raise/give` on submission.
</Note>

## When to use this workflow

| Scenario                                                                | Approach                                                                                                                        |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Customer wants to add a donation form to their existing website         | Use this workflow. The customer's web team places the embed code; your integration handles the post-donation events.            |
| Partner application provides a donation widget that customers configure | Use this workflow. Your integration retrieves the embed code during customer onboarding and serves it through your application. |
| Building a fully custom donation form with the partner's own UI         | Skip this workflow. See [Process a Donation](/raise/workflows/process-a-donation) for the direct `POST /api/Raise/give` flow.   |
| Sending donors to a hosted Raise form via a direct link (no embed)      | This is the simplest case — share the form's landing URL with donors via email, SMS, etc. No embed needed.                      |

***

## The four-step embed workflow

### Step 1: customer obtains the embed code

The customer's Raise administrator opens the form in the admin UI and copies the embed code provided there. This step happens outside the API — your integration doesn't trigger it.

The customer provides the embed code to your integration through whatever onboarding flow you've built. Typically this is a settings page in your integration's UI where the customer pastes the code into a text field:

```text theme={null}
Settings → Donation Form Embed Code
[                                                                      ]
```

Validate that the pasted code is non-empty and matches the expected shape (e.g., contains an iframe or script tag pointing at a Raise domain). Don't try to parse or rewrite the embed code itself — treat it as opaque HTML to be rendered on the target page.

### Step 2: render the embed code on the target page

For an integration that hosts the page directly, render the embed code as HTML in the donation page's body:

```html HTML theme={null}
<!DOCTYPE html>
<html>
<head>
  <title>Donate to Wayne Foundation</title>
</head>
<body>
  <h1>Support Our Mission</h1>
  <p>Your gift makes a difference.</p>

  <!-- Begin Raise form embed (from customer's admin UI) -->
  {{ rawEmbedCode }}
  <!-- End Raise form embed -->
</body>
</html>
```

Three things matter at this point:

* **HTTPS only.** The embed code typically loads resources from Raise over HTTPS; embedding it on an HTTP page will produce mixed-content warnings or block the form entirely in modern browsers. Always serve the embedding page over HTTPS.
* **No Content Security Policy conflicts.** If the embedding page sets a restrictive CSP, allow the Raise origin for `frame-src`, `script-src`, and `connect-src` as appropriate for the embed mechanism. Coordinate the exact directives with the customer if needed.
* **Don't wrap or interfere with the embed.** The embed code is designed to work as-is. Wrapping the form in another iframe or modifying the embed's HTML can break the donor experience.

### Step 3: capture donation events server-side

When a donor completes the embedded form, the form's submission flow calls `POST /api/Raise/give` and creates the Gift in Raise. Your integration learns about the donation through the **webhook subscription** — not by parsing the embedded form's UI.

```javascript JavaScript theme={null}
// Webhook handler on your server
app.post('/raise-webhooks', async (req, res) => {
  // Validate signature first — see /raise/webhooks/signature-verification
  if (!validateSignature(req)) {
    return res.status(401).send('Invalid signature');
  }

  const event = req.body;
  if (event.eventType === GIFT_CREATE_EVENT_TYPE) {
    await processCompletedDonation(event.payload);
  }

  res.status(200).send('OK');
});
```

This is the same webhook subscription you'd set up for any Raise integration — see [Configure a Donation Form](/raise/workflows/configure-a-donation-form#step-2-subscribe-to-relevant-webhook-events).

The webhook is the canonical "donation complete" signal. Don't try to detect form completions through DOM events, postMessage listeners, or other client-side mechanisms — those are unreliable across browsers and embed types. The webhook is the right channel.

### Step 4: handle the donor's return flow

After the form submission completes, the embedded form typically displays a thank-you screen within the embed. The donor stays on your page. Two patterns for what your integration does next:

#### Pattern A: stay on the embedding page

The simplest pattern — let the form's built-in thank-you display handle the user experience and let your integration's server-side webhook handler trigger any background work (thank-you email, CRM sync, etc.).

This is the right default when:

* The embedding page is a standalone donation page that doesn't need to do anything user-facing after the donation.
* The form's thank-you display is sufficient on its own.

#### Pattern B: redirect or display custom content

For more involved flows — multi-step processes, custom thank-you experiences, or integration-specific follow-ups — coordinate with the form's configuration to redirect or display custom content after submission.

The mechanism for this is form-configuration-side (set in the Raise admin UI) — not via API. Coordinate with the customer's admin team to configure the form's success behavior. From the partner side, what your integration controls is the destination URL the form redirects to and any logic on that destination page.

```javascript JavaScript theme={null}
// Example: a custom thank-you page that pulls the gift ID from the URL
// (set by the form's post-submission redirect, configured in admin UI)
app.get('/donation-thank-you', async (req, res) => {
  const giftId = req.query.giftId;

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

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

<Note>
  The form's exact post-submission redirect behavior (whether a `giftId` query parameter is appended, the redirect URL configuration, etc.) is set in the Raise admin UI and not documented in the API spec. Confirm against the live form configuration before relying on specific URL parameters.
</Note>

***

## Multi-customer embed patterns

For partner integrations that host donation pages for multiple customers, each customer's embed code needs to be served correctly to their respective pages.

### Pattern: per-customer embed code storage

Store the embed code as a per-customer setting:

```javascript JavaScript theme={null}
// Per-customer settings table
const customerSettings = {
  customer_acme: {
    raiseEmbedCode: '<iframe src="..." ></iframe>',
    raiseApiToken: '...',
    raiseWebhookId: 'webhook_123',
  },
  customer_beta: {
    raiseEmbedCode: '<script src="..."></script>',
    raiseApiToken: '...',
    raiseWebhookId: 'webhook_456',
  },
};

// Donation page handler — looks up the right embed code by customer
app.get('/donate/:customerId', async (req, res) => {
  const { customerId } = req.params;
  const settings = customerSettings[customerId];
  if (!settings?.raiseEmbedCode) {
    return res.status(404).send('Donation form not configured');
  }

  res.render('donation-page', {
    embedCode: settings.raiseEmbedCode,
    customerName: settings.displayName,
  });
});
```

The embed code is opaque HTML/JS — your integration doesn't need to understand what's inside it, just to serve it to the right page.

### Multiple forms per customer

A single customer often has multiple forms (general giving, monthly sustainers, capital campaign, etc.). Store each form's embed code separately and let the customer choose which form a specific page uses:

```javascript JavaScript theme={null}
const customerSettings = {
  customer_acme: {
    forms: {
      'general': { id: 1234, embedCode: '...' },
      'monthly': { id: 5678, embedCode: '...' },
      'capital-2025': { id: 9012, embedCode: '...' },
    },
  },
};

app.get('/donate/:customerId/:formKey', async (req, res) => {
  const { customerId, formKey } = req.params;
  const form = customerSettings[customerId]?.forms[formKey];
  if (!form) return res.status(404).send('Form not configured');

  res.render('donation-page', { embedCode: form.embedCode });
});
```

This produces URLs like `/donate/customer_acme/general`, `/donate/customer_acme/monthly`, etc. — clean per-form entry points without each form needing its own bespoke page.

***

## Testing the embed locally

For development, embed the form on a local page that runs over HTTPS. Tools that help:

| Tool                             | Use                                                                                  |
| -------------------------------- | ------------------------------------------------------------------------------------ |
| **ngrok or Cloudflare Tunnel**   | Expose your local development server over HTTPS with a real certificate.             |
| **Test-mode Raise organization** | Embed against a customer's test-mode account so submissions don't charge real cards. |
| **Browser developer tools**      | Verify there are no CSP errors, mixed-content warnings, or blocked resources.        |

A typical local-dev setup:

<Steps>
  <Step title="Start your local server over HTTPS">
    Use ngrok to expose `localhost:3000` over HTTPS at a public URL.
  </Step>

  <Step title="Configure the customer's test-mode form to allow your dev origin">
    If the form's embed has origin restrictions, add your ngrok URL to the allowlist temporarily. Coordinate with the customer's admin if needed.
  </Step>

  <Step title="Use the test-payment-method generator">
    `POST /api/Raise/generate-test-payment-method` produces a token usable in test mode. Use this for development submissions.
  </Step>

  <Step title="Subscribe your dev webhook to the test-mode events">
    Point your webhook subscription's `notificationUrl` at your ngrok URL so events from test-mode donations reach your local development server.
  </Step>

  <Step title="Run a test donation through the embedded form">
    Confirm the donation completes, the webhook arrives, and your post-donation logic fires correctly.
  </Step>
</Steps>

See [Local Testing](/raise/webhooks/local-testing) for the webhook-specific testing patterns.

***

## Common embed issues

A few issues come up frequently when embedding Raise forms. Quick references:

| Symptom                                                               | Likely cause                                                                                                                 |
| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Form doesn't load                                                     | Embedding page is HTTP, not HTTPS; CSP blocks the embed; browser ad-blocker filtering.                                       |
| Form loads but submissions fail                                       | Customer's account is misconfigured (e.g., no payment gateway); test-mode payment method used in non-test environment.       |
| Donation succeeds but webhook doesn't arrive                          | Webhook subscription's `notificationUrl` is wrong; signature validation failing; firewall blocking inbound from Raise's IPs. |
| Donor stays on the form's thank-you instead of returning to your site | Form's success behavior is configured to stay on Raise — coordinate with admin to change to a redirect.                      |
| Embedding multiple forms on the same page causes one to misbehave     | Embed codes may not be designed for multi-instance pages — confirm with admin team.                                          |

The webhook log endpoints (`GET /api/Webhook/{id}/log/list`) are particularly useful for investigating "donation succeeded but webhook didn't arrive" — they show whether Raise attempted delivery and how the receiver responded. See [Webhooks Overview](/raise/webhooks/overview).

***

## Where to go next

<CardGroup cols={2}>
  <Card title="Configure a Donation Form" icon="gear" href="/raise/workflows/configure-a-donation-form">
    The complementary workflow for the partner-side wiring around any form.
  </Card>

  <Card title="Process a Donation" icon="hand-holding-dollar" href="/raise/workflows/process-a-donation">
    The `POST /api/Raise/give` flow for fully-custom (non-embedded) donation experiences.
  </Card>

  <Card title="Webhooks Overview" icon="webhook" href="/raise/webhooks/overview">
    Set up the webhook subscription that delivers donation events to your integration.
  </Card>

  <Card title="Embed a Form on a Website (Recipe)" icon="globe" href="/raise/recipes/embed-a-form-on-a-website">
    An end-to-end recipe walking through a complete embed-and-react integration.
  </Card>
</CardGroup>
