Webhooks

Webhooks let RCMS push assessment events to your system as they happen: an assessment submitted, scored or locked, or one coming due. RCMS registers your endpoint during onboarding and then calls it whenever a subscribed event occurs.

When to use webhooks (and when not)

Use caseUse
Sync assessment scores into your system as they are finalizedWebhook
Trigger a notification when an assessment is coming due or is overdueWebhook
Look up a client's latest score on demandJSON API
Periodic batch sync (nightly refresh, backfills)JSON API
React to a client being enrolled, discharged or readmittedJSON API (delta sync with updated_after). Client events are not yet emitted; see below.

Webhooks are for push notifications of state changes. For on-demand reads, use the API.

Registration

RCMS support registers your webhook during partner onboarding. Send us:

You receive the webhook id (a UUID) and a signing secret, delivered once through a secure channel, never by email. RCMS does not display the secret again; if it is lost, ask support to rotate it. Changes to the URL or the event list also go through support.

No self-serve registration endpoint yet

The POST /v1/webhooks operation in the API reference is marked Status: roadmap and is not callable; the gateway returns 404 for it. There is no endpoint to send test events, list deliveries or delete a webhook. The delivery contract on this page is what registered endpoints receive today.

Event types

Emitted today

Event typeFires when
assessment.submittedA client or staff member submits an assessment (scoring may still be pending)
assessment.scoredScoring finished; the scores are in the payload
assessment.lockedThe edit window closed; the assessment is immutable
assessment.due_soonDaily sweep: a scheduled assessment is within 7 days of its due date. Fires again if the assessment is rescheduled into the window; deduplicated per session and due date
assessment.overdueDaily sweep: a scheduled assessment is past its due date and not yet submitted

Changed 2026-09-16: assessment.due_soon now fires earlier, and can fire twice

Nothing changes on your endpoint, your subscription or the payload shape. If you treat assessment.due_soon as “once per assessment”, that assumption no longer holds: key your own reminder state on session_id together with due_date.

Accepted at registration, not yet emitted

These names are valid in the event list and can be included in a registration, but RCMS does not deliver them today. No delivery date is committed. When one goes live, its payload shape is added to this page and support tells subscribed partners; nothing changes on your URL.

client.enrolled, client.updated, client.discharged, client.readmitted, client.intake_incomplete, assessment.scheduled, staff.created, staff.updated, staff.deactivated, organization.created, organization.updated

Event payload format

Every delivery is a POST to your URL with the same envelope. The body is compact JSON (no whitespace); the data field varies by event type.

POST /hooks/rcms HTTP/1.1
Host: partner.example.com
Content-Type: application/json
User-Agent: RCMS-Webhook/v1
X-Webhook-Signature: sha256=5257a869e7ecfe04b08e4107d4e9c2d3b1c7e9f0a8e1b2c3d4e5f60718293a4b
X-Webhook-Event-Id: 3f0c9a4e-7d2b-4c8e-9a1f-5b6d7e8f9a0b
X-Webhook-Event-Type: assessment.scored
X-Webhook-Delivery-Attempt: 1

{
  "id": "evt_3f0c9a4e-7d2b-4c8e-9a1f-5b6d7e8f9a0b",
  "type": "assessment.scored",
  "created_at": "2026-09-10T14:33:21.512Z",
  "organization_id": "0f8e7d6c-5b4a-4c3d-8e2f-1a0b9c8d7e6f",
  "data": {
    "object": "assessment_session",
    "session_id": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
    "client_id": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
    "assessment_number": 3,
    "due_date": "2026-09-10",
    "submitted_at": "2026-09-10T14:33:10Z",
    "scored_at": "2026-09-10T14:33:21Z",
    "scores": {
      "total_score": 72.5,
      "total_max_score": 120,
      "total_percentage": 60.4,
      "overall_recovery_capital": 18.0,
      "negative_capital": 42.0,
      "positive_capital": 60.4
    },
    "barriers_count": 3,
    "strengths_count": 9,
    "next_due_date": "2026-12-09"
  }
}

The example body is shown pretty-printed for readability; the bytes on the wire are compact.

Headers

HeaderValue
X-Webhook-Signaturesha256=<hex>: HMAC-SHA256 of the exact request body, keyed with your signing secret
X-Webhook-Event-IdUUID of the event. Retries carry the same id, so use it to deduplicate. This is the event id the API Terms of Service refer to (section 6.1); the envelope id carries the same UUID with an evt_ prefix.
X-Webhook-Event-TypeThe event type, for example assessment.scored
X-Webhook-Delivery-Attempt1 on the first attempt, then 2 to 7 on retries
User-AgentRCMS-Webhook/v1

Envelope fields

FieldDescription
idevt_ followed by the same UUID as X-Webhook-Event-Id. Use either for idempotency.
typeEvent type (see the list above)
created_atISO-8601 UTC timestamp of when the event was raised
organization_idThe organization's public UUID; the same value you send as X-Organization-Id on API calls
dataEvent-specific payload. Every id inside data is a plain UUID.

The data object by event type

assessment.submitted and assessment.locked:

{
  "object": "assessment_session",
  "session_id": "<uuid>",
  "client_id": "<uuid, the client_uuid the API returns>",
  "assessment_number": 3,
  "due_date": "2026-09-10",
  "submitted_at": "2026-09-10T14:33:10Z",
  "locked_at": null
}

assessment.scored: the fields above (without locked_at) plusscored_at, scores, barriers_count and strengths_count, as in the full example. The score to display, if you show one, is scores.overall_recovery_capital: a points value from -100 to +100. total_percentage is an internal figure, not a resident-facing number. next_due_date is the due date of the assessment RCMS scheduled next for this client, or null when none was scheduled, so you do not need a follow-up status call to learn it. Since API 1.4.0 assessment.scored is emitted once per distinct result: a rescoring run that produces the same scores and the same submitted_at is not sent again; a changed score or a resubmission arrives as a new event with a new id.

assessment.due_soon and assessment.overdue:

{
  "object": "assessment_session",
  "session_id": "<uuid>",
  "client_id": "<uuid>",
  "assessment_number": 3,
  "due_date": "2026-09-17",
  "days_until_due": 7
}

assessment.overdue has the same fields with days_overdue (a positive integer) in place of days_until_due.

For assessment.due_soon, days_until_due is between 1 and 7 from 2026-09-16. If the assessment is rescheduled, a second event carries the new due_date and a days_until_due counted from it, so the pair (session_id, due_date) identifies a reminder.

Verify the signature

Every delivery carries an X-Webhook-Signature header in this format:

X-Webhook-Signature: sha256=5257a869e7ecfe04b08e4107d4e9c2d3b1c7e9f0a8e1b2c3d4e5f60718293a4b

The value after sha256= is the lowercase hex HMAC-SHA256 digest of the raw request body, keyed with your signing secret. There is no timestamp in the signature and no replay window.

Verification steps

  1. Read the request body as raw bytes, before any JSON parsing.
  2. Strip the sha256= prefix from the header value.
  3. Compute HMAC-SHA256 of the raw bytes with your signing secret and encode it as lowercase hex.
  4. Compare the two with a constant-time comparison; reject the request if they differ.
  5. Only then parse the JSON body.

Hash the bytes exactly as received. Re-serializing a parsed object changes key order or whitespace and the digest will not match.

Node.js example

import crypto from "node:crypto";

// rawBody: the request body exactly as received (Buffer or string), before JSON.parse
export function verifyRcmsWebhook(rawBody, signatureHeader, secret) {
  // header looks like: sha256=5257a869e7ec...
  if (!signatureHeader || !signatureHeader.startsWith("sha256=")) return false;
  const received = signatureHeader.slice("sha256=".length);
  const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  if (received.length !== expected.length) return false;
  return crypto.timingSafeEqual(Buffer.from(received, "hex"), Buffer.from(expected, "hex"));
}

Python example

import hmac
import hashlib

# raw_body: the request body bytes exactly as received, before json.loads
def verify_rcms_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
    # header looks like: sha256=5257a869e7ec...
    header = signature_header or ""
    received = header[7:] if header.startswith("sha256=") else ""
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(received, expected)

Notification routing — who sends what to users

RCMS sends a few transactional messages natively (assessment reminders, follow-up nudges). When a partner integration is involved, clients often end up signed up through the partner's platform — at that point RCMS emails from an unfamiliar domain create confusion.

To avoid that, notification routing follows the auth identity owner rule. No org-level configuration; no duplicate emails:

Who created the auth identityHow reminder notifications are delivered
Your API key (partner-provisioned user)RCMS fires assessment.due_soon / assessment.overdue webhooks to your endpoint. RCMS does not send its own email.
RCMS (org admin invited the user directly)RCMS sends its native reminder email from noreply@measurerecovery.com. No webhook is fired for reminders.

If you subscribe to assessment.due_soon and assessment.overdue, you'll receive events for every client your API key created — RCMS assumes you're handling the outreach from your domain with your branding. Clients you didn't create stay on RCMS-native email delivery automatically.

Applies to reminder events only

Transactional events you want for analytics or sync (assessment.submitted, assessment.scored, assessment.locked) fire for every subscribed webhook regardless of who created the auth identity. This routing rule only affects user-facing notifications where duplicate branding would cause confusion.

Delivery semantics

BehaviorDetail
At-least-once deliveryYou may receive the same event more than once. Deduplicate on X-Webhook-Event-Id (or the envelope id).
Success criteriaReturn any 2xx within 30 seconds, the deadline in the API Terms of Service (section 6.4). See the note below on the current timeout.
Retry triggerNon-2xx response, timeout or connection error
Retry schedule1 minute, 5 minutes, 15 minutes, 1 hour, 4 hours, 12 hours, 24 hours after each failure: 7 attempts in total, about 18.5 hours end to end
After the last attemptThe delivery is marked failed and is not retried automatically. Contact support if you need it re-sent.
OrderingNot guaranteed. Use created_at to order.

Note on the current timeout: the dispatcher today waits 10 seconds for your response before treating the delivery as failed, and that wait is being aligned to the 30-second deadline in the Terms. Until then a response slower than 10 seconds may be retried. Retries carry the same event id, so a deduplicating receiver is unaffected; if you see repeated deliveries to a slow endpoint, tell support.

Testing and support

There is no self-serve test endpoint yet. To exercise your receiver before real traffic:

Best practices

Next steps

Getting Started

Request an API key and make your first call.

API Reference

Full request/response documentation for every endpoint.