Skip to main content

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.
The 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 with GET /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.
A successful round trip looks like this once the attempt has run:
The log never includes the payload. To see the body CaseXchange sent, use echo mode.

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 400 with error.code echo_requires_active.
The 200 response contains exactly two fields and nothing else:
This is the only place 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

Each capture is one delivery as CaseXchange sent it:
Key order in the body is not part of the contract — always hash the 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) and limit (default 20, maximum 100), like the other webhook lists.
5

Disable echo mode

The 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.
The echo token is a credential. The capture endpoint accepts unauthenticated posts on that token, so anyone who has it can inject fake captures into your subscription. Do not share it, log it, or commit it. Captures also contain the full delivered payload, including client details on unmasked events — treat echo mode with the same care as the payload itself, and disable it when you are done.
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.
Tunnel hostnames often change between sessions. Update the subscription with PATCH /webhooks/{id} and { "url": "https://new-host.example/casexchange" } rather than creating a new one each time — a new subscription only receives events that happen after it is created, and only five can be active per firm.

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 shows isTestCase: 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

  1. Subscribe and confirm reachability. Create the subscription, send a test event, and confirm DELIVERED in the delivery log.
  2. Receive referral.sent. When the test case is sent, your subscription receives referral.sent with perspective: "sent" and isTestCase: true. (Referring an ordinary, non-test case with POST /sent-cases/{id}/refer behaves the same way but carries isTestCase: 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 gets referral.received for the same fact with the same X-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.
  3. Acknowledge on the receiving side. Until the receiving firm acknowledges the referral itself, its deliveries carry piiMasked: true and a reduced data.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 with piiMasked: false and the full snapshot. Only the receiving firm’s own action unmasks; see Client data and masking.
  4. Receive case.status_changed. Make a further transition on the referral, such as closing or withdrawing it, and confirm case.status_changed arrives with data.previousStatus and data.status describing the move. If the receiving firm accepts it first, you see referral.accepted instead.
  5. Verify the signature on every delivery. Compute the HMAC over the raw body before parsing, check the t window, and compare in constant time. Return a non-2xx status when verification fails.
  6. Deduplicate. Send a test event to an endpoint that returns 500 once and then 200; confirm the retry carries the same X-CaseXchange-Delivery and that your handler applies it once.
  7. Order by occurredAt. Deliveries can arrive out of order. Confirm that your handler applies the delivery with the greatest occurredAt and chains same-timestamp status rows through data.previousStatus to data.status.
  8. Reconcile. Compare what your handler stored against GET /status-updates or GET /sent-cases/{id} — the API remains the authority, and webhooks are the signal to go and check.
See the Event catalog for the envelope of each event and Status Updates examples for the reconciliation calls.

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.