Overview
There are three ways to exercise a subscription before and after you go live, from cheapest to most realistic:
All three need an existing subscription. If you do not have one yet, start with the Webhooks quickstart. Test events and echo mode are mutations and need a
standard or full key; reading deliveries and captures works with read_only.
Send a test event
POST /webhooks/{id}/test queues a synthetic webhook.test delivery to the subscription. The subscription must be active — an inactive one returns 409 with error.code subscription_inactive; reactivate it with PATCH /webhooks/{id} and { "isActive": true } first.
200 response is the delivery record, not the result of the attempt. It is the record as it stood before the first attempt ran — the attempt is dispatched immediately, in the background, so a poll a moment later may already show DELIVERED.
What your endpoint receives
The test delivery carries the same four headers as every other delivery and is signed the same way, so it is a complete rehearsal of Verifying signatures.
The body is not the standard event envelope. It is exactly:
There is no
eventId, schemaVersion, perspective, or referralId in the test body. The eventId travels only in the X-CaseXchange-Event-Id header. Branch on event === "webhook.test" before your envelope parser runs, or the missing fields will fail validation.Poll the delivery log
Follow the outcome withGET /webhooks/{id}/deliveries. The test delivery is retried on the same schedule as a real one, so a failing endpoint shows FAILED with a nextRetryAt rather than EXHAUSTED right away.
Echo mode
Echo mode diverts deliveries for one subscription to a capture endpoint hosted by CaseXchange, then lets you read back what was sent — headers, the exact signed bytes, and whether the signature verifies against your current secret. Nothing reaches your URL while it is on, and you do not need an endpoint of your own.1
Enable echo mode
The subscription must be active; an inactive one returns The This is the only place
400 with error.code echo_requires_active.200 response contains exactly two fields and nothing else:echoUrl and echoToken are ever returned — GET /webhooks/{id} does not expose them. The call is idempotent: calling it again on a subscription that is already in echo mode returns the same values, so use that if you need to see them again.2
Understand what changed
While echo mode is on:
Now trigger something to capture:
POST /webhooks/{id}/test is the quickest, or wait for a real event.3
Read the captures
rawBody you received rather than rebuilding it.Run your own verifier over rawBody and headers["x-casexchange-signature"] with your secret and compare your result with signatureValid. If they disagree, check first that you are hashing the raw bytes — verifying a re-serialised body is the most common cause of a mismatch.Reading captures on a subscription whose echo mode is off returns 400 with error.code echo_not_active.4
Know the limits
- CaseXchange keeps the 50 most recent captures per subscription; older ones are evicted as new ones arrive.
- Captures expire after 1 hour. Read them promptly.
- A delivery body larger than 16 KB is rejected by the capture endpoint with
413; it is not captured, and the delivery is recorded as a failed attempt and retried. - Pagination on the captures list uses
page(default 1) andlimit(default 20, maximum 100), like the other webhook lists.
5
Disable echo mode
200 response has "data": null. Disabling restores delivery to your URL, sets isEchoActive back to false, and deletes every capture for the subscription. Copy anything you still need out of the captures before you disable.For 24 hours after a rotation,
signatureValid on a capture also accepts the previous secret — a read-back convenience for captures, not a grace window for your endpoint, which must move to the new secret immediately, as Verifying signatures explains.Echo mode error reference
Local development
A subscription URL must be public HTTPS on port 443. CaseXchange resolves the hostname when you create or update the subscription and again at every delivery, and rejects any address that is not publicly routable —localhost, 127.0.0.1, and private LAN ranges all fail with 400 invalid_webhook_url. You cannot point a subscription at a development machine directly.
Pick one of these instead:
HTTPS tunnel
Expose your local server through a tunnel such as ngrok or Cloudflare Tunnel and subscribe to the public
https:// hostname it gives you. Deliveries hit your real handler code.Hosted request capture
Subscribe to a hosted request-capture service and inspect deliveries there. Useful for a first look at real payloads before you have written any handler.
Echo mode
Needs no endpoint at all. Best for checking exact bytes and signature handling without any network setup.
Delivery log
Whichever option you choose,
GET /webhooks/{id}/deliveries shows the status code and error of every attempt.Real business events
Test events prove connectivity; they do not exercise your envelope parsing, masking logic, deduplication, or ordering. For that, drive a real referral through a status change and watch the deliveries arrive. Use a test case for this. A test case showsisTestCase: true on GET /sent-cases/{id}, and every delivery for it carries isTestCase: true in the envelope. That lets you run the end-to-end check on a production subscription and have your handler ignore or route those deliveries separately. You can create a test case from the Test Case section of your account settings, and help@casexchange.com can help if you do not have access to it.
End-to-end checklist
- Subscribe and confirm reachability. Create the subscription, send a test event, and confirm
DELIVEREDin the delivery log. - Receive
referral.sent. When the test case is sent, your subscription receivesreferral.sentwithperspective: "sent"andisTestCase: true. (Referring an ordinary, non-test case withPOST /sent-cases/{id}/referbehaves the same way but carriesisTestCase: false— the flag comes from the case itself, not from who you refer it to.) On a referral between two live firms, the receiving firm getsreferral.receivedfor the same fact with the sameX-CaseXchange-Event-Id; a test case referred to another real firm delivers to that firm exactly as a live one does, so both sides can rehearse against it. - Acknowledge on the receiving side. Until the receiving firm acknowledges the referral itself, its deliveries carry
piiMasked: trueand a reduceddata.case. Acknowledge it as that firm — in the app, from the acknowledgment email, or through its own connected system — and confirm the next delivery to that firm arrives withpiiMasked: falseand the full snapshot. Only the receiving firm’s own action unmasks; see Client data and masking. - Receive
case.status_changed. Make a further transition on the referral, such as closing or withdrawing it, and confirmcase.status_changedarrives withdata.previousStatusanddata.statusdescribing the move. If the receiving firm accepts it first, you seereferral.acceptedinstead. - Verify the signature on every delivery. Compute the HMAC over the raw body before parsing, check the
twindow, and compare in constant time. Return a non-2xxstatus when verification fails. - Deduplicate. Send a test event to an endpoint that returns
500once and then200; confirm the retry carries the sameX-CaseXchange-Deliveryand that your handler applies it once. - Order by
occurredAt. Deliveries can arrive out of order. Confirm that your handler applies the delivery with the greatestoccurredAtand chains same-timestamp status rows throughdata.previousStatustodata.status. - Reconcile. Compare what your handler stored against
GET /status-updatesorGET /sent-cases/{id}— the API remains the authority, and webhooks are the signal to go and check.
Reading the delivery log
GET /webhooks/{id}/deliveries is your main debugging tool once traffic is flowing: each record carries the delivery’s status, how many of its attempts have run, the HTTP code your endpoint returned on the last one, and a short lastError when that attempt failed. Reliability documents the delivery record field by field, the full list of statuses, and what each lastError string means; Troubleshooting maps the common symptoms to fixes.
Next steps
Verifying signatures
Reference verifiers and the rules your implementation must follow.
Reliability
Retries, deactivation after repeated failures, and how long records are kept.
Event catalog
Every event, its perspectives, and its
data shape.Troubleshooting
Symptoms, causes, and fixes for deliveries that do not land.

