Skip to main content
The single most common Volunteer integration pattern: mirror VOMO Users into an external system — a CRM, a data warehouse, a marketing platform, an HR roster. This recipe walks through the end-to-end implementation: initial backfill of historical data, ongoing polling for incremental changes, periodic reconciliation to catch gaps, and the operational practices that keep the integration production-grade reliable. The recipe combines workflows covered separately in the docs (listing users, upserting users, polling, reconciliation) into a single working integration architecture.

What you’ll build

A partner integration that:
  • Receives a Bearer token from the customer during onboarding
  • Performs an initial full backfill of all VOMO Users into the external system
  • Polls VOMO for ongoing changes and propagates them to the external system
  • Reconciles daily to catch gaps and deletions
  • Surfaces sync health visibility to the customer
By the end you’ll have a complete reference architecture and the code to implement it.

When this recipe fits


Architecture

Six components, each independent — failures in one don’t crash others:

Step 1: customer onboarding

The onboarding flow handles three things: capture the Bearer token, configure sync targets, trigger the initial backfill.
JavaScript

Why initialize the checkpoint to epoch

Setting the checkpoint to new Date(0) means the next polling cycle would query “everything updated since 1970” — which is everything. But the backfill runs first; once the backfill completes, the checkpoint advances to the latest-seen timestamp. From that point forward, polling operates incrementally. This approach unifies backfill and steady-state under one mechanism — no separate code paths.

Step 2: initial backfill

The backfill reads all VOMO Users and pushes them to the external system. For a 10,000-user customer, this is ~667 paginated requests at 15 records per page.
JavaScript

What processUserForBackfill does

JavaScript
The three writes — to external system, to mapping table, to audit log — happen together. If any fails, the whole record fails (caught by the backfill’s try/catch and routed to DLQ).

Backfill duration estimates

Communicate this during onboarding so customers understand the initial sync isn’t instant. Show progress in the UI if available.

Step 3: steady-state polling

After backfill, the polling worker takes over. It runs on a schedule and processes only what’s changed.
JavaScript

Why use the mapping table to detect new vs. update

The 200/201 detection works at the VOMO upsert level. Here we’re going the other direction (VOMO → external) — the mapping table is the source of truth for “have we seen this user before in this customer’s integration”: This is more robust than time-based heuristics (created_at == updated_at). The mapping table is the integration’s persisted view of reality.

Step 4: daily reconciliation

Daily reconciliation catches gaps and re-processes them. See Reconciliation Patterns for the full pattern.
JavaScript

Weekly deletion detection

JavaScript

Step 5: customer-facing visibility

Customers ask “is our integration working?” — build a per-customer dashboard that answers in seconds:
JavaScript
Surface this through a UI in your partner product. Customers can self-diagnose “did the sync run today?” without contacting support.

Bidirectional sync

Some integrations need to also write back to VOMO — e.g., the external CRM is source-of-truth for user phone numbers.
JavaScript
The critical pattern: email mismatch in the external→VOMO direction is dangerous (creates VOMO duplicates). Detect, alert, don’t auto-sync — let a human resolve it. For most integrations, stay one-way (VOMO → external only) unless bidirectional is a genuine business need.

Operational concerns

Token expiration

JavaScript
See Authentication: Handling auth failures.

Customer offboarding

JavaScript
Customers sometimes re-enable shortly after disabling. The 30-day retention window allows re-onboarding without re-running the full backfill.

Per-customer rate-limit budgets

JavaScript
Fair-share rate limiting prevents one customer’s heavy use from starving others.

Things to watch for

A few subtleties that surface in production:

The participation gap

This recipe syncs users but not their participations (since participations don’t advance updated_at). For integrations that need participation data, see Detecting User Changes: The participation caveat and add a separate participation-polling layer.

Large customers stress the integration

A 100,000-user customer can produce 5,000 changes per day during active periods. The polling worker handles this through pagination, but downstream processing (writes to external system) is often the bottleneck. Monitor per-customer processing rate and scale workers as needed.

Backfill scheduling

For very large customers, the initial backfill can take hours. Schedule it for low-traffic hours and communicate timing to the customer. Prevent multiple workers from picking up the same backfill (use a lock or unique-claim pattern).

Mapping table growth

Per-customer mapping tables grow unbounded over time. For multi-tenant integrations:
  • Periodic archival of long-inactive customers’ mapping tables
  • Partitioning by customer for query performance
  • Indexes on (customer_id, vomo_user_id) and (customer_id, email)

Dead-letter queue management

The DLQ catches failures but isn’t self-cleaning. Build:
  • Automatic retry of recoverable failures (timeouts, transient 5xx)
  • Manual review queue for unrecoverable failures (data validation, permanent destination errors)
  • Aging policies (alert on DLQ entries older than N days)

What you’ve built

After this recipe:
  • ✅ Onboarding flow that captures and validates the Bearer token
  • ✅ Initial backfill that mirrors all VOMO users to the external system
  • ✅ Polling worker that catches incremental changes every 15-30 minutes
  • ✅ Daily reconciliation catching gaps
  • ✅ Weekly deletion detection
  • ✅ Per-customer dashboards showing sync health
  • ✅ Operational handling for token failures, offboarding, and rate budgets
This is the foundational integration shape for most VOMO partner work. Other recipes (Groups, hours reporting) build on this base.

Where to go next

Build a Group from a Query

Build Groups dynamically from query results.

Report on Volunteer Hours

The reporting recipe using participation data.

Reconciliation Patterns

The reconciliation patterns this recipe references.

Sync Architecture Patterns

The broader architectural patterns for sync designs.
Last modified on May 22, 2026