SOAP Request Formatter
Format and check SOAP envelopes. Credentials stay in your tab.
Everything runs in this tab. Nothing you paste is uploaded, logged or sent anywhere. Open your network panel and check.
Paste a SOAP request or response above and it is indented as you type, with namespace prefixes dimmed so soap:Body reads as Body with a marker attached, and every well-formedness problem reported with its line, its column and what to write instead. Set the attribute control to three and the Envelope stops being a 300-character line: each xmlns declaration gets its own row.
The envelope you are pasting almost certainly came out of a log: a capture from Fiddler, a PHP SoapClient __getLastRequest() dump, a WCF message trace, a SoapUI raw tab, or a line written at 03:00 by a logger that does not wrap. It is unreadable in that state, and you need it readable before you can tell whether the fault is yours or theirs.
Nothing is uploaded, and here that is the point rather than a feature. SOAP payloads carry WS-Security tokens with passwords in them, signed SAML assertions, account numbers and patient records. One of the validators that rank for these searches asks you to tick a box confirming your data is stored on their servers; another site that publishes a SOAP formatter saves submitted documents publicly, and Google has indexed them. Here the parser and the formatter are JavaScript in this tab.
The envelope, and the URI that identifies a version
A SOAP message is one XML document with a fixed outer shape. The root is Envelope. It may have a Header, and if it does the Header comes first. It must have a Body, holding either the operation payload or a Fault. Everything below Body belongs to the service.
The version is identified by the namespace URI, never the prefix. The prefix is arbitrary: soap, soapenv, SOAP-ENV and env are all in circulation. If a server answers a valid-looking request with a VersionMismatch fault, compare the URI character by character before looking at anything else.
- SOAP 1.1: namespace http://schemas.xmlsoap.org/soap/envelope/ (the trailing slash is part of it), Content-Type text/xml, and the operation in a separate SOAPAction header whose value must be quoted, possibly as an empty pair of quotes.
- SOAP 1.2: namespace http://www.w3.org/2003/05/soap-envelope, Content-Type application/soap+xml with an action parameter, and no SOAPAction header. A 1.2 endpoint given a 1.1 content type usually answers HTTP 415, which makes the failure look like a transport problem.
- SOAP 1.1 tolerated elements after the Body. SOAP 1.2 does not: Header and Body are the only children of Envelope, and Body is last.
Why prefix errors are the most common SOAP breakage
Namespace declarations live on the Envelope element, and the part you care about is four levels below it. Copy the interesting fragment out of a log and you have taken the prefixes and left the declarations behind. The message then names the prefix rather than the cause: libxml2 says "Namespace prefix soap on Body is not defined", .NET says "'soap' is an undeclared prefix". The scan here reports it with the declaration to add.
The inverse mistake is quieter and worse. Pasting an unprefixed fragment into a Body that sits under a default namespace moves every element in it into that namespace: the document parses, the service accepts it, and the fields come back empty. Put xmlns="" on the root of the pasted fragment to opt out.
One rule catches out experienced people: a default namespace applies to element names, never to attribute names. That is why mustUnderstand, actor and role must carry the envelope prefix even when the envelope namespace is the default.
mustUnderstand, and headers that fail before your payload is read
A header block marked mustUnderstand is a contract: a receiver playing the targeted role must either understand the block or reject the whole message with a MustUnderstand fault, processing nothing else. That is why a request with a perfectly correct Body gets refused; the service never reached the Body.
The value differs by version, and mixing them up is a silent failure rather than an error. SOAP 1.1 defines the characters "1" or "0", defaulting to "0"; SOAP 1.2 types it as xs:boolean, so "true" and "false" work too. Send mustUnderstand="true" to a strict 1.1 stack and the attribute reads as absent, making a mandatory header optional. Targeting is the other half: 1.1 uses actor with a URI, while 1.2 renames it role and defines role/none, role/next and role/ultimateReceiver, the last being the default. Most failures at this layer are a WS-Security header marked mustUnderstand against a server with no security policy configured for that operation.
Reading a soap:Fault
A Fault is an ordinary element inside Body, and when present it must be the only child of Body. The structure changed completely between versions, which is why fault handling written against one version silently matches nothing against the other.
In SOAP 1.1 the Fault children are unqualified: faultcode, faultstring, faultactor and detail sit in no namespace even though Fault is in the envelope namespace, so //soap:Fault/soap:faultstring returns nothing and it has to be //soap:Fault/faultstring. faultcode holds a QName, usually soap:Client (your message was wrong) or soap:Server (their end failed, a retry may work).
SOAP 1.2 qualifies and renames everything: Code holds a Value from a fixed list (Sender, Receiver, VersionMismatch, MustUnderstand, DataEncodingUnknown) with an optional Subcode chain, Reason holds Text elements that each require xml:lang, and Node, Role and Detail replace the rest. The status carries information too: 1.1 returns 500 for every fault, 1.2 returns 400 for Sender and 500 for Receiver.
Sending and reading SOAP in code
The envelope you paste here usually came from one of these. Each sample sends a request, checks for a Fault before assuming success, and parses the response safely, since it is XML from a remote party and the defaults in Java, PHP and Python resolve external entities.
// SOAP 1.1 over fetch. Note SOAPAction: it is a separate header and its
// value must be quoted, even when it is empty.
const envelope = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"',
' xmlns:ns="urn:example:orders">',
' <soap:Body>',
' <ns:GetOrder><ns:id>ORD-4471</ns:id></ns:GetOrder>',
' </soap:Body>',
'</soap:Envelope>',
].join('\n');
const response = await fetch('https://example.com/orders', {
method: 'POST',
headers: {
'Content-Type': 'text/xml; charset=utf-8',
SOAPAction: '"urn:example:orders/GetOrder"',
// SOAP 1.2 instead: no SOAPAction header, and
// 'Content-Type': 'application/soap+xml; charset=utf-8; action="urn:example:orders/GetOrder"'
},
body: envelope,
});
// A fault arrives with HTTP 500 in SOAP 1.1, so response.ok is false and the
// body still holds the answer. Never throw on the status alone.
const text = await response.text();
const doc = new DOMParser().parseFromString(text, 'application/xml');
const SOAP11 = 'http://schemas.xmlsoap.org/soap/envelope/';
const fault = doc.getElementsByTagNameNS(SOAP11, 'Fault')[0];
if (fault) {
// faultcode and faultstring are unqualified, even inside a qualified Fault.
const code = fault.getElementsByTagName('faultcode')[0]?.textContent;
const reason = fault.getElementsByTagName('faultstring')[0]?.textContent;
throw new Error(code + ': ' + reason);
}import requests
from defusedxml.ElementTree import fromstring # never the stdlib parser here
SOAP11 = 'http://schemas.xmlsoap.org/soap/envelope/'
NS = {'soap': SOAP11, 'ns': 'urn:example:orders'}
envelope = """<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns="urn:example:orders">
<soap:Body>
<ns:GetOrder><ns:id>ORD-4471</ns:id></ns:GetOrder>
</soap:Body>
</soap:Envelope>"""
response = requests.post(
'https://example.com/orders',
data=envelope.encode('utf-8'),
headers={
'Content-Type': 'text/xml; charset=utf-8',
'SOAPAction': '"urn:example:orders/GetOrder"',
},
timeout=30,
)
# Do not call raise_for_status(): a SOAP 1.1 fault is HTTP 500 and the body
# is the part you need.
root = fromstring(response.content)
fault = root.find('.//soap:Fault', NS)
if fault is not None:
code = fault.findtext('faultcode') # unqualified in SOAP 1.1
reason = fault.findtext('faultstring')
raise RuntimeError(f'{code}: {reason}')
# For a real client, zeep reads the WSDL and builds the envelope for you.
# This shape is for debugging one call, which is when you end up here.import jakarta.xml.soap.*; // javax.xml.soap before Jakarta EE 9
import java.io.ByteArrayOutputStream;
// SOAPConstants.SOAP_1_2_PROTOCOL for a 1.2 endpoint. The choice sets both
// the envelope namespace and the content type, so it is the one line that
// decides which version you are speaking.
MessageFactory factory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL);
SOAPMessage message = factory.createMessage();
SOAPEnvelope envelope = message.getSOAPPart().getEnvelope();
envelope.addNamespaceDeclaration("ns", "urn:example:orders");
SOAPBody body = envelope.getBody();
SOAPElement call = body.addChildElement("GetOrder", "ns");
call.addChildElement("id", "ns").addTextNode("ORD-4471");
// SOAPAction, quoted, as a MIME header. SOAP 1.2 does not use it.
message.getMimeHeaders().addHeader("SOAPAction", "\"urn:example:orders/GetOrder\"");
message.saveChanges();
// The raw bytes on the wire: this is what you paste into a formatter.
ByteArrayOutputStream sent = new ByteArrayOutputStream();
message.writeTo(sent);
System.out.println(sent.toString("UTF-8"));
SOAPConnection connection = SOAPConnectionFactory.newInstance().createConnection();
SOAPMessage response = connection.call(message, "https://example.com/orders");
if (response.getSOAPBody().hasFault()) {
SOAPFault fault = response.getSOAPBody().getFault();
throw new RuntimeException(
fault.getFaultCode() + ": " + fault.getFaultString());
}using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Xml;
using System.Xml.Linq;
const string Soap11 = "http://schemas.xmlsoap.org/soap/envelope/";
XNamespace soap = Soap11;
XNamespace ns = "urn:example:orders";
var envelope = new XDocument(
new XElement(soap + "Envelope",
new XAttribute(XNamespace.Xmlns + "soap", Soap11),
new XElement(soap + "Body",
new XElement(ns + "GetOrder",
new XElement(ns + "id", "ORD-4471")))));
using var http = new HttpClient();
var content = new StringContent(envelope.ToString(), Encoding.UTF8);
content.Headers.ContentType = new MediaTypeHeaderValue("text/xml")
{
CharSet = "utf-8",
};
// SOAP 1.2 instead: media type application/soap+xml with an action parameter,
// and no SOAPAction header.
content.Headers.Add("SOAPAction", "\"urn:example:orders/GetOrder\"");
var response = await http.PostAsync("https://example.com/orders", content);
var body = await response.Content.ReadAsStringAsync();
// A fault is HTTP 500 with a real body, so do not call
// EnsureSuccessStatusCode() before you have looked at it.
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null,
};
using var reader = XmlReader.Create(new StringReader(body), settings);
var doc = XDocument.Load(reader);
var fault = doc.Descendants(soap + "Fault").FirstOrDefault();
if (fault is not null)
{
// Unqualified children in SOAP 1.1: no namespace on the element name.
var code = fault.Element("faultcode")?.Value;
var reason = fault.Element("faultstring")?.Value;
throw new InvalidOperationException(code + ": " + reason);
}<?php
// trace => true is why this snippet exists: it is how you get the raw
// envelope to paste into a formatter and see what was actually sent.
$client = new SoapClient('https://example.com/orders?wsdl', [
'trace' => true,
'exceptions' => true,
'soap_version' => SOAP_1_1, // SOAP_1_2 changes the namespace and the
// content type together
'cache_wsdl' => WSDL_CACHE_NONE,
'stream_context' => stream_context_create([
'ssl' => ['verify_peer' => true, 'verify_peer_name' => true],
]),
]);
try {
$result = $client->GetOrder(['id' => 'ORD-4471']);
} catch (SoapFault $e) {
// faultcode is the QName from the envelope, e.g. "soap:Client".
fprintf(STDERR, "%s: %s\n", $e->faultcode, $e->getMessage());
} finally {
// Both are null unless trace was enabled before the call.
echo $client->__getLastRequest(), "\n";
echo $client->__getLastResponse(), "\n";
}# Capture a request and a response you can actually read. The SOAPAction
# value keeps its own quotes inside the header value.
curl -sS -D headers.txt \
-H 'Content-Type: text/xml; charset=utf-8' \
-H 'SOAPAction: "urn:example:orders/GetOrder"' \
--data-binary @request.xml \
https://example.com/orders \
| tee response.xml | xmllint --format --nonet -
# SOAP 1.2: no SOAPAction header, the action rides on the content type.
curl -sS \
-H 'Content-Type: application/soap+xml; charset=utf-8; action="urn:example:orders/GetOrder"' \
--data-binary @request.xml \
https://example.com/orders | xmllint --format --nonet -
# Was it a fault? Binding a namespace to xmllint --xpath is awkward, so match
# on the local name:
xmllint --nonet --xpath 'count(//*[local-name()="Fault"])' response.xml
# curl exits 0 on HTTP 500. Check the status line yourself:
head -1 headers.txtThe recurring mistake in all six is treating HTTP 500 as a transport failure. A SOAP 1.1 fault is delivered with status 500 and a complete envelope in the body, so raise_for_status(), EnsureSuccessStatusCode() and a bare response.ok check throw away the only description of what went wrong that you are going to get.
Common questions
My envelope contains a password and a customer record. Is it uploaded?
No. The parser and the formatter are JavaScript running in this tab, in a Web Worker. There is no endpoint to send anything to, no analytics with access to the editor and no third-party scripts.
Check it rather than believing it: open the Network tab, paste the envelope and format it. The page loads its own assets once and then goes quiet. That check matters more here than anywhere else on this site, because a WS-Security header carries a UsernameToken with a password digest or, on plenty of internal services, the password itself. Your input stays in this browser's localStorage until you clear it.
What is the difference between SOAP 1.1 and SOAP 1.2?
Start with the namespace URI, because everything else follows from it: 1.1 is http://schemas.xmlsoap.org/soap/envelope/ and 1.2 is http://www.w3.org/2003/05/soap-envelope. The prefix tells you nothing.
On the wire, 1.1 uses text/xml with a separate quoted SOAPAction header and 1.2 uses application/soap+xml with an action parameter and no SOAPAction. Inside the message, 1.2 rewrote the Fault and qualified every part of it, typed mustUnderstand as a boolean, renamed actor to role, and forbade application elements after the Body. Most services in production are still 1.1.
Why do I keep getting "undeclared prefix" errors?
Because the xmlns declarations live on the Envelope element and you copied something below it. A prefix is only meaningful while a declaration binding it is in scope, so soap:Body pasted on its own is not well-formed XML, never mind valid SOAP.
Add the binding to the root of what you pasted: xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" for a 1.1 fragment. The inverse mistake raises no error at all, so watch for it: an unprefixed fragment pasted into a Body under a default namespace is silently moved into that namespace, and the fields come back empty.
Does this validate my envelope against the SOAP schema?
No, and pretending otherwise would be the wrong kind of helpful. This page parses the envelope, reports every well-formedness error with a line and a column, checks that each prefix is bound, formats without touching your data, and reports the size, line count and element count.
It does not enforce the SOAP content model, so it will not object if Header follows Body, if Body is missing, or if you built a 1.1 fault inside a 1.2 envelope. Those are schema constraints: run the envelope against the SOAP envelope schema, published at the namespace URI, in the XSD validator here. It does not read WSDL, send requests or verify signatures either.
Where do I get the raw envelope to paste in here?
From the client rather than from your code, because you want the bytes that went on the wire, not the object you handed to a library. In PHP, construct SoapClient with trace and call __getLastRequest(). In Java with SAAJ, call message.writeTo(System.out) after saveChanges(). In .NET, enable WCF message logging. In Python with zeep, attach the HistoryPlugin and read last_sent.
From outside the process, curl with --data-binary and -D writes the response and its headers to files, Fiddler and mitmproxy capture live traffic, and SoapUI has a raw tab on both sides. However you get it, it arrives as one long line, which is what this page is for.