Your SOAP request looks syntactically correct. The operation name is right, the parameters are valid, and the endpoint is reachable. But instead of a response, you get:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:MustUnderstand</faultcode>
<faultstring>
SOAP header Security was not understood.
</faultstring>
</soap:Fault>
</soap:Body>
</soap:Envelope>
A MustUnderstand fault means the server received a SOAP header element marked as mandatory (mustUnderstand="1" or mustUnderstand="true") but doesn't have a handler registered for that header. The SOAP specification requires that a conformant processor must reject any message containing a mandatory header it cannot process — there is no fallback, no graceful degradation. The message is rejected outright.
What mustUnderstand Actually Means
The mustUnderstand attribute is a binary directive on any SOAP header element. When set to "1" (SOAP 1.1) or "true" (SOAP 1.2), it tells the receiving SOAP processor: "You must know how to process this header. If you don't, reject the entire message."
<soap:Header>
<wsse:Security
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
soap:mustUnderstand="1">
<wsse:UsernameToken>
<wsse:Username>admin</wsse:Username>
<wsse:Password>secret</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
In this example, if the server doesn't have WS-Security processing enabled, it must return a MustUnderstand fault. It cannot simply ignore the Security header and process the body, because doing so would be a security violation — the client explicitly required the header to be understood.
Common Headers That Cause MustUnderstand Faults
1. WS-Security Headers
The most frequent trigger. Your client's SOAP library automatically adds a WS-Security header (username token, X.509 certificate, or SAML assertion), but the server either doesn't implement WS-Security or expects a different security mechanism.
Typical header:
<wsse:Security soap:mustUnderstand="1"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsu:Created>2026-03-01T10:00:00Z</wsu:Created>
<wsu:Expires>2026-03-01T10:05:00Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
Why it happens: Frameworks like WCF with wsHttpBinding add WS-Security headers by default. If the target service uses basicHttpBinding or a non-WCF SOAP stack, it has no WS-Security handler and rejects the header.
2. WS-Addressing Headers
WS-Addressing adds routing metadata like To, Action, MessageID, and ReplyTo to the SOAP header. Some frameworks add these with mustUnderstand="1" by default.
Typical headers:
<wsa:Action soap:mustUnderstand="1"
xmlns:wsa="http://www.w3.org/2005/08/addressing">
http://example.com/IOrderService/GetOrder
</wsa:Action>
<wsa:To soap:mustUnderstand="1"
xmlns:wsa="http://www.w3.org/2005/08/addressing">
https://api.example.com/OrderService.svc
</wsa:To>
Why it happens: WCF's wsHttpBinding includes WS-Addressing by default. Java-based SOAP services often don't have WS-Addressing handlers configured, and older ASP.NET ASMX services never supported it.
3. WS-ReliableMessaging Headers
WS-ReliableMessaging ensures message delivery through acknowledgment sequences. If your binding enables reliable sessions, these headers appear:
<wsrm:Sequence soap:mustUnderstand="1"
xmlns:wsrm="http://schemas.xmlsoap.org/ws/2005/02/rm">
<wsrm:Identifier>urn:uuid:abc123</wsrm:Identifier>
<wsrm:MessageNumber>1</wsrm:MessageNumber>
</wsrm:Sequence>
Why it happens: WCF's wsHttpBinding with reliableSession enabled="true" adds these headers. Most non-WCF services have no idea what to do with them.
How to Identify the Offending Header
The fault message sometimes names the header (like "Security was not understood"), but not always. When it doesn't, you need to inspect the outbound request.
Step 1: Capture the Raw Request
Using WCF message logging:
<system.diagnostics>
<sources>
<source name="System.ServiceModel.MessageLogging" switchValue="Verbose">
<listeners>
<add name="messages"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData="c:\logs\messages.svclog" />
</listeners>
</source>
</sources>
</system.diagnostics>
<system.serviceModel>
<diagnostics>
<messageLogging logEntireMessage="true"
logMalformedMessages="true"
logMessagesAtServiceLevel="false"
logMessagesAtTransportLevel="true" />
</diagnostics>
</system.serviceModel>
Using a network proxy (language-agnostic):
# Using mitmproxy to intercept SOAP traffic
mitmproxy --mode reverse:https://api.example.com --listen-port 8080
# Point your client at localhost:8080 instead of the real service
Step 2: Find All mustUnderstand="1" Headers
Search the captured XML for mustUnderstand:
# Extract headers with mustUnderstand from the captured request
curl -s -X POST https://api.example.com/service.svc \
--trace-ascii /dev/stderr \
-H "Content-Type: text/xml; charset=utf-8" \
-d @request.xml 2>&1 | grep -i "mustunderstand"
Every header element with mustUnderstand="1" is a potential culprit. The server must have a handler for each one.
Solutions
Solution 1: Remove Unnecessary Headers
The most direct fix. If the server doesn't need WS-Security, WS-Addressing, or WS-ReliableMessaging, stop sending those headers.
In WCF (.NET) — switch from wsHttpBinding to basicHttpBinding:
<!-- Before: wsHttpBinding adds WS-Security + WS-Addressing -->
<endpoint binding="wsHttpBinding" contract="IOrderService" />
<!-- After: basicHttpBinding sends plain SOAP with no WS-* headers -->
<endpoint binding="basicHttpBinding" contract="IOrderService" />
In WCF — disable specific features while keeping wsHttpBinding:
<wsHttpBinding>
<binding name="NoWsExtensions">
<security mode="None" /> <!-- Removes WS-Security headers -->
<reliableSession enabled="false" /> <!-- Removes WS-RM headers -->
</binding>
</wsHttpBinding>
To remove WS-Addressing specifically, use a custom binding:
<customBinding>
<binding name="NoAddressing">
<textMessageEncoding messageVersion="Soap11" />
<httpTransport />
</binding>
</customBinding>
Using Soap11 message version in a custom binding implicitly disables WS-Addressing (which is only included by default in Soap12WSAddressing10 and Soap11WSAddressing10 message versions).
Solution 2: Configure the Server to Handle the Header
If the headers are required for your use case (e.g., you genuinely need WS-Security), the server must be configured to process them.
In Java (Apache CXF) — add a WS-Security interceptor:
import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor;
Map<String, Object> inProps = new HashMap<>();
inProps.put("action", "UsernameToken");
inProps.put("passwordType", "PasswordText");
inProps.put("passwordCallbackClass", "com.example.ServerPasswordCallback");
endpoint.getInInterceptors().add(new WSS4JInInterceptor(inProps));
In Spring Boot with CXF:
<jaxws:endpoint id="orderService" implementor="#orderServiceImpl"
address="/OrderService">
<jaxws:inInterceptors>
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken" />
<entry key="passwordType" value="PasswordText" />
</map>
</constructor-arg>
</bean>
</jaxws:inInterceptors>
</jaxws:endpoint>
Solution 3: Implement a Custom Header Handler
If you control the server and need to acknowledge a header without actually processing it (rare, but sometimes necessary for interoperability), you can register a no-op handler.
In WCF:
public class CustomHeaderHandler : IDispatchMessageInspector
{
public object AfterReceiveRequest(
ref Message request,
IClientChannel channel,
InstanceContext instanceContext)
{
// Find and mark the header as understood
int headerIndex = request.Headers.FindHeader(
"CustomHeader",
"http://example.com/custom");
if (headerIndex >= 0)
{
request.Headers.UnderstoodHeaders.Add(
request.Headers[headerIndex]);
}
return null;
}
public void BeforeSendReply(
ref Message reply,
object correlationState) { }
}
Warning: Only use this approach when you genuinely understand the header's purpose and have determined that not processing it is safe. Blindly marking security headers as "understood" without processing them creates security vulnerabilities.
Solution 4: Set mustUnderstand to 0 on the Client Side
If you control the client and the header is informational rather than mandatory, you can set mustUnderstand="0" to make it optional:
# Python (zeep) — add a custom header without mustUnderstand
from lxml import etree
from zeep import Client
client = Client("https://api.example.com/service?wsdl")
header = etree.Element(
"{http://example.com/custom}TraceId",
nsmap={"custom": "http://example.com/custom"}
)
header.text = "req-12345"
# mustUnderstand is 0 by default when not explicitly set
response = client.service.GetOrder(
orderId="12345",
_soapheaders=[header]
)
Debugging Workflow Summary
1. Capture outbound SOAP request (raw XML)
↓
2. List all <Header> elements with mustUnderstand="1"
↓
3. For each header:
├── Is it required for your use case?
│ ├── Yes → Configure server to handle it
│ └── No → Remove it from client configuration
└── Is it added by framework defaults?
├── Yes → Switch to simpler binding (e.g. basicHttpBinding)
└── No → Check if mustUnderstand can be set to "0"
How SOAPless Helps
MustUnderstand faults almost always arise from framework-level header injection — your SOAP library adds headers that the target service wasn't built to handle. This is a pervasive problem in cross-platform SOAP integrations (WCF calling Java services, PHP calling .NET services, etc.).
SOAPless acts as a protocol bridge that normalizes these differences. When you register a WSDL, SOAPless constructs SOAP requests with only the headers the target service expects — no extraneous WS-Addressing, no unexpected WS-ReliableMessaging, and WS-Security only when explicitly configured. Your application communicates through a clean REST JSON API, and SOAPless handles the SOAP header negotiation with the upstream service.
All authentication credentials are managed through the SOAPless dashboard with AES-256-GCM encryption, and you can test any operation directly in the browser before deploying to production.