We have trained ourselves to read a successful HTTP status as the end of a story. The request returned 200. The dashboard is green. The endpoint is healthy. Yet the customer still has no value, the ledger is unchanged, or the downstream operation quietly moved into a failed state.
Transport success is only one layer
An HTTP status answers a narrow question: did this server accept and respond to this request in a way the protocol considers successful? That matters, but it is not the same as asking whether the business operation achieved its intended outcome.
Consider a transfer API that accepts a request, records it as pending, dispatches work to a provider, and returns immediately. A 200 OK or 202 Accepted may be perfectly honest. The API did its part. The provider can still reject the transfer ten seconds later. The customer experiences failure even though every synchronous span looks healthy.
Technical completion and business success are related, but they are not synonyms.
Model the outcome explicitly
The mistake is not asynchronous processing. The mistake is leaving the final meaning implicit. A useful execution model should be able to answer four different questions:
- Did the request complete?
- Did the technical workflow complete?
- Did the intended business outcome occur?
- If not, where did reality diverge from expectation?
That usually means recording an outcome close to the domain, not trying to infer everything later from status codes and log text.
execution.SetOutcome(
status: OutcomeStatus.Failed,
code: "PROVIDER_REJECTED",
reason: "Beneficiary account could not be credited");
Follow the operation, not only the request
In distributed systems, the unit of interest is often larger than one HTTP request. It may include an inbound call, two outbound dependencies, a database write, a queued continuation, and a webhook that arrives later. Treating each piece as an isolated success creates a collection of locally correct statements that still fail to explain the customer’s experience.
The operational question should become: what was supposed to happen next, and did it happen? That leads naturally to execution timelines, parent-child relationships, expected continuations, and explicit business outcomes.
What teams should alert on
Alerting on every 500 response creates noise. Alerting only on infrastructure health misses business failures. Better signals combine the two. A spike in provider timeouts matters. So does a rise in executions where the transport succeeded but the outcome became failed, abandoned, or unresolved.
The goal is not to produce more telemetry. It is to shorten the distance between a customer-visible failure and a precise explanation.
The useful definition of success
Success is not “the handler did not throw.” Success is the intended state transition happening, with enough evidence to trust that conclusion. Sometimes that conclusion is available immediately. Sometimes it arrives through a job, provider callback, or reconciliation process. The architecture should make that delay explicit rather than pretending the first response contains the whole truth.
A 200 can be correct. It just should not be mistaken for the ending.