One of the most expensive integration habits is forwarding raw SOAP faults to downstream teams and expecting them to parse XML error structures they did not sign up for.
If every consumer of your integration must understand soap:Client versus soap:Server, dig through <detail> elements for vendor-specific exception strings, and decide whether a fault is retryable — you have not simplified anything. You have duplicated the integration cost across the organization.
The anatomy of a SOAP Fault
Before discussing normalization, it helps to understand exactly what a raw SOAP fault contains.
SOAP 1.1 Fault structure
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:Client</faultcode>
<faultstring>Invalid account number format</faultstring>
<faultactor>http://example.com/billing</faultactor>
<detail>
<ValidationError xmlns="http://example.com/errors">
<Field>accountNumber</Field>
<Rule>Must be 10 digits</Rule>
</ValidationError>
</detail>
</soap:Fault>
</soap:Body>
</soap:Envelope>
SOAP 1.2 Fault structure
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<soap12:Fault>
<soap12:Code>
<soap12:Value>soap12:Sender</soap12:Value>
<soap12:Subcode>
<soap12:Value>ex:ValidationFailed</soap12:Value>
</soap12:Subcode>
</soap12:Code>
<soap12:Reason>
<soap12:Text xml:lang="en">Invalid account number format</soap12:Text>
</soap12:Reason>
<soap12:Detail>
<ValidationError xmlns="http://example.com/errors">
<Field>accountNumber</Field>
<Rule>Must be 10 digits</Rule>
</ValidationError>
</soap12:Detail>
</soap12:Fault>
</soap12:Body>
</soap12:Envelope>
The two versions use different element names, different code taxonomies (Client/Server vs Sender/Receiver), and different nesting structures. Expecting downstream teams to handle both is asking them to build a SOAP parser they never wanted.
The real cost of passing faults through
When raw SOAP faults reach downstream teams, each team independently has to solve:
- Parsing: extract error information from XML with varying structures
- Classification: determine if the fault is a client error (bad input) or server error (upstream outage)
- Retry logic: decide whether the error is transient (
soap:Server) or permanent (soap:Client) - User messaging: translate XML fault strings into something end users or operators can act on
- Monitoring: build alerts and dashboards around XML fault codes that vary by vendor
A three-team organization consuming the same SOAP service will build three slightly different fault parsers, three different retry strategies, and three different monitoring dashboards — all doing roughly the same work.
Before and after: what normalization looks like
Before — raw SOAP fault forwarded to consumer
The consumer receives the full XML body and a generic HTTP 500:
curl -s https://api.internal.example.com/billing/validate \
-H "Content-Type: text/xml" \
-d @request.xml
# Response: HTTP 500
# Body: raw SOAP fault XML (47 lines of XML)
The consumer must parse the XML, extract <faultcode>, read <faultstring>, and optionally traverse <detail> — all with an XML parser they may not even have in their stack.
After — normalized JSON error at the integration boundary
The integration boundary translates the SOAP fault into a structured JSON response with an appropriate HTTP status code:
{
"error": {
"type": "validation_error",
"code": "INVALID_FORMAT",
"message": "Invalid account number format",
"detail": {
"field": "accountNumber",
"rule": "Must be 10 digits"
},
"upstream": {
"soap_fault_code": "soap:Client",
"soap_fault_actor": "http://example.com/billing"
}
}
}
With this approach:
- HTTP status conveys the error class:
400for client faults,502for server faults - Structured JSON is parseable by any language without an XML dependency
- Upstream metadata is preserved for debugging without leaking raw XML to every consumer
- Retry decisions are straightforward:
400= do not retry,502= retry with backoff
Mapping SOAP fault codes to HTTP status codes
A reliable normalization layer uses the fault code to pick the right HTTP status:
| SOAP Fault Code | Meaning | HTTP Status |
|---|---|---|
soap:Client / Sender | Bad request from caller | 400 |
soap:Server / Receiver | Upstream service failure | 502 |
soap:VersionMismatch | Wrong SOAP version | 400 |
soap:MustUnderstand | Required header not processed | 400 |
| Timeout / no response | Upstream did not respond | 504 |
What a responsible boundary does
A healthy integration boundary performs three functions:
- Normalizes fault shape: every error follows the same JSON schema regardless of which upstream SOAP service produced it
- Preserves diagnostic detail: the original fault code and fault string are available for debugging, but wrapped in a structure that does not require XML parsing
- Stops leaking XML handling into unrelated teams: frontend developers, mobile engineers, and data pipeline teams should not need an XML library to handle errors
This is not about hiding information. It is about translating faults into a form the rest of the organization can act on without learning SOAP.
The communication cost nobody measures
Beyond code, raw SOAP faults create invisible communication overhead:
- Slack threads: "What does
soap:Servermean? Should I retry?" - Incident response: "Is this a client bug or an upstream outage?" — requires reading XML to answer
- Onboarding: new engineers must learn SOAP fault semantics before they can debug integration errors
- Documentation: each team writes its own "how to interpret SOAP faults" guide
Normalizing faults at the boundary eliminates these conversations. A 400 with a clear message is universally understood.
How SOAPless handles this automatically
SOAPless sits at exactly this integration boundary. When a SOAP service returns a fault, SOAPless automatically:
- Parses the SOAP fault XML (both 1.1 and 1.2 formats)
- Maps the fault code to the appropriate HTTP status code
- Returns a normalized JSON error response with the original fault details preserved
- Makes fault information visible in the dashboard for debugging
Your downstream consumers receive clean JSON errors with standard HTTP status codes. They never need to parse XML, learn SOAP fault taxonomies, or build their own normalization layer. The SOAP complexity stays where it belongs — at the boundary.