How to read this page
A firm subscription receives 13 business events plus one syntheticwebhook.test event. There is no per-subscription event filter: every active subscription receives every event your firm is entitled to, so branch on the event field (or the X-CaseXchange-Event header) and route from there.
Every business event arrives inside the same envelope. The envelope fields (eventId, occurredAt, perspective, piiMasked, referralId, baseCaseId, actor, and so on) are documented on Anatomy of a delivery. This page covers what changes from event to event: the data object and who receives it.
The data schema names below (CaseSnapshot, StatusChangeData, NoteData, …) are the component names the API reference uses for the same shapes, and the case snapshot uses the same field vocabulary as GET /cases, so a push and a poll line up field for field there.
Ignore event names you do not recognise. An unknown
event value is not an error and must not fail your handler.Catalog
Notes on the table:
- Can be masked means the event can arrive with
piiMasked: true. Only the status family can. Before a receiving firm acknowledges a referral it receives the status family (referral.received,referral.accepted,referral.rejected,case.status_changed) and, in addition,note.createdandnote.updatedfor notes its own firm owns: a note is that firm’s own content, and it can write notes while the referral is still insent. What is withheld from an unacknowledged receiving firm iscase.updated,document.uploaded,document.deleted, and the counterparty’s notes. The full rules are on Client data and masking. changedFieldsis present only oncase.updatedandnote.updated. Oncase.updatedit is already intersected with what your firm may see; a recipient whose intersection is empty is not delivered the event at all. The sentinel valuecustomFieldsmeans your custom field values changed and you should re-GETthe case; a masked recipient never receives it.referral.acceptedis normally the first unmasked delivery a receiving firm gets. If an administrator or an integration made the transition without the receiving firm acknowledging, it stays masked. An administrator override such assenttosignedarrives ascase.status_changed, notreferral.accepted, and does not unmask.document.uploadedanddocument.deletedare stricter than case content: document access ends atclosed, wherecase.updatedandnote.*keep flowing. Fee-agreement documents produce no event.case.ratedis the only way to observe a tier. The case snapshot carries notierfield, and a receiving firm never receives this event.
Status vocabulary
Everystatus and previousStatus value, in the envelope’s data and inside a case snapshot, uses the same normalized lower-case vocabulary as GET /cases:
draft, sent, received, under_evaluation, investigating, signed, in_litigation, closing, won, lost, closed, rejected, withdrawn, pending_spin_off
Two normalizations apply:
- The legacy
receivedstatus is published asunder_evaluation. - Private terminated and exhausted categorizations are published as
rejected.
StatusChangeData
Delivered as data on referral.sent, referral.received, referral.accepted, referral.rejected and case.status_changed. One shape for all five, so you can point them at a single handler.
Example: referral.accepted, sending firm’s copy
The receiving firm acknowledged the referral. This delivery went to the sending firm (perspective: "sent"), so the snapshot is complete.
MaskedStatusChangeData
The status-family data delivered to a receiving firm that has not acknowledged the referral. Same keys as StatusChangeData, with two differences:
caseis aMaskedCaseSnapshot(14 keys; everything else absent, not null).messageis forced tonull.
piiMasked: true. Check that flag before reading any client field.
Example: masked referral.received
CaseSnapshot
The referral as it stands after the change committed, using the same vocabulary as GET /cases. It appears in two places:
- Directly as
dataoncase.createdandcase.updated. - Nested as
data.caseon the status family (StatusChangeData).
tier, source, or custom field values. Custom field changes surface only as the customFields sentinel in changedFields; re-GET the case to read them.
Identity
Status and outcome
Descriptors
Client
These fields are absent from the masked snapshot.Warm transfer
Money
Dates
Example: case.updated
data is the full snapshot; changedFields sits on the envelope. Here the sending firm updated the projected settlement and a custom field, and the delivery went to the sending firm itself. To ignore your own writes, compare the delivered state with what you last wrote; actor.firmId tells you which firm acted, not which channel.
PATCH /sent-cases/{id} reports the columns it wrote in changedFields, not a value diff. Re-sending an identical value can still produce a case.updated naming that column.MaskedCaseSnapshot
The pre-acknowledgment snapshot: ids, the shared status, and non-personal descriptors only. It contains exactly these 14 keys and nothing else. Omitted fields are absent from the object, not present with a null value, so code that reads data.case.clientLastName will find undefined, not null.
The masked
referral.received example earlier on this page shows a full masked delivery; it is the canonical one, and other pages point here for it.
NoteData
Delivered as data on note.created and note.updated. Notes carry no case snapshot: ids and the note body only. Notes are per-firm; each side of a referral keeps its own.
Example: note.updated
changedFields is always ["body"]: the body is the only editable field.
authorId is present. The same fact delivered to the sending firm carries authorId: null.
There is no
note.deleted event. Notes written automatically alongside a status transition, a case creation or a CSV import are not published as note.created; a note supplied to PATCH /sent-cases/{id} is a real note and is. See Known gaps.DocumentData
Delivered as data on document.uploaded. It goes to the case’s sending firm (the case owner), which is not necessarily whoever uploaded the file, and to each counterparty whose hop still grants document access. Metadata only: there is no download URL in the event, because the signed download URL expires. To fetch the bytes, call GET /cases/{referralId}/documents/{id}/download using the envelope’s referralId — always present, unlike data.referralId — together with data.id, then follow the pre-signed URL it returns. See Documents examples.
Example: document.uploaded
uploadedById is null.
DocumentTombstone
Delivered as data on document.deleted. Ids only.
CaseTombstone
Delivered as data on case.deleted, which announces a deleted draft. A draft has no receiving firm, so this event goes to the sending firm alone and referentFirm on the envelope is null. The ids were captured before the row was removed; the envelope fields describe the state before the delete.
CaseRatingData
Delivered as data on case.rated. A tier is the sending firm’s private appraisal of its own case, so this event goes to the owning firm only, whether or not the receiving firm has acknowledged. The case snapshot carries no tier, which makes this event the only way to observe one. ratingNotes is never published.
webhook.test
Queued by POST /webhooks/{id}/test. It is not an envelope: none of the fields above are present. This is the canonical webhook.test body; other pages link here rather than repeating it. The body is exactly:
eventId travels only in the X-CaseXchange-Event-Id header, and it is signed like any other delivery, so it is a good first check of your verifier. See Testing webhooks for the full test flow and echo mode.
Related pages
Anatomy of a delivery
Envelope fields, headers, ordering and deduplication.
Client data and masking
The full rules for who receives what, and when a receiving firm unmasks.
Verifying signatures
Check
X-CaseXchange-Signature before you trust any body on this page.Known gaps
Writes that produce no event, and how to reconcile around them.

