{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://www.firstmilelabs.com/schemas/webhooks/case.onboarding_progress.changed.v1.json",
  "title": "FirstMileLabs Outbound Webhook — case.onboarding_progress.changed (v1)",
  "description": "A de-duplicated, CRM-safe snapshot of a case's onboarding journey. Emitted only when the overall stage OR a contributor's coarse verification status actually changes. Carries a plain-language journey stage plus a per-person roster (director / UBO / signatory) so a CRM can render live progress without polling. Never carries EDD / risk / screening-adjudication / analyst-note signals, nor any contributor PII beyond the display name.",
  "type": "object",
  "additionalProperties": true,
  "required": [
    "event",
    "eventId",
    "eventVersion",
    "timestamp",
    "orgId",
    "caseId",
    "stage",
    "stageLabel",
    "roster",
    "summary"
  ],
  "properties": {
    "event": { "type": "string", "const": "case.onboarding_progress.changed" },
    "eventId": { "type": "string", "format": "uuid" },
    "eventVersion": { "type": "integer", "const": 1 },
    "timestamp": { "type": "string", "format": "date-time" },
    "orgId": { "type": "string" },
    "crmRecordId": { "type": ["string", "null"], "description": "The CRM record id supplied when the case was created, echoed back so the receiver can correlate." },
    "crmSource": { "type": ["string", "null"], "description": "The CRM the case originated from (e.g. \"salesforce\"), so a multi-CRM receiver can route." },
    "caseId": { "type": "string" },
    "companyName": { "type": ["string", "null"], "description": "Subject company display name. Null for individual (KYC) cases." },
    "stage": {
      "type": "string",
      "enum": ["created", "invite_sent", "collecting_documents", "application_submitted", "in_screening", "awaiting_id_verification", "decision_made"],
      "description": "Machine key for the furthest-along onboarding stage reached."
    },
    "stageLabel": { "type": "string", "description": "Plain-language label for `stage`." },
    "summary": {
      "type": "object",
      "additionalProperties": true,
      "required": ["verified", "total"],
      "properties": {
        "verified": { "type": "integer", "minimum": 0 },
        "total": { "type": "integer", "minimum": 0 }
      }
    },
    "roster": {
      "type": "array",
      "description": "Per-person verification roster. Coarse, customer-safe statuses only.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["name", "role", "status", "statusLabel"],
        "properties": {
          "name": { "type": "string", "description": "Contributor display name (the only PII surfaced — a CRM must show which director/UBO)." },
          "role": { "type": "string", "description": "e.g. \"Director\", \"Beneficial Owner\", \"Authorised Signatory\"." },
          "status": { "type": "string", "enum": ["link_sent", "in_progress", "action_needed", "verified"] },
          "statusLabel": { "type": "string", "description": "Plain-language label for `status`." }
        }
      }
    }
  }
}
