SOAP APIs
SOAP (Simple Object Access Protocol) is an XML-based messaging protocol with a formal contract. It predates REST's dominance and survives in banking, insurance, telecom, government, and enterprise integrations where contract formality and WS- standards* matter.
You will not usually choose SOAP for a new product. You will integrate with one.
The message
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<wsse:Security xmlns:wsse="...">
<wsse:UsernameToken>
<wsse:Username>partner</wsse:Username>
<wsse:Password>...</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
<soap:Body>
<GetAccountBalance xmlns="urn:bank:accounts:v2">
<AccountId>acct_123</AccountId>
</GetAccountBalance>
</soap:Body>
</soap:Envelope>
Three parts:
- Envelope — the message wrapper.
- Header — security, routing, transaction context, correlation.
- Body — the operation payload.
- Fault — structured error inside the body (
soap:Fault).
The contract: WSDL + XSD
A WSDL describes operations, messages, bindings (SOAP 1.1/1.2, HTTP), and endpoints. XSD defines the data types. Tooling generates strongly typed clients from these files.
That formality is the main advantage:
- the contract is machine-readable and versioned;
- validation can happen before business logic;
- code generation is standard across languages;
- enterprise partners can agree on a document rather than a wiki page.
The cost is verbosity and tooling drift: WSDL/XSD versioning, namespace changes, and generator quirks are a real maintenance burden.
Style: document/literal
Use document/literal wrapped (WS-I compliant) for new integrations. Older rpc/encoded services are hard to consume and effectively legacy.
<soap:Body>
<CreateTransfer xmlns="urn:bank:transfers:v1">
<From>acct_1</From>
<To>acct_2</To>
<Amount currency="USD">100.00</Amount>
</CreateTransfer>
</soap:Body>
WS-* standards you may meet
| Standard | Purpose | Practical note |
|---|---|---|
| WS-Security | message-level auth/signature/encryption | UsernameToken, X.509, SAML assertions |
| WS-Policy | declares required security/policy | often ignored by hand-written clients |
| WS-ReliableMessaging | delivery guarantees | rare; most rely on retries/idempotency |
| WS-AtomicTransaction | distributed transactions | heavy, brittle, usually avoided |
| WS-Addressing | message routing/correlation headers | useful for async patterns |
| MTOM | binary attachments | large files; often replaced by object storage |
Use only what the partner requires. WS-AtomicTransaction across organisations is an operational hazard.
Errors
SOAP errors are faults:
<soap:Fault>
<faultcode>soap:Client</faultcode>
<faultstring>Account not found</faultstring>
<detail>
<Error xmlns="urn:bank:errors:v1">
<Code>ACCOUNT_NOT_FOUND</Code>
</Error>
</detail>
</soap:Fault>
ClientvsServerdistinguishes caller vs provider faults.- Put a stable business code in
detail. - HTTP status is often
500even for client faults — parse the fault, not the status.
Security
- Transport: TLS with mutual authentication (mTLS) is common.
- Message: WS-Security signatures/encryption protect end-to-end through intermediaries.
- Identity: SAML assertions or X.509 certificates; UsernameToken only over TLS and ideally not at all.
- Replay protection: timestamps and nonces in the security header.
- XML attacks: disable DTDs/external entities (XXE), limit payload size, validate against schema.
When SOAP is the right choice
- A partner or regulator mandates it.
- The contract must be formally described and versioned (WSDL/XSD).
- WS-Security or WS-* semantics are required end to end.
- Existing enterprise service bus (ESB) infrastructure expects it.
When it is not
- New internal APIs → REST/gRPC.
- Browser/mobile clients → JSON/HTTP.
- High-throughput, low-latency service calls → gRPC.
- Simple public integrations → REST + webhooks.
Integrating without inheriting the pain
- Isolate SOAP behind an adapter with your own domain types.
- Generate the client from WSDL; never hand-parse XML.
- Map faults to your error taxonomy.
- Add timeouts, retries with idempotency, and circuit breakers at the adapter boundary.
- Log the SOAP envelope id/correlation id, not full payloads with secrets.
- Record contract versions and test against partner sandboxes in CI where possible.
- Facade for migration: expose REST/gRPC internally; translate to SOAP at the edge until the partner moves.
interface BalancePort {
getBalance(accountId: AccountId): Promise<Money>;
}
class PartnerSoapAdapter implements BalancePort {
constructor(private readonly client: PartnerClient) {}
async getBalance(accountId: AccountId): Promise<Money> {
try {
const response = await this.client.getAccountBalance({ accountId });
return Money.fromDecimal(response.amount, response.currency);
} catch (error) {
throw mapSoapFault(error);
}
}
}
Testing
- Contract tests against the WSDL/XSD (schema validation).
- Fault-path tests (Client/Server, business codes).
- Sandbox integration tests with recorded fixtures.
- Timeout/retry tests, especially for idempotent operations.
- XML security tests (XXE disabled, signature verification).
Checklist
- The WSDL/XSD version in use is recorded and validated in CI.
- document/literal is used; no rpc/encoded for new work.
- Faults map to stable business codes in your domain.
- mTLS and/or WS-Security requirements are implemented and tested.
- XXE and DTD processing are disabled; payload size is capped.
- Timeouts, retries, and idempotency are handled at the adapter.
- The SOAP client is isolated behind a domain port.
- Correlation ids are logged for partner support.
Next: Modern RPC (gRPC & tRPC) — typed contracts without XML.