SOAP is a contract-driven way to call remote operations by exchanging XML messages, usually over HTTP. The contract is a machine-readable file called a WSDL, and most of the work in a SOAP integration is reading it correctly. This article is meant for developers building or consuming web services, with a basic familiarity with HTTP and XML. For the lighter-weight alternative, see REST API Fundamentals.
What SOAP is
SOAP (Simple Object Access Protocol) is a specification for exchanging structured information using XML as the message format. It is most often carried over HTTP, though the specification allows other transports.
Where REST models resources and uses HTTP methods to act on them, SOAP models operations. You call GetOrderStatus or SubmitInvoice, passing a structured XML message and getting one back. That has some practical consequences:
-
One endpoint, many operations. A service typically has a single URL. The operation is named inside the message (and often in a
SOAPActionheader), not in the URL path. - Contract first. The service publishes a WSDL that defines every operation and message format. You build your client from it rather than from prose documentation.
- Strongly typed. Messages are described with XML Schema, so both sides can validate them mechanically.
- Standards for the hard parts. Security, reliability, and attachments each have defined extensions (WS-Security, WS-ReliableMessaging, MTOM), instead of being left to each provider.
Message structure
Every SOAP message is an XML document with the same outer structure: an Envelope that contains an optional Header and a required Body.
- Envelope: the root element. It declares the SOAP version through its XML namespace.
- Header: optional metadata that isn't part of the business data, such as authentication tokens or message IDs.
-
Body: the actual request or response, or a
Faultif something went wrong.
Here is a complete request and response for a hypothetical order-status operation:
Request
POST /OrderService HTTP/1.1
Host: api.example.com
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://example.com/orders/GetOrderStatus"
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ord="http://example.com/orders">
<soapenv:Header/>
<soapenv:Body>
<ord:GetOrderStatusRequest>
<ord:orderId>1042</ord:orderId>
</ord:GetOrderStatusRequest>
</soapenv:Body>
</soapenv:Envelope>
Response
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ord="http://example.com/orders">
<soapenv:Body>
<ord:GetOrderStatusResponse>
<ord:orderId>1042</ord:orderId>
<ord:status>SHIPPED</ord:status>
</ord:GetOrderStatusResponse>
</soapenv:Body>
</soapenv:Envelope>
Pay attention to the namespaces. soapenv is the SOAP envelope namespace, and ord is the service's own namespace. The prefix names (soapenv, ord) are arbitrary, but the namespace URIs they point to must match the WSDL exactly. A correct element in the wrong namespace is one of the most common reasons a request is rejected.
SOAP over HTTP
When SOAP runs over HTTP, the rules are simple and quite different from REST:
- Requests are almost always
POSTto the single service URL. - The body is the XML envelope, with a
Content-Typethat depends on the SOAP version (see below). - Success returns
200 OKwith a response envelope. A SOAP Fault is returned with500 Internal Server Error, as the SOAP 1.1 specification requires.
Two SOAP versions are in use, and the service's WSDL tells you which one it expects:
| SOAP 1.1 | SOAP 1.2 | |
|---|---|---|
| Envelope namespace | http://schemas.xmlsoap.org/soap/envelope/ |
http://www.w3.org/2003/05/soap-envelope |
Content-Type |
text/xml |
application/soap+xml |
| Operation hint |
SOAPAction HTTP header |
action parameter inside Content-Type
|
| Fault structure |
faultcode, faultstring, detail
|
Code, Reason, Detail
|
| Prevalence | Still the most common | Common in newer services |
Common mismatch: sending a SOAP 1.2 envelope with a SOAP 1.1 Content-Type (or the reverse) typically produces a 415, a 500, or a VersionMismatch fault. Match the version and content type to the WSDL's binding.
The WSDL contract
The WSDL (Web Services Description Language) file is the single source of truth for a SOAP service. It tells the consumer where the service is, what operations it offers, and exactly what each message must look like. Many services publish it at the endpoint URL with ?wsdl appended, though that is a convention, not a rule.
A WSDL answers four questions, and it is organized to match:
| Element | Question it answers |
|---|---|
types |
What data can I send and receive? (XML Schema definitions) |
message / portType
|
What operations exist, and what goes in and out of each? |
binding |
How are those operations carried: SOAP version, transport, SOAPAction, document or RPC style? |
service / port
|
Where do I send the request? (The endpoint URL) |
Here is a condensed WSDL 1.1 for the example operation above:
<definitions name="OrderService"
targetNamespace="http://example.com/orders"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:ord="http://example.com/orders">
<!-- 1. DATA TYPES: the XML Schema for every message -->
<types>
<xsd:schema targetNamespace="http://example.com/orders">
<xsd:element name="GetOrderStatusRequest">
<xsd:complexType><xsd:sequence>
<xsd:element name="orderId" type="xsd:string"/>
</xsd:sequence></xsd:complexType>
</xsd:element>
<!-- GetOrderStatusResponse omitted for brevity -->
</xsd:schema>
</types>
<!-- 2. MESSAGES: named wrappers around those types -->
<message name="GetOrderStatusIn">
<part name="parameters" element="ord:GetOrderStatusRequest"/>
</message>
<message name="GetOrderStatusOut">
<part name="parameters" element="ord:GetOrderStatusResponse"/>
</message>
<!-- 3. PORT TYPE: the operations (the abstract interface) -->
<portType name="OrderPort">
<operation name="GetOrderStatus">
<input message="ord:GetOrderStatusIn"/>
<output message="ord:GetOrderStatusOut"/>
</operation>
</portType>
<!-- 4. BINDING: how the operations travel (SOAP over HTTP) -->
<binding name="OrderBinding" type="ord:OrderPort">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="GetOrderStatus">
<soap:operation soapAction="http://example.com/orders/GetOrderStatus"/>
<input><soap:body use="literal"/></input>
<output><soap:body use="literal"/></output>
</operation>
</binding>
<!-- 5. SERVICE: where to send the request -->
<service name="OrderService">
<port name="OrderPort" binding="ord:OrderBinding">
<soap:address location="https://api.example.com/OrderService"/>
</port>
</service>
</definitions>
If the service uses WS-Security, its requirements are described in the WSDL as well (usually as a policy attached to the binding). The consumer uses the schemas to build valid requests and to interpret the responses.
Tip: Read the WSDL from the bottom up. Start at service for the endpoint, move to binding for the protocol details, then portType for the operations, then types for the message fields.
Most developers never write these messages by hand. Tools generate client code or build requests directly from the WSDL, such as wsimport for Java, svcutil or Add Service Reference for .NET, zeep for Python, and SoapUI for interactive testing.
Faults
SOAP defines a standard way to report errors: the Fault element. It goes in the Body in place of the normal response, and the HTTP status is 500. This differs from REST, where the status code carries most of the meaning. In SOAP, you need to read the Fault.
SOAP 1.1 fault
HTTP/1.1 500 Internal Server Error
Content-Type: text/xml; charset=utf-8
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<soapenv:Fault>
<faultcode>soapenv:Client</faultcode>
<faultstring>Order 9999 not found</faultstring>
<detail>
<ord:OrderError xmlns:ord="http://example.com/orders">
<ord:code>ORDER_NOT_FOUND</ord:code>
</ord:OrderError>
</detail>
</soapenv:Fault>
</soapenv:Body>
</soapenv:Envelope>
SOAP 1.2 fault (same information, different elements)
<env:Fault xmlns:env="http://www.w3.org/2003/05/soap-envelope"> <env:Code><env:Value>env:Sender</env:Value></env:Code> <env:Reason><env:Text xml:lang="en">Order 9999 not found</env:Text></env:Reason> <env:Detail>...</env:Detail> </env:Fault>
| 1.1 code | 1.2 code | Meaning |
|---|---|---|
Client |
Sender |
The request was wrong. Fix it and don't retry unchanged. |
Server |
Receiver |
The service failed. A retry may succeed. |
VersionMismatch |
VersionMismatch |
Wrong envelope namespace, so the SOAP version is wrong. |
MustUnderstand |
MustUnderstand |
The service couldn't process a mandatory header block. |
The detail element is where services put their own structured error information. Log it. It is usually more useful than the fault string alone.
WS-Security
SOAP has a built-in extension point for message-level security, called WS-Security. When a service uses it, the requirements are defined in the WSDL, and the credentials or signatures travel in the SOAP Header. A simple example is a username token:
<soapenv:Header>
<wsse:Security
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken>
<wsse:Username>svc-orders</wsse:Username>
<wsse:Password>********</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
WS-Security can also sign and encrypt parts of the message with X.509 certificates, so protection applies to the message itself and not only to the connection. That matters when a message passes through intermediaries.
Always use HTTPS as well. WS-Security protects the message; TLS protects the connection. A UsernameToken with a plain-text password sent over HTTP exposes the password. For certificate-based signing and encryption, see Managing Keys and Certificates.
Attachments: SOAP with Attachments
XML isn't a good container for large or binary data. Embedding a file inline means encoding it (typically Base64, which adds about a third to its size) and parsing it as part of the XML. SOAP with Attachments (SwA) solves this by sending the file as a separate MIME part alongside the envelope, usually over HTTP. The SOAP message references the attachment by its content ID.
POST /DocumentService HTTP/1.1
Content-Type: multipart/related; boundary=MIME_boundary;
type="text/xml"; start="<rootpart@example.com>"
SOAPAction: "http://example.com/docs/Upload"
--MIME_boundary
Content-Type: text/xml; charset=utf-8
Content-ID: <rootpart@example.com>
<soapenv:Envelope ...>
<soapenv:Body>
<doc:Upload>
<doc:fileName>invoice-8841.pdf</doc:fileName>
</doc:Upload>
</soapenv:Body>
</soapenv:Envelope>
--MIME_boundary
Content-Type: application/pdf
Content-Transfer-Encoding: binary
Content-ID: <invoice-8841@example.com>
...binary PDF bytes...
--MIME_boundary--
The request is a multipart/related message. The first part is the SOAP envelope and the following parts are the attachments, each identified by a Content-ID.
You may also see MTOM (Message Transmission Optimization Mechanism), a newer standard that also sends binary data as separate MIME parts but keeps it logically inside the XML document. The WSDL and the provider's documentation will say which approach a service expects, and they aren't interchangeable.
In CIC Studio, processing attachments is handled by Business Process tasks that support Web Services.
Calling a SOAP service
With curl, to see exactly what goes on the wire
curl -i -X POST https://api.example.com/OrderService \ -H 'Content-Type: text/xml; charset=utf-8' \ -H 'SOAPAction: "http://example.com/orders/GetOrderStatus"' \ --data @get-order-status.xml
Here get-order-status.xml contains the request envelope shown earlier. This is the quickest way to isolate whether a problem is in the service or in your client.
With a generated client (Python and zeep)
from zeep import Client
# zeep reads the WSDL and builds the request XML for you
client = Client("https://api.example.com/OrderService?wsdl")
result = client.service.GetOrderStatus(orderId="1042")
print(result.status) # SHIPPED
The library reads the WSDL, builds a correct envelope, handles namespaces, and parses the response into objects. Faults become exceptions:
from zeep.exceptions import Fault
try:
client.service.GetOrderStatus(orderId="9999")
except Fault as f:
print(f.message) # Order 9999 not found
Generated clients are the right default. Hand-built XML is useful for debugging, or when a provider's WSDL isn't tooling-friendly.
SOAP vs. REST
Neither is better in general. They fit different problems.
| SOAP | REST | |
|---|---|---|
| Style | Operation-oriented (RPC-like) | Resource-oriented |
| Format | XML envelope only | Any; usually JSON |
| Contract | WSDL and XML Schema, required | Optional (often OpenAPI) |
| Transport | HTTP and others | HTTP |
| Security | WS-Security (message-level) plus TLS | TLS plus token-based auth such as OAuth 2.0 |
| Errors | Standard Fault element |
HTTP status codes and provider-defined bodies |
| Message size | Verbose | Compact |
| Tooling | Code generation from WSDL | Testable with a browser or curl |
Where SOAP is strong: a formal, validated contract; built-in standards for security, reliability, and attachments; excellent tooling for generating clients; and good fit for connecting multiple products or enterprise systems that need guarantees about message structure.
What to plan for:
- More complexity in design, setup, and execution, including namespaces, schemas, and bindings.
- Larger messages and more parsing overhead than JSON.
- Harder manual debugging. You rarely build requests by hand.
- Strict validation means small mismatches (a wrong namespace or element order) fail outright.
SOAP in CIC Studio
In CIC Studio, SOAP services are consumed with the Web Service Consumer object, and sending or receiving files as attachments is managed with Business Process tasks that support Web Services. The WSDL concepts on this page (endpoint, operations, schemas, and WS-Security) are the information you'll need to have on hand when configuring them.
Troubleshooting checklist
-
Read the Fault first. Check
faultcode,faultstring, anddetail. They usually name the problem. -
Confirm the SOAP version. The envelope namespace and
Content-Typemust both match the WSDL binding. -
Check
SOAPAction. For SOAP 1.1, it must match the value in the WSDL for the operation, quotes included. - Check namespaces. Wrong or missing namespace URIs on the operation or its child elements are the top cause of "unexpected element" errors.
- Validate the body against the schema. Element names, order, required fields, and data types all count.
- Check you're using the current WSDL. If the provider changed the service, cached copies or generated code may be out of date.
-
Verify the endpoint. Use the
locationin the WSDL'sservicesection. Some WSDLs list a test or internal address that you must replace. - Check security. Expired certificates, wrong credentials, and clock skew can all cause WS-Security failures.
- Reproduce with curl or SoapUI. If it works there, the problem is in how your client builds the message.
Frequently asked questions
Is SOAP outdated?
It isn't growing like REST, but it remains common in enterprise, financial, healthcare, and supply chain integrations, where formal contracts and message-level security are valuable. If a trading partner or system exposes SOAP, you need to be able to consume it.
Does SOAP only work over HTTP?
No. The specification allows other transports, but HTTP is by far the most common, and it's what most tooling and partners assume.
What's the difference between document and RPC style?
It's a binding choice in the WSDL. Document style sends an XML document the schema fully describes. RPC style wraps parameters in an element named after the operation. Document/literal is the usual modern choice, and your client tooling handles the difference if you use the WSDL.
Can I call a SOAP service from JavaScript?
Yes. You can post the XML with fetch, or use a library that reads the WSDL, such as soap for Node.js. As with any language, a WSDL-driven client avoids hand-built XML errors.
Comments
0 comments
Please sign in to leave a comment.