Sync Patterns
Partners and RCMS keep data in sync using two mechanisms that complement each other: webhooks push events when something changes; the API serves on-demand reads and backfills. This page covers the recommended pattern, expected latency, idempotency, and how to recover from outages.
The recommended pattern
- Subscribe to webhooksfor the events you care about. This is your primary change feed — you get pushed updates in seconds.
- Fetch full resources via APIafter receiving an event. The webhook payload tells you “client 260 was discharged;” a follow-up
GET /v1/clients/260gives you the complete, post-change state. - Periodically reconcile with a delta sync— once a day is usually enough. List endpoints accept
?updated_after=so you can pull only what's changed since your last check.
Webhooks are push. API reads are pull. Use both.
Expected latency
| Event | Webhook fires within |
|---|---|
| Client enrolled / discharged / updated | 30 seconds |
| Assessment submitted | 30 seconds |
| Assessment scored | 1–3 minutes (scoring runs asynchronously) |
| Assessment due soon / overdue | Within 1 hour of the state change |
| Staff created / updated / deactivated | 30 seconds |
API reads are near real-time; data written to RCMS is visible on the next GET to that resource within a few seconds.
Delta sync with updated_after
Every list endpoint accepts an updated_after query parameter (ISO-8601 timestamp). Pass the timestamp of your last successful sync to get only the records modified since.
# Nightly reconciliation job (pseudo-code)
last_sync_at = read_state("rcms_last_sync_at")
url = f"https://api.measurerecovery.com/v1/clients?updated_after={last_sync_at}"
while url:
response = http_get(url, headers={"Authorization": f"Bearer {API_KEY}"})
for client in response["data"]:
upsert_into_partner_db(client)
url = response["meta"]["next_url"] # null when no more pages
write_state("rcms_last_sync_at", now_iso())updated_afteris inclusive — running the same timestamp twice will re-fetch boundary records. Upsert using the resource's id so repeats are harmless.
Idempotency keys
Every write endpoint (POST /clients, POST /staff, etc.) accepts an Idempotency-Key header. If RCMS sees the same key within 24 hours, it returns the original response instead of creating a duplicate.
curl -X POST https://api.measurerecovery.com/v1/clients \
-H "Authorization: Bearer rcms_live_xxxxxxxx" \
-H "Idempotency-Key: partner-enrollment-2026-04-19-abc123" \
-H "Content-Type: application/json" \
-d '{ "first_name": "...", ... }'Recommendation: use your own internal ID for the record as the idempotency key (e.g., onestep-client-12345). If a network blip causes you to retry, RCMS won't create a second client.
Outage recovery
If your webhook endpoint is down
- RCMS retries failed deliveries with exponential backoff across a 24-hour window (30s, 2m, 10m, 30m, 1h, 2h, 6h, 12h)
- After 50 consecutive failures the webhook auto-disables; you'll need to delete and re-create it
- Past 24 hours, events are no longer retried. Recovery path: run a
?updated_after=<outage_start_time>list query against each affected resource to catch up
If RCMS is down (rare)
- Your writes will get 5xx. Use exponential backoff and retry with the same
Idempotency-Key— no duplicates even if some succeeded server-side before the timeout - Webhook events queue up inside RCMS and fire when service returns, in original order
- Status page:
status.measurerecovery.com(coming soon)
Full rebuild / onboarding a new partner app
If you're starting from scratch (e.g., new database, or first sync after signing on): run the list endpoints without updated_after to pull everything, page by page. Then record the current timestamp and switch to the webhook + delta pattern.
Per-resource sync recommendations
| Resource | Primary | Backup | Reconcile |
|---|---|---|---|
| Organizations | Webhook: organization.* | GET /organizations?updated_after= | Weekly |
| Staff | Webhook: staff.* | GET /staff?updated_after= | Daily |
| Clients | Webhook: client.* | GET /clients?updated_after= | Daily |
| Assessment scores | Webhook: assessment.scored | GET /clients/:id/assessment-status | Daily |
| Client notifications (reminders) | Webhook only (assessment.due_soon / overdue) | — | n/a (partner owns delivery) |
“Primary” is how you should normally get updates. “Backup” is the fallback when webhooks are unavailable. “Reconcile” is the recommended delta sync cadence to catch anything webhooks missed.
Common mistakes to avoid
- Polling instead of subscribing.Don't hit the API every 30 seconds looking for changes — use webhooks. Polling burns rate limits and adds latency.
- Relying on webhook payload data for writes.The payload is a pointer (“client X changed”). Always
GETthe full resource before making decisions — the payload might be stale if multiple changes happened in quick succession. - Skipping idempotency keys on retries. Network glitches happen. A retry without an idempotency key creates a duplicate client record.
- Ignoring webhook retries. If your endpoint returns 5xx and RCMS retries, make sure your handler is idempotent on the event
id— otherwise you'll double-process. - Storing webhook payloads as source of truth. Treat webhooks as notifications, not as the record. The API is the canonical source.