SOAP Source

Expose a SOAP web service endpoint to receive XML-based messages from legacy healthcare systems.

Overview

The SOAP source creates an HTTP server that exposes a SOAP web service endpoint. It parses incoming SOAP envelopes, extracts the body payload, and passes it into the channel pipeline for validation and transformation.

Many healthcare systems, particularly older EHR platforms, clinical document repositories, and public health reporting systems, still rely on SOAP-based web services for interoperability. This source enables intu to act as a SOAP service provider, receiving CDA documents, IHE transactions, and other XML-based clinical data.

Note If you provide a WSDL file via wsdl_path, intu will serve it at the endpoint's ?wsdl URL, allowing clients to auto-discover the service contract.

Configuration

yaml
listener:
  type: soap
  soap:
    port: 8443
    wsdl_path: ./schemas/clinical-document.wsdl
    service_name: ClinicalDocumentService
    tls:
      cert_file: /etc/intu/certs/server.crt
      key_file: /etc/intu/certs/server.key
    auth:
      type: basic
      username: ${SOAP_USER}
      password: ${SOAP_PASS}

Properties

port int required
The TCP port the SOAP server listens on.
wsdl_path string optional
Path to a WSDL file that defines the service contract. When provided, the WSDL is served at the endpoint URL with a ?wsdl query parameter.
service_name string optional
The SOAP service name. Used for logging and WSDL service identification. Defaults to the channel id.
tls object optional
TLS configuration for HTTPS. Contains cert_file and key_file.
auth object optional
Authentication for the SOAP endpoint. Supports basic (username/password) and wsse (WS-Security UsernameToken) types.

Auth Type Details

Type Properties Description
basic username, password HTTP Basic authentication applied to the SOAP endpoint.
wsse username, password, nonce (bool) WS-Security UsernameToken profile. When nonce is true, a nonce and timestamp are required in the security header.

Complete Example

A SOAP endpoint that receives CDA (Clinical Document Architecture) documents from an EHR system and transforms them into FHIR DocumentReference resources.

yaml
id: cda-document-receiver
enabled: true

listener:
  type: soap
  soap:
    port: 8443
    wsdl_path: ./schemas/clinical-document.wsdl
    service_name: ClinicalDocumentService
    tls:
      cert_file: /etc/intu/certs/server.crt
      key_file: /etc/intu/certs/server.key
    auth:
      type: basic
      username: ${SOAP_USER}
      password: ${SOAP_PASS}

validator:
  runtime: node
  entrypoint: validator.ts

transformer:
  runtime: node
  entrypoint: transformer.ts

destinations:
  - fhir-document-store
  - audit-log

TypeScript Transformer Example

This transformer processes a SOAP XML payload containing a CDA document and extracts key metadata into a FHIR DocumentReference resource.

typescript
import { Message, TransformResult } from "@intu/sdk";

function extractElement(xml: string, tag: string): string {
  const regex = new RegExp(`<${tag}[^>]*>([^<]*)</${tag}>`);
  const match = xml.match(regex);
  return match ? match[1].trim() : "";
}

function extractAttribute(xml: string, tag: string, attr: string): string {
  const regex = new RegExp(`<${tag}[^>]*${attr}="([^"]*)"[^>]*/?>`);
  const match = xml.match(regex);
  return match ? match[1] : "";
}

export default function transform(msg: Message): TransformResult {
  const soapXml = msg.body as string;

  const bodyMatch = soapXml.match(
    /<(?:\w+:)?Body[^>]*>([\s\S]*?)<\/(?:\w+:)?Body>/
  );
  if (!bodyMatch) {
    return { success: false, error: "Could not extract SOAP Body" };
  }
  const bodyContent = bodyMatch[1];

  const documentId = extractAttribute(bodyContent, "id", "root");
  const title = extractElement(bodyContent, "title");
  const effectiveTime = extractAttribute(bodyContent, "effectiveTime", "value");
  const patientId = extractAttribute(bodyContent, "patientRole", "extension")
    || extractAttribute(bodyContent, "id", "extension");
  const authorName = extractElement(bodyContent, "assignedPerson");

  const typeCode = extractAttribute(bodyContent, "code", "code");
  const typeSystem = extractAttribute(bodyContent, "code", "codeSystem");
  const typeDisplay = extractAttribute(bodyContent, "code", "displayName");

  const documentReference = {
    resourceType: "DocumentReference",
    masterIdentifier: {
      system: "urn:ietf:rfc:3986",
      value: `urn:oid:${documentId}`,
    },
    status: "current",
    type: {
      coding: [
        {
          system: mapOidToUri(typeSystem),
          code: typeCode,
          display: typeDisplay,
        },
      ],
    },
    subject: {
      identifier: {
        system: "urn:oid:2.16.840.1.113883.19.5",
        value: patientId,
      },
    },
    date: formatCdaDate(effectiveTime),
    description: title,
    content: [
      {
        attachment: {
          contentType: "text/xml",
          data: Buffer.from(bodyContent).toString("base64"),
        },
        format: {
          system: "urn:oid:1.3.6.1.4.1.19376.1.2.3",
          code: "urn:ihe:iti:xds:2017:mimeTypeSufficient",
        },
      },
    ],
  };

  return {
    success: true,
    body: JSON.stringify(documentReference),
    contentType: "application/fhir+json",
  };
}

function mapOidToUri(oid: string): string {
  const oidMap: Record<string, string> = {
    "2.16.840.1.113883.6.1": "http://loinc.org",
    "2.16.840.1.113883.6.96": "http://snomed.info/sct",
  };
  return oidMap[oid] || `urn:oid:${oid}`;
}

function formatCdaDate(raw: string): string {
  if (!raw || raw.length < 8) return new Date().toISOString();
  return `${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`;
}