Skip to main content
The Raise webhook system supports 15 distinct event types. The OpenAPI spec exposes them as an integer enum ([10, 11, 12, 20, 21, 22, 30, 31, 32, 40, 41, 42, 50, 51, 52]) without labels. This page covers what the integer pattern suggests, the discovery path for confirming the labels, and the practical patterns for subscribing to and handling events.

The 15 event type integers

The full enum from the spec:
10, 11, 12,
20, 21, 22,
30, 31, 32,
40, 41, 42,
50, 51, 52
The grouping is striking — five tens (10s, 20s, 30s, 40s, 50s) with three consecutive integers in each group. This pattern almost certainly corresponds to 5 resource families × 3 event actions per family.
⚠️ Spec gap: The Raise OpenAPI spec doesn’t label the EventType enum integers. The pattern suggests resource × action semantics (e.g., 10 = gift created, 11 = gift updated, 12 = gift deleted), but the canonical mapping is not documented.The discovery path below identifies labels by inspecting webhook delivery logs — the eventTypeDisplay field on WebhookLogListModel provides the human-readable label for each integer. Subscribe to a broad set of events on a test webhook, generate sample activity, and read the resulting log entries to learn the labels.

The likely resource groups

Based on the integer pattern and the Raise data model, the 5 groups likely correspond to:
Integer groupLikely resource family
10, 11, 12Gifts
20, 21, 22RecurringGifts
30, 31, 32Donors
40, 41, 42Campaigns
50, 51, 52Pages / Donation Forms
And the three per-family integers likely correspond to lifecycle actions — typically created, updated, deleted or similar. So 10 would be “gift created”, 11 “gift updated”, 12 “gift deleted” — with the same pattern applying to each family. Treat this mapping as a working hypothesis, not as the spec’s contract. The discovery path below confirms it for any specific event integer your integration cares about.

Discovering the mapping

The WebhookLogListModel schema (returned by GET /api/Webhook/log/list and the per-webhook log endpoints) carries both eventType (integer) and eventTypeDisplay (human-readable label). Inspecting log entries reveals the mapping for each integer that has fired in the customer’s environment.

A discovery script

Run this once against a customer’s webhook subscription to learn the integer-to-label mapping:
JavaScript
async function discoverEventTypeLabels(webhookId) {
  const params = new URLSearchParams({ Take: '500', SortBy: 'createddatetime' });
  const response = await fetch(
    `https://prod-api.raisedonors.com/api/Webhook/${webhookId}/log/list?${params}`,
    { headers: { Authorization: `Bearer ${process.env.RAISE_API_TOKEN}` } }
  );
  const logs = await response.json();

  // Build the integer-to-label map from observed events
  const mapping = new Map();
  for (const log of logs.items) {
    if (!mapping.has(log.eventType)) {
      mapping.set(log.eventType, log.eventTypeDisplay);
    }
  }

  return Object.fromEntries(mapping);
}

const mapping = await discoverEventTypeLabels(123);
console.log('Event type labels:', mapping);
// Example output:
// {
//   10: 'Gift Created',
//   11: 'Gift Updated',
//   ...
// }
The script returns whatever event types have been delivered through the subscription. For a complete mapping of all 15 event types, the customer’s account needs to have generated activity across all 5 resource families with each action type — which may take time to accumulate naturally.

Forcing event generation in a test environment

For development environments where you need to discover the full mapping faster, generate test activity that triggers each event type:
Event integerHow to generate
10, 11, 12 (likely Gift)Submit a donation via POST /api/Raise/give; update or refund the resulting gift
20, 21, 22 (likely RecurringGift)Submit a recurring donation; update the schedule; cancel it
30, 31, 32 (likely Donor)Donations create donors; PATCH a donor’s email; archive a donor
40, 41, 42 (likely Campaign)Create a campaign via POST /api/Campaign; update it; delete it
50, 51, 52 (likely Page)Page events likely fire from form publish or page-generation actions
Subscribe a test webhook to all 15 integers, trigger the activity, and inspect the resulting logs to learn the labels.

Subscribing to specific events

Once you know which integers correspond to the events your integration cares about, subscribe via the eventTypesList field on the WebhookRequest:
JavaScript
async function subscribeToGiftEvents() {
  return fetch(
    'https://prod-api.raisedonors.com/api/Webhook',
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.RAISE_API_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        name: 'Gift event subscription',
        notificationUrl: 'https://partner.example.com/raise-webhooks',
        eventTypesList: [10, 11, 12], // Subscribe to all gift events (per the working hypothesis)
        format: 1,
        status: 1,
        securityToken: process.env.WEBHOOK_SECRET,
      }),
    }
  ).then((r) => r.json());
}
For broad subscriptions that capture all events from a customer:
JavaScript
const ALL_EVENT_TYPES = [10, 11, 12, 20, 21, 22, 30, 31, 32, 40, 41, 42, 50, 51, 52];

async function subscribeToEverything() {
  return fetch(
    'https://prod-api.raisedonors.com/api/Webhook',
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.RAISE_API_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        name: 'All Raise events',
        notificationUrl: 'https://partner.example.com/raise-webhooks/all',
        eventTypesList: ALL_EVENT_TYPES,
        format: 1,
        status: 1,
        securityToken: process.env.WEBHOOK_SECRET,
      }),
    }
  ).then((r) => r.json());
}
For most integrations, subscribing to a focused subset is better than subscribing to everything — fewer events to filter, less noise in logs, and clearer integration semantics.

What event payloads contain

The exact payload shape varies by event type. The Raise OpenAPI spec doesn’t document each event’s payload schema explicitly — the schemas come from inspecting actual deliveries via the log endpoints. In general, partner integrations should expect:
ElementDescription
Event type identifierThe integer matching the EventType enum
The triggering resourceThe Gift, Donor, RecurringGift, etc. that the event is about, typically as a complete record matching the relevant *Model schema
Timing metadataWhen the event occurred

Inspecting a real payload

The fastest way to learn an event’s exact shape:
cURL
# 1. Find a recent delivery log for the event type you're interested in
curl "https://prod-api.raisedonors.com/api/Webhook/123/log/list?Take=10" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

# 2. Fetch the specific log's payload
curl https://prod-api.raisedonors.com/api/Webhook/123/log/456 \
  -H "Authorization: Bearer YOUR_API_TOKEN"
The single-log response includes payLoad — the exact body delivered to the partner endpoint. Inspect this to understand the JSON structure for the event types your integration handles.

Routing events on the partner side

A common pattern: a single webhook endpoint receives all event types and routes them to per-type handlers based on the event type integer.
JavaScript
const handlers = {
  10: handleGiftCreated,
  11: handleGiftUpdated,
  12: handleGiftDeleted,
  20: handleRecurringGiftCreated,
  21: handleRecurringGiftUpdated,
  22: handleRecurringGiftCancelled,
  30: handleDonorCreated,
  31: handleDonorUpdated,
  // ...
};

app.post('/raise-webhooks', async (req, res) => {
  // 1. Verify signature before parsing — see Signature Verification
  if (!verifySignature(req)) {
    return res.status(401).send('Invalid signature');
  }

  // 2. Acknowledge quickly
  res.status(200).send('OK');

  // 3. Process asynchronously
  await eventQueue.publish({
    eventType: req.body.eventType,
    payload: req.body,
    receivedAt: new Date(),
  });
});

// Worker that drains the queue
async function processEvent(event) {
  const handler = handlers[event.eventType];
  if (!handler) {
    console.warn(`No handler for event type ${event.eventType}`);
    return;
  }

  await handler(event.payload);
}
Three patterns this gets right:
  • Verify before parsing. Don’t trust the body’s eventType field until the signature has been verified.
  • Acknowledge quickly. Return 200 OK before processing — see Webhooks Overview: What the partner endpoint should do.
  • Tolerate unknown event types. If Raise adds new event types in the future or the customer subscribes to additional types after deployment, your handler should log and skip unknown types rather than throw.

Per-event handling patterns

The specific handlers depend on what your integration does. A few common patterns:

Gift created (likely event 10)

The most common event partners subscribe to — a new donation completed. Typical handlers:
JavaScript
async function handleGiftCreated(payload) {
  const gift = payload;

  // 1. Send a thank-you email (if your integration handles donor stewardship)
  await emailService.sendThankYou(gift.donor.email, {
    amount: gift.formattedAmount,
    project: gift.projects[0]?.projectName,
  });

  // 2. Sync to your external system (if your integration is a data pipeline)
  await externalSystem.recordDonation({
    raiseGiftId: gift.id,
    amount: gift.amount,
    date: gift.date,
    donorEmail: gift.donor.email,
  });

  // 3. Alert internal team for major gifts (if applicable)
  if (gift.amount >= 1000) {
    await slackNotifier.alert(`Major gift: ${gift.formattedAmount} from ${gift.donor.name}`);
  }
}

Recurring gift created or cancelled

Useful for partner integrations that maintain donor stewardship state:
JavaScript
async function handleRecurringGiftCreated(payload) {
  const schedule = payload;
  await donorJourney.recordEvent({
    donorId: schedule.donor.id,
    event: 'started_recurring',
    amount: schedule.amount,
    frequency: schedule.formattedFrequency,
  });
}

async function handleRecurringGiftCancelled(payload) {
  const schedule = payload;
  await donorJourney.recordEvent({
    donorId: schedule.donor.id,
    event: 'cancelled_recurring',
    cancelledAt: new Date(),
  });
  // Trigger retention workflow
  await retentionService.scheduleOutreach(schedule.donor.id);
}

Donor updated

Most useful for partner integrations doing two-way sync with an external CRM:
JavaScript
async function handleDonorUpdated(payload) {
  const donor = payload;

  // Sync changes to your external system
  await externalCrm.updateContact({
    raiseId: donor.id,
    email: donor.email,
    firstName: donor.firstName,
    lastName: donor.lastName,
  });
}

Distinguishing recurring vs. one-time gift events

A common integration question: when a Gift event fires (event 10, likely “Gift Created”), is the gift one-time or one of a recurring schedule’s payments? The answer is in the Gift payload itself: check the recurringGiftId field.
JavaScript
async function handleGiftCreated(payload) {
  const gift = payload;

  if (gift.recurringGiftId) {
    // This gift is a payment in a recurring schedule
    await handleRecurringPayment(gift);
  } else {
    // This is a one-time donation
    await handleOneTimeDonation(gift);
  }
}
This pattern matters for thank-you flows — donors signing up for recurring giving typically get a “thanks for signing up for monthly support!” email, while donors making subsequent recurring payments get a lighter-touch acknowledgment. Distinguishing the two cases at the integration level produces a better donor experience.

Handling event ordering

Webhook events for the same resource are typically delivered in order — a gift created event arrives before a gift updated event for the same gift. But ordering across resources isn’t guaranteed, and rare delivery hiccups can cause out-of-order arrival. Two patterns help:

Tolerate out-of-order events with state checks

For workflows that depend on event order:
JavaScript
async function handleGiftUpdated(payload) {
  const gift = payload;

  // Check if we've already seen this gift
  const existing = await db.findByRaiseGiftId(gift.id);

  if (!existing) {
    // We're seeing an update before the create — treat as a create
    await handleGiftCreated(gift);
    return;
  }

  // Normal update flow
  await db.update(existing.id, gift);
}
This pattern handles out-of-order updates by upgrading the update to a create if needed.

Use modifiedDate for tie-breaking

When an update event arrives for a record you already have, compare timestamps before applying:
JavaScript
async function handleGiftUpdated(payload) {
  const gift = payload;
  const existing = await db.findByRaiseGiftId(gift.id);

  if (existing && existing.modifiedDate >= gift.modifiedDate) {
    // We already have a newer version of this record — ignore the older update
    return;
  }

  await db.update(existing.id, gift);
}
This produces last-writer-wins semantics keyed on modifiedDate rather than on arrival order.

A complete subscription strategy

Pulling the patterns together: for a typical partner integration that needs to react to donation activity, the recommended subscription strategy:
EventSubscribe?Why
Gift created (likely 10)YesPrimary signal — new donations
Gift updated (likely 11)OptionalUseful for integrations syncing gift state
Gift deleted (likely 12)OptionalUseful for integrations that need to handle deletions
RecurringGift created (likely 20)Yes for stewardship integrationsSignals donor commitment
RecurringGift updated (likely 21)OptionalCatches hasPaymentFailed changes if no polling
RecurringGift cancelled (likely 22)Yes for stewardshipTriggers retention workflows
Donor events (30, 31, 32)Yes for sync integrationsTwo-way sync with external CRM
Campaign events (40, 41, 42)RareMost integrations don’t react to Campaign changes
Page events (50, 51, 52)RareMost integrations focus on Donor/Gift activity
A typical partner subscription might cover [10, 11, 20, 21, 22, 30, 31] — the events that drive most donor-and-gift workflows without subscribing to everything.

Where to go next

Webhooks Overview

The full subscription management surface — create, update, delete, inspect logs.

Signature Verification

Validate that incoming requests actually came from Raise.

Idempotency and Safe Reprocessing

Handle duplicate deliveries without side effects.

Retry Behavior

What happens when the partner endpoint doesn’t respond successfully.
Last modified on May 20, 2026