The fundamental shape
For most partner integrations, Raise is the source and an external system is the destination: The customer’s fundraising team operates Raise (collecting donations, managing donors, configuring campaigns). The customer’s other teams need that data in their respective systems. The partner integration’s job is to keep those downstream systems aligned with what’s happening in Raise. The patterns on this page describe different ways to design that pipeline. Most production integrations use a combination of two or three.Pattern 1: webhook-driven push (the default)
The simplest pattern — Raise pushes events to the partner, the partner writes to the destination. The pattern in code:JavaScript
When this pattern fits
| Scenario | Why this pattern |
|---|---|
| Real-time sync is acceptable | Webhook events deliver in seconds |
| The destination has an upsert API | Idempotency comes from upsert semantics |
| The customer’s volume is reasonable | Webhook fan-out scales linearly with event count |
| The partner can host a public webhook receiver | HTTPS endpoint, signature verification, queue infrastructure |
When this pattern doesn’t fit alone
| Issue | Implication |
|---|---|
| Webhook events can be missed | Need a reconciliation backstop (pattern 4) |
| Initial customer state isn’t covered | Need a backfill pattern (pattern 5) |
| Destination has poor idempotency | Need dedup layer in the worker |
| Customer needs historical reporting before integration was live | Backfill required before steady-state matters |
Pattern 2: polled pull
For destinations that can’t receive webhooks (legacy systems, on-premise tools, batch-oriented warehouses), the partner integration polls Raise on a schedule: The pattern in code:JavaScript
When polled pull fits
| Scenario | Why this pattern |
|---|---|
| Destination is a batch-oriented data warehouse | Aligns with the destination’s natural cadence |
| Partner can’t host a public webhook receiver (firewall, on-premise) | No inbound port needed |
| Sync lag of 15–60 minutes is acceptable | Polling cadence determines latency |
| Customer’s volume is low enough that polling stays cheap | A few hundred to a few thousand records per cycle |
Trade-offs vs. webhook-driven
| Aspect | Webhook-driven | Polled pull |
|---|---|---|
| Latency | Seconds | Minutes (poll interval) |
| Rate-limit cost | Near-zero for detection | Each poll consumes budget |
| Missed events | Possible — needs backstop | Detected at next poll |
| Setup complexity | Subscribe to webhook | Schedule the cron job |
| Deletion detection | Catches delete events | Hard — deleted records don’t appear in queries |
Advancing the checkpoint correctly
A common bug in polled pull: advancing the checkpoint to “now” rather than to the last successful record’s timestamp:JavaScript
Pattern 3: hybrid (webhook + periodic reconciliation)
The most robust production pattern. Webhooks handle steady-state real-time sync; periodic reconciliation catches anything webhooks missed. The reconciler runs daily (or hourly for higher-stakes integrations) and verifies that every Raise record from yesterday made it to the destination. Gaps go back into the queue for re-processing. The pattern in code:JavaScript
Why hybrid is the production default
| Without reconciliation | With reconciliation |
|---|---|
| One missed webhook = one permanently missed record | Missed records detected within 24 hours |
| No way to verify the integration is working | Daily metric proves end-to-end correctness |
| Customer issues require ad-hoc investigation | Confidence to answer “did this gift make it?” |
Tuning reconciliation cadence
| Cadence | When to use |
|---|---|
| Daily | Most partner integrations — catches gaps with 1-day lag |
| Hourly | High-stakes integrations where multi-hour gaps matter |
| Weekly | Low-stakes integrations or supplementary to other safeguards |
| Real-time | Not feasible — would consume too much rate-limit budget |
Pattern 4: backfill + steady-state
For customers with existing data when the integration starts, a backfill pulls historical records before steady-state sync takes over. The full sequence:Customer onboarding triggers initial backfill
Pull historical records via
POST /api/Gift/query with pagination.Subscribe to webhooks (in parallel)
Set up the webhook subscription early so events fire to a queue from the start.
Backfill streams records to the same queue as the webhook
Both backfill and live events flow through one worker — single processing path.
When backfill completes, mark the customer as live
The queue continues to drain, now fed only by webhooks.
When backfill is needed
| Scenario | Backfill? |
|---|---|
| Customer has years of historical data; downstream needs it | Yes — backfill all |
| Customer is brand new to Raise | No — steady-state alone is sufficient |
| Customer only needs forward-looking sync | No — set the checkpoint to “now” and skip backfill |
| Customer wants partial history (e.g., last 2 years) | Backfill with a date filter |
Backfill performance
A customer with 100,000 historical gifts is a substantial backfill — atTake=1000 with 1-second throttling, that’s ~100 minutes of constant API calls. Plan for this:
JavaScript
Resumable backfill
If the backfill crashes partway through, it should resume from where it left off rather than starting over. Track progress in a checkpoint:JavaScript
Pattern 5: two-way coordination (rare)
When the partner integration writes back to Raise — for example, syncing donor preferences from an external system into Raise — coordination between the two directions becomes important. The pattern in code:JavaScript
The echo problem
When the partner writes to Raise, Raise fires a webhook back. Without coordination, the partner integration would treat the echo as an external change and try to sync it back to the external system — producing a loop. The “write attribution” pattern records the partner’s intent before writing so the echo can be recognized and ignored.When two-way sync is needed
| Scenario | Two-way needed? |
|---|---|
| Customer uses Raise as the source of truth | No — one-way out of Raise is enough |
| Customer’s email platform is canonical for opt-in state | Yes — opt-in changes need to flow into Raise |
| Customer’s CRM holds richer donor data not in Raise | Often — partner integration writes updates back to Raise |
| Customer’s accounting system is canonical for revenue | No — accounting reads from Raise; doesn’t write back |
Combining patterns
Real production integrations combine these patterns. The most common combination:| Phase | Pattern |
|---|---|
| Initial customer onboarding | Backfill (pattern 4) |
| Ongoing real-time sync | Webhook-driven push (pattern 1) |
| Verifying correctness | Daily reconciliation (pattern 3) |
| Phase | Pattern |
|---|---|
| Initial customer onboarding | Backfill (pattern 4) |
| Ongoing sync | Polled pull (pattern 2) |
| Verifying correctness | Daily reconciliation against the destination’s state |
| Phase | Pattern |
|---|---|
| Initial customer onboarding | Backfill in both directions |
| Ongoing sync | Webhook-driven push (pattern 1) + two-way coordination (pattern 5) |
| Verifying correctness | Daily reconciliation with attribution-aware comparison |
Destination-specific considerations
The patterns above apply broadly, but specific destination types have particular needs:Accounting destinations (QuickBooks, Xero, NetSuite)
| Consideration | Implication |
|---|---|
| Strong idempotency required | Accounting can’t tolerate double-recorded revenue |
| Refunds need separate records, not mutations | Maps to credit notes / credit memos |
| Hard deletes typically not supported | Use void-with-reason instead |
| Period close requires sync to be “final” by month-end | Reconciliation timing matters |
BI / data warehouse destinations (Snowflake, BigQuery, Redshift)
| Consideration | Implication |
|---|---|
| Flat wide rows preferred over nested structures | Schema mapping flattens nested JSON |
Deletes can be soft (set a deleted_at flag) | Preserves historical data for analysis |
| High latency is acceptable | Daily or hourly batch sync often sufficient |
| Volume tends to be substantial | ID-cursor iteration becomes important |
External CRM destinations (HubSpot, Salesforce, ActiveCampaign)
| Consideration | Implication |
|---|---|
| Contact records keyed by email | Maps to Raise’s email-as-primary-key model |
| Two-way sync sometimes needed | Especially for opt-in state |
| Custom field mapping varies | Document the mapping per-customer |
| API rate limits often stricter than Raise’s | The destination may be the bottleneck |
Marketing automation destinations (Mailchimp, Klaviyo, Iterable)
| Consideration | Implication |
|---|---|
| List/audience membership is the primary state | Map donors to list members |
| Tag-based segmentation is common | Translate Raise attributes to tags |
| Deliverability matters | Don’t sync test-mode donors |
| GDPR / opt-in compliance is strict | Honor donorEmailOptIn and similar flags |
Operational practices
A few practices that apply to any sync architecture:Monitor everything end-to-end
Track the full journey: gift creation in Raise → webhook delivery → queue depth → worker processing → destination write → reconciliation verification. Latency at each step. Failure rate at each step. The end-to-end view catches issues that any single stage’s metrics miss.Per-customer dashboards
For partner integrations with many customers, build per-customer dashboards that show sync health for each:- Last successful sync timestamp
- Records synced today / this week / this month
- Open dead-letter entries
- Webhook subscription status
- Recent reconciliation results
Customer-facing audit trail
Expose the sync history to the customer’s team. They should be able to look up any Raise gift ID and see where it is in the sync pipeline — synced to which destinations, with what destination ID, at what time. Turns “we’ll have to investigate” into “I can see exactly what happened.”Graceful degradation
When a destination is unavailable, the sync should pause for that destination rather than failing the whole pipeline. A workflow that writes to three destinations should continue to write to the two that are healthy when the third is down.JavaScript
Choosing an architecture
For a new integration, walk through these questions:What's the acceptable latency?
Seconds → webhook-driven. Minutes → either webhook or polled. Hours → polled is fine.
How critical is data accuracy?
High → add reconciliation as a backstop. Low → steady-state alone may suffice.
Are there multiple destinations?
Yes → fan-out from one worker to each destination, with independent failure handling.
Where to go next
Sync Raise Gifts to an External System
The end-to-end recipe that combines several of these patterns for a real implementation.
Reconcile with CRM+
The reconciliation workflow that the hybrid pattern depends on.
Error Recovery Patterns
The error-handling patterns that the sync worker uses for resilience.
API Performance Tips
The performance patterns that make sync workloads efficient.