pythonsoap-errorstroubleshooting

Python Zeep SOAP Errors: Fault, TransportError, and ValidationError Troubleshooting Guide

SOAPless Team7 min read

Python's Zeep library is the most popular way to consume SOAP services in Python. It handles WSDL parsing, XML serialization, and transport — but when things go wrong, the error messages can be cryptic. This guide covers every common Zeep error, explains what causes it, and gives you working code to fix it.

Error 1: zeep.exceptions.Fault

A Fault exception means the SOAP server processed your request but returned a SOAP Fault element. This is the SOAP equivalent of an HTTP error response.

from zeep import Client
from zeep.exceptions import Fault

client = Client('https://example.com/service?wsdl')

try:
    result = client.service.GetUser(user_id=999)
except Fault as e:
    print(f"Fault code: {e.code}")
    print(f"Fault message: {e.message}")
    print(f"Fault detail: {e.detail}")

The raw SOAP Fault XML looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <soap:Fault>
      <faultcode>soap:Server</faultcode>
      <faultstring>User not found</faultstring>
      <detail>
        <ErrorCode>USR_404</ErrorCode>
        <ErrorMessage>No user exists with ID 999</ErrorMessage>
      </detail>
    </soap:Fault>
  </soap:Body>
</soap:Envelope>

Common causes and fixes:

  • soap:Client fault code: Your request is malformed. Check required fields, data types, and enum values against the WSDL.
  • soap:Server fault code: The server-side service failed. Check the detail element for specific error codes.
  • Authentication faults: The service rejected your credentials. Verify your WS-Security headers or HTTP auth.
# Inspect the full fault detail as an lxml element
from lxml import etree

try:
    result = client.service.GetUser(user_id=999)
except Fault as e:
    if e.detail is not None:
        print(etree.tostring(e.detail, pretty_print=True).decode())

Error 2: zeep.exceptions.TransportError

A TransportError means Zeep failed at the HTTP level — it never got a valid SOAP response. This usually wraps an HTTP status code.

zeep.exceptions.TransportError: Server returned HTTP status 503 (b'Service Unavailable')

Diagnosing with status codes:

HTTP StatusLikely Cause
401 / 403Missing or invalid authentication
404Wrong endpoint URL
500Server-side crash (check Fault in body)
503Service temporarily unavailable
0 / ConnectionErrorNetwork issue, DNS failure, firewall

Fix: Configure timeouts and retries

from requests import Session
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from zeep.transports import Transport

session = Session()
retry = Retry(
    total=3,
    backoff_factor=0.5,
    status_forcelist=[500, 502, 503, 504],
)
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)

transport = Transport(session=session, timeout=30)
client = Client('https://example.com/service?wsdl', transport=transport)

Fix: Handle SSL certificate issues

session = Session()
# For self-signed certificates in development only
session.verify = '/path/to/custom-ca-bundle.crt'
# Or disable verification (NEVER in production)
# session.verify = False

transport = Transport(session=session)
client = Client('https://example.com/service?wsdl', transport=transport)

Error 3: zeep.exceptions.ValidationError

Zeep validates your input against the WSDL schema before sending the request. A ValidationError means your data doesn't match the expected types or structure.

zeep.exceptions.ValidationError: Missing element AccountNumber (CreateInvoice.AccountNumber)

Common scenarios:

  1. Missing required fields: The WSDL defines minOccurs="1" but you didn't supply the value.
  2. Wrong data types: Passing a string where an integer is expected.
  3. Incorrect complex type structure: Nested objects need to be created with Zeep's type factory.

Fix: Use the type factory for complex types

client = Client('https://example.com/billing?wsdl')

# Inspect what the operation expects
print(client.service.CreateInvoice.__doc__)

# Build complex types properly
InvoiceType = client.get_type('ns0:Invoice')
LineItemType = client.get_type('ns0:LineItem')

invoice = InvoiceType(
    AccountNumber='ACCT-001',
    InvoiceDate='2025-11-19',
    LineItems={
        'LineItem': [
            LineItemType(ProductCode='WIDGET-A', Quantity=10, UnitPrice=29.99),
            LineItemType(ProductCode='WIDGET-B', Quantity=5, UnitPrice=49.99),
        ]
    }
)

result = client.service.CreateInvoice(invoice)

Fix: Disable strict validation when the WSDL is inaccurate

Some WSDL files have incorrect schema definitions. Zeep's strict mode (enabled by default) will reject valid data if the WSDL is wrong.

from zeep import Settings

settings = Settings(strict=False, xml_huge_tree=True)
client = Client('https://example.com/service?wsdl', settings=settings)

Setting strict=False tells Zeep to be lenient about type mismatches. Use this when you know the service accepts data that doesn't match its own WSDL.

Error 4: lxml.etree.XMLSyntaxError

This error means Zeep received something that isn't valid XML. It usually happens during WSDL loading or response parsing.

lxml.etree.XMLSyntaxError: Opening and ending tag mismatch: hr line 3 and body, line 4, column 8

Common causes:

  • The URL returns an HTML page (login page, error page, proxy page) instead of XML.
  • The response is truncated due to a proxy or load balancer cutting the connection.
  • Encoding mismatch: The XML declares UTF-8 but the actual bytes are in a different encoding.

Fix: Inspect the raw response

from zeep.plugins import HistoryPlugin

history = HistoryPlugin()
client = Client('https://example.com/service?wsdl', plugins=[history])

try:
    result = client.service.GetUser(user_id=1)
except Exception:
    # See what the server actually returned
    print("Request:")
    print(etree.tostring(history.last_sent['envelope'], pretty_print=True).decode())
    print("\nResponse:")
    print(etree.tostring(history.last_received['envelope'], pretty_print=True).decode())

If history.last_received is None, the response wasn't XML at all. Use the raw session to see what came back:

session = Session()
transport = Transport(session=session)
client = Client('https://example.com/service?wsdl', transport=transport)

# After a failed call, check the response directly
response = session.post(
    'https://example.com/service',
    data='<test/>',
    headers={'Content-Type': 'text/xml'}
)
print(f"Status: {response.status_code}")
print(f"Content-Type: {response.headers.get('Content-Type')}")
print(f"Body: {response.text[:500]}")

Error 5: WSDL Loading Failures

When Client() fails during initialization, the WSDL itself is the problem.

requests.exceptions.ConnectionError: HTTPSConnectionPool(host='example.com', port=443): Max retries exceeded

Fix: Load WSDL from a local file

If the WSDL URL is unreliable or behind a VPN, download it once and load it locally:

# Download the WSDL
import requests
resp = requests.get('https://example.com/service?wsdl')
with open('service.wsdl', 'w') as f:
    f.write(resp.text)

# Load from local file
client = Client('service.wsdl')

For WSDLs that import other schemas (XSD files), you need to handle the imports:

from zeep.transports import Transport
from requests import Session

session = Session()
session.auth = ('user', 'password')  # If WSDL requires auth
transport = Transport(session=session)

# Zeep will follow wsdl:import and xsd:import references
client = Client('https://example.com/service?wsdl', transport=transport)

Error 6: Namespace Mismatches

Namespace issues cause operations to silently fail or return empty responses. The server doesn't recognize the elements because they're in the wrong XML namespace.

# Check what namespaces Zeep found in the WSDL
from zeep.helpers import serialize_object

# List all available services and ports
for service in client.wsdl.services.values():
    print(f"Service: {service.name}")
    for port_name, port in service.ports.items():
        print(f"  Port: {port_name}")
        for operation in port.binding._operations.values():
            print(f"    Operation: {operation.name}")

Fix: Manually set the SOAPAction or target namespace

from zeep import Client

client = Client('https://example.com/service?wsdl')

# Override the SOAPAction header if the WSDL has it wrong
with client.settings(force_https=False):
    result = client.service.GetUser(
        user_id=1,
        _soapheaders={'SOAPAction': 'http://example.com/GetUser'}
    )

Debugging Best Practice: Logging Plugin

The single most useful debugging technique with Zeep is logging all raw XML traffic:

import logging
from zeep.plugins import HistoryPlugin

# Enable Zeep's built-in logging
logging.getLogger('zeep.transports').setLevel(logging.DEBUG)

# Or use the history plugin for programmatic access
history = HistoryPlugin()
client = Client('https://example.com/service?wsdl', plugins=[history])

try:
    result = client.service.GetUser(user_id=1)
finally:
    for hist in [history.last_sent, history.last_received]:
        if hist and 'envelope' in hist:
            print(etree.tostring(hist['envelope'], pretty_print=True).decode())

This shows you exactly what XML Zeep sends and what the server returns — which is almost always enough to identify the problem.

How SOAPless Helps

Debugging Zeep errors means dealing with WSDL parsing, namespace resolution, XML serialization, and transport configuration — all at once. If you're building an integration that needs to be reliable in production, SOAPless takes a different approach entirely.

Instead of wrestling with Zeep's XML layer, you paste your WSDL URL into SOAPless and get REST JSON endpoints immediately. Your Python code becomes a simple requests.post() call with a JSON body — no XML, no namespace issues, no lxml dependencies. SOAPless handles the SOAP envelope construction, namespace resolution, and XML-to-JSON conversion on the server side, so TransportError, ValidationError, and XMLSyntaxError are problems you never encounter in your application code.

SOAPless also provides a dashboard where you can test operations before writing any code, and it auto-generates OpenAPI specs so you can use standard API tools instead of SOAP-specific ones.