SOAP tutorial - Call SOAP services

Call external SOAP APIs from your services - requests, responses and faults, with a U.S. immunization registry as the example.

This tutorial configures an outgoing SOAP connection and implements a service that invokes operations of a remote SOAP endpoint. Requests and responses are Python objects, and no XML, schema or WSDL compilation is required.

The example is a U.S. state immunization registry that implements the CDC interface for immunization information systems, with the connectivityTest and submitSingleMessage operations. The same configuration applies to any other SOAP service.

In this tutorial

  1. Create the outgoing connection
  2. Invoke an operation
  3. Construct complex requests
  4. Read responses
  5. Handle faults
  6. Deploy with enmasse

Remember: you can connect your AI copilot to Zato documentation.

Create the outgoing connection

  1. If you do not have Zato running yet, install it via Docker - it takes under 5 minutes.
  2. Open the web admin dashboard at http://localhost:8183 and navigate to Connections > Outgoing > SOAP.
  3. Click Create a new outgoing SOAP connection and fill in the form - Name: Immunization Registry, Host: https://registry.example.gov, URL path: /iisb/services, SOAP action: urn:cdc:iisb:2011:connectivityTest.
  4. In the SOAP tab, set SOAP version to 1.2.
  5. Click OK.

Every field has a How does it work? link next to it that explains what the field does and when you need it.

Note: Each SOAP action gets its own connection - if the registry exposes connectivityTest and submitSingleMessage, that is two connections differing only in name and SOAP action.

Invoke an operation

Open the Zato IDE at http://localhost:8183, create a new file called registry_api.py, paste this code, and click Deploy:

# -*- coding: utf-8 -*-

# Zato
from zato.common.soap.message import SOAPMessage
from zato.server.service import Service

# ################################################################################################################################
# ################################################################################################################################

class ConnectivityTest(Service):
    """ Checks that the immunization registry is reachable and answering.
    """

    name = 'registry-api.connectivity-test'

    def handle(self) -> 'None':

        # Build the request - one field, echoBack, which the registry echoes in its reply
        request = SOAPMessage()
        request.namespace = 'urn:cdc:iisb:2011'
        request.echoBack = 'Hello registry'

        # Invoke the operation over the connection configured in the Dashboard
        conn = self.soap['Immunization Registry']
        response = conn.invoke('connectivityTest', request)

        # The reply is a SOAPMessage too, read with dot access at every level
        echoed = response.connectivityTestResponse.return_

        self.response.payload = {'echoed': echoed}

That is the whole flow: build a message, invoke an operation, read the reply. The platform wraps your fields in the right envelope for the configured SOAP version, sets the SOAP action, sends the request and parses what comes back.

You can invoke the service from the IDE's invoker panel to watch it run.

Try it: Change echoBack to another value, redeploy and invoke again - the registry echoes back whatever you send.

Construct complex requests

Attributes become elements, in the exact order you assign them, and nesting is just deeper dot access. Submitting an HL7 message to the registry looks like this:

request = SOAPMessage()
request.namespace = 'urn:cdc:iisb:2011'
request.hl7Message = 'MSH|^~\\&|MYAPP|MYFAC|IIS|STATE|20260115||VXU^V04^VXU_V04|123|P|2.5.1'

response = self.soap['Registry Submit'].invoke('submitSingleMessage', request)

A few more constructs you will need sooner or later:

# Nested elements - intermediate levels spring into existence
request.patient.name.family = 'Smith'
request.patient.name.given = 'Jane'

# XML attributes go through brackets
request.slot['name'] = 'creationTime'

# Lists become repeated elements
request.codes.code = ['90715', '90716']

# Python types choose the XML form - datetime becomes xs:dateTime,
# bool becomes true/false, None becomes xsi:nil (from datetime import datetime)
request.submittedOn = datetime.utcnow()
request.isProduction = False

The full set of rules is in the Python message objects reference.

Read responses

Responses are SOAPMessage objects as well, read with the same dot access. The reply's fields are under the operation's response element - submitSingleMessageResponse for submitSingleMessage:

response = conn.invoke('submitSingleMessage', request)

# Scalar fields are plain strings
ack = response.submitSingleMessageResponse.return_

# Repeated elements are lists
for detail in response.submitSingleMessageResponse.details.detail:
    self.logger.info('Detail: %s', detail)

# Attributes read through brackets
status_code = response.submitSingleMessageResponse.status['code']

No namespaces are ever needed to read a reply - dot access finds elements by their local names.

Handle faults

When the endpoint rejects a request, it answers with a SOAP fault - and a fault of either SOAP version is raised as the same SOAPFault exception. The service below is invoked through a REST channel with the HL7 v2 data format, so self.request.input is the HL7 message as text:

# -*- coding: utf-8 -*-

# Zato
from zato.common.soap.common import SOAPFault
from zato.common.soap.message import SOAPMessage
from zato.server.service import Service

# ################################################################################################################################
# ################################################################################################################################

class SubmitWithFallback(Service):

    name = 'registry-api.submit-with-fallback'

    def handle(self) -> 'None':

        request = SOAPMessage()
        request.namespace = 'urn:cdc:iisb:2011'
        request.hl7Message = self.request.input

        try:
            response = self.soap['Registry Submit'].invoke('submitSingleMessage', request)
        except SOAPFault as fault:

            # fault.code is Sender or Receiver, fault.reason is the human-readable text
            # and fault.detail is a dot-accessed message with whatever the endpoint added
            self.logger.warning('Registry rejected the message: %s %s', fault.code, fault.reason)
            self.response.status_code = 502

        else:
            self.response.payload = {'ack': response.submitSingleMessageResponse.return_}
Try it: Point the connection at a URL path that does not exist and invoke the service - the resulting fault contains the endpoint's error text in fault.reason.

Deploy with enmasse

Everything you configured through the Dashboard can also be defined declaratively in YAML and deployed with enmasse. This is the recommended approach for production, CI/CD pipelines, and version-controlled infrastructure.

The connection from this tutorial can be expressed as:

outgoing_soap:

  - name: Immunization Registry
    host: https://registry.example.gov
    url_path: /iisb/services
    soap_action: urn:cdc:iisb:2011:connectivityTest
    soap_version: "1.2"
    timeout: 30

Import it in the Dashboard under System → Config → Import enmasse, or mount the file under /opt/hot-deploy/enmasse/enmasse.yaml inside the container to have it imported on start.

Completed configuration

  • An outgoing SOAP connection holding the endpoint's address, SOAP action and version
  • A service that builds requests as plain Python objects and invokes operations with self.soap['name'].invoke(...)
  • Dot-accessed responses - nested fields, lists, and attributes, no namespaces required
  • Fault handling with a single exception type instead of parsed XML
  • Enmasse deployment - declarative YAML for automated, repeatable provisioning

Continue with SOAP security - the registry example gets real credentials there - or read the SOAP integrations pillar for every building block: WS-Addressing, MTOM attachments, ebXML and more.


Schedule a meaningful demo

Book a demo with an expert who will help you build meaningful systems that match your ambitions

"For me, Zato Source is the only technology partner to help with operational improvements."

- John Adams
Program Manager of Channel Enablement at Keysight