Skip to main content
Users are the most commonly-polled resource in Volunteer integrations. Almost every partner integration that syncs data to an external system needs to detect “which users were created or modified since last time” — for sync to a CRM, for triggering follow-up workflows, for keeping reporting databases current. This page covers the User-specific polling pattern in production-grade detail. It builds on the foundation in Polling Overview with User-specific filters, edge cases, and the central caveat: participation changes don’t advance updated_at, so polling Users alone misses a significant class of “user activity” events. If you haven’t yet, skim the Polling Overview for the core polling pattern.

When to use this workflow


The baseline pattern

The simplest user-change polling worker:
JavaScript
This is the canonical shape. Most of this page adds layers on top.

Distinguishing new from modified users

updated_after returns both newly-created and modified users. Sometimes you want to treat them differently — fire a welcome email on creation, fire an update event on modification. The distinction: for newly-created users, created_at equals updated_at (or is very close). For updates, updated_at > created_at.
JavaScript

Why this works (and why it’s approximate)

The heuristic assumes that creating a user produces created_at and updated_at simultaneously (with sub-second resolution). For most cases this is correct. Edge cases:
  • A user created and then immediately modified within the same poll window — both created_at and updated_at are recent, but they’re not equal. The heuristic might miss the “created” event and treat it as an update.
  • Bulk imports where many users are created in batch — they may all share the same created_at but have slightly different updated_at.
For more robust detection, track previously-seen User IDs:
JavaScript
The cost: a per-user database lookup. The benefit: definitive new-vs-update detection. For most production integrations, the database approach is more reliable.

The participation caveat

The most important thing to understand about User polling: new participations do not advance the User’s updated_at. This is the central polling caveat. Polling /users?updated_after=X will not detect new volunteer activity.

Why this matters

A partner integration that promises “we’ll detect when your volunteers serve” cannot deliver on that promise via User polling alone. The integration architecture needs to account for this gap.

What to do instead

For detecting new participations, three options:

Option A: Poll GET /users/{id} for each user

After detecting a user change via the list endpoint, fetch detail to see their full participation list:
JavaScript
Cost: This only catches new participations for users whose User record was modified for some other reason — which is unlikely to coincide with new participations. So this option mostly doesn’t work for the stated purpose.

Option B: Periodic full participation scan

On a slower cadence (daily, weekly), iterate all users and check their participations:
JavaScript
Cost: N+1 — one User list + one User detail per User. For an account with 5,000 users, this is 5,001 requests every scan. Throttle aggressively and schedule for off-peak.

Option C: Poll Project Dates instead

If your integration mainly needs participation data for specific Projects, poll the Project Dates and pull participants from there:
JavaScript
Cost: Bounded by recent Project Dates rather than all users. For active customers with predictable Project schedules, this scales better than Option B. The right choice depends on your integration’s specific needs. See Reconciliation Patterns for the structural approach.

Combining updated_after with other filters

The polling pattern can be narrowed with additional filters when not all users need processing:

Poll only verified users

JavaScript
Note: the API doesn’t expose user_status as a server-side filter, so this is client-side filtering — the API returns all updated users, and your code filters in memory. The poll still consumes rate budget for all changed users, but downstream processing is narrowed.

Poll only users with email matches

JavaScript
This narrows server-side — useful for partner integrations scoped to a specific email domain (e.g., a corporate volunteer program’s employees).

Detecting deletions

Polling has a fundamental gap: deleted records don’t appear in queries. If a user is deleted in VOMO, polling /users?updated_after=X won’t show them — they’re just gone. For partner integrations that need to mirror deletions to external systems, the only path is reconciliation:
JavaScript
This is a slow reconciliation pattern — run daily or weekly because it’s a full-dataset operation. See Reconciliation Patterns.

A subtle gotcha: “deletion” vs. “not accessible”

A user may “disappear” from /users results for reasons other than deletion:
  • The user was moved to a different organization within the family
  • The token’s permissions changed
  • The user was banned or soft-deleted but still exists
Don’t assume “not in list” means “deleted.” Treat it as “not currently visible” and surface for review rather than immediately propagating as a deletion to the external system.

Reading user detail during the poll

The list endpoint returns abbreviated UserResource objects. If your integration needs the full UserDetailResource (with participations and profile_field_values), fetch detail per user:
JavaScript
Cost: One additional request per changed user. For a poll cycle that detects 50 changes, that’s 51 requests instead of (roughly) 4 pages of 15.

When to fetch detail vs. when to skip

The list-shape vs. detail-shape decision matters for poll cost. Most integrations can use list shape for the change detection itself, fetching detail only for the subset of users where the additional fields matter.

Throttling and resource limits

Per-poll-cycle request cost on /users:
A poll cycle that detects 150 changes pages 10 times. Multiplied across customers and resources, this is the bulk of the integration’s API traffic. A reference throttled polling worker:
JavaScript
A 3-req/sec rate is conservative; tune based on your overall integration’s budget across customers. See Rate Limits.

A reference user-change poller

A complete reference implementation incorporating the patterns above:
JavaScript
This handles the production-grade concerns: throttling, new-vs-update detection via persistent state, per-record failure isolation, dead-letter queueing, and correct checkpoint advancement.

Monitoring

Track these metrics per customer: A simple “is polling healthy?” alert checks that now - latest_checkpoint < 2 * poll_interval. Beyond that, no recent activity means polling has stalled.

Production checklist

For a User-change polling worker:
  • Checkpoint persisted per-customer in durable storage
  • Checkpoint advanced to the latest updated_at actually seen (not wall-clock time)
  • Per-user failures isolated; don’t fail whole batch on one bad record
  • Failed records go to a dead-letter queue
  • Rate-limit-aware throttling in place
  • Distinct paths for new-user vs. updated-user processing (where business logic differs)
  • Deletion detection runs as a separate (slower) reconciliation
  • Participation-related workflows use a different polling strategy (Project Dates, full scans, etc.)
  • Per-customer monitoring dashboards exist
  • Alerts on stalled checkpoints and growing DLQ

Where to go next

Detecting Project Changes

The Project-specific polling pattern with schedule-change considerations.

Reconciliation Patterns

The slow-scan patterns for deletion detection and gap recovery.

Change Detection Best Practices

The cross-cutting patterns — checkpointing, idempotency, drift.

Users

The reference page for User fields and the upsert behavior.
Last modified on May 22, 2026