soap_call
Per-record SOAP enrichment with caching and merge strategies.
The soap_call filter sends a SOAP request for each record and merges the
parsed SOAP response back into that record. It supports the same cache and merge
strategy model as http_call, plus SOAP-specific XML body templating,
WS-Security, and MTOM.
Minimal example
filters:
- type: soap_call
endpoint: https://soap.example.com/customers
soapAction: urn:GetCustomer
operation: GetCustomer
body: |
<m:GetCustomer xmlns:m="urn:customers">
<m:CustomerId>{{ record.customerId }}</m:CustomerId>
</m:GetCustomer>
dataField: Envelope.Body.GetCustomerResponse.Customer
mergeStrategy: append
resultKey: customerOptions
| property | type | default | description |
|---|---|---|---|
typerequired | "soap_call" | — | Module type discriminator. Must be `soap_call` for this module. |
id | string | — | Unique identifier within the pipeline. |
name | string | — | Human-readable name. |
description | string | — | — |
enabled | boolean | true | Whether module is active. |
tags | array<string> | — | — |
onError | string | "fail" | Default error action. Case-insensitive; normalized to lowercase by the runtime. |
endpointrequired | string | — | SOAP endpoint URL. Templates are accepted; the final URL is validated at runtime after template resolution. |
soapVersion | string | "1.1" | SOAP envelope and HTTP binding version. 1.11.2 |
soapAction | string | — | SOAP action. SOAP 1.1 sends this as the SOAPAction header; SOAP 1.2 sends it as a Content-Type action parameter. |
operationrequired | string | — | Logical SOAP operation name. |
bodyrequired | string | — | Raw XML body fragment. Templates ({{record.field}}) are XML-escaped at runtime. |
| array<object> | — | Raw SOAP header XML fragments. | |
| union (4 variants) | — | HTTP transport authentication. | |
| object | — | — | |
| object | — | — | |
httpHeaders | map<string, string> | — | Additional HTTP headers. Content-Type and SOAPAction are controlled by the SOAP version. |
timeoutMs | integer | — | Request timeout in milliseconds. When omitted, each module applies its own runtime default. |
mergeStrategy | string | "merge" | How to merge the call response with the input record. merge: deep-merge the response into the record. replace: overwrite the record's top-level keys with the response ones. append: nest the whole response under resultKey. mergereplaceappend |
resultKey | string | — | Record key receiving the whole response with mergeStrategy: append. Required as soon as mergeStrategy is append, for the three call filters alike; ignored by the merge and replace strategies. |
| array<object> | — | List of key configurations for extracting values from records and using them in request metadata. | |
| object | — | — | |
dataField | string | — | Field path to extract from the parsed SOAP response map before merge/replace/append. When omitted, the full parsed SOAP response is used. |
| object | — | — |
Merge strategies
| Strategy | Effect |
|---|---|
merge (default) | Deep-merge response fields into the current record. Nested objects are merged; response values overwrite conflicts. |
replace | Overlay SOAP response fields onto the current record. Existing fields not present in the response are preserved. |
append | Store the SOAP response under resultKey. |
append requires resultKey so the filter has a stable destination for the
SOAP result — omitting it fails cannectors validate.
http_call, soap_call and sql_call share this contract — see
merge contract of the call filters.
Selecting the response with dataField
dataField is a path into the parsed SOAP response
(Envelope.Body.GetCustomerResponse.Customer). Repeated XML elements parse to a
list, and that list reaches the record unchanged under resultKey — iterate it
with loop. A path resolving to a scalar or to
nothing is an error arbitrated by onError, not an enrichment.
This is a behavior change. A list response used to be wrapped under an implicit
items key and a scalar under value — keys no pipeline ever declared. Both
are gone: the list is the list, and a scalar is an error.
http_call follows the same rules — see
selecting the response with dataField.
Caching
Use the cache block to deduplicate SOAP enrichment calls that would resolve to
the same result:
cache:
enabled: true
maxSize: 1000
ttlSeconds: 300
key: "{{ record.customerId }}"If cache.key is omitted, the resolved endpoint and operation shape are used as
the cache identity. Set an explicit per-record key when the SOAP body varies by
record.
The cache also outlives a
run the same way it
does for http_call and sql_call: on a scheduled pipeline, ttlSeconds is how
stale an enrichment may get, not a per-tick setting. SOAP services often back
slow-moving reference data, which tolerates a high TTL — but check that
assumption rather than inherit the default.
Error handling
onError uses the same strategies as other filters:
| Value | Effect |
|---|---|
fail | Stop the pipeline on the first SOAP error. |
skip | Drop the failing record. |
log | Log the SOAP error and keep the record, annotated under _errors. |
With log, the record passes through carrying an
error marker so you can
route on the failure instead of losing it.
A SOAP fault is classified on its fault code, not on the HTTP status: SOAP
1.1 mandates 500 for every fault, so a Client / Sender fault is recorded
as validation / not retryable, and a Server / Receiver fault as server /
retryable. Any other fault code is recorded as non-retryable.
resultKey cannot be _errors — the key is reserved for the runtime's error
markers and the pipeline fails validation.
Examples
name: soap-call-enrichment
version: 1.0.0
description: Poll orders every five minutes, enrich each record with a SOAP lookup, cache responses, and append the SOAP result under a result key.
tags:
- soap
- enrichment
input:
type: httpPolling
schedule: "*/5 * * * *"
endpoint: https://source.example.com/api/orders
dataField: orders
filters:
- type: soap_call
endpoint: https://soap.example.com/customers
soapAction: urn:GetCustomer
operation: GetCustomer
body: |
<m:GetCustomer xmlns:m="urn:customers">
<m:CustomerId>{{record.customerId}}</m:CustomerId>
</m:GetCustomer>
dataField: Envelope.Body.GetCustomerResponse.Customer
mergeStrategy: append
resultKey: customer
cache:
enabled: true
maxSize: 1000
ttlSeconds: 300
key: "{{record.customerId}}"
output:
type: httpRequest
endpoint: https://destination.example.com/api/orders/enriched
method: POST
requestMode: batch
headers:
Content-Type: application/jsonname: call-filters-shared-merge-contract
version: 1.0.0
description: The three call filters share one merge contract - mergeStrategy append nests the whole response under resultKey.
tags:
- http-call
- soap-call
- sql-call
- append
input:
type: httpPolling
schedule: "*/15 * * * *"
endpoint: https://source.example.com/api/orders
dataField: orders
filters:
- type: http_call
endpoint: https://crm.example.com/api/customers
method: GET
keys:
- field: customerId
paramType: query
paramName: id
mergeStrategy: append
resultKey: customer
cache:
enabled: true
maxSize: 1000
ttlSeconds: 300
key: "customer:{{record.customerId}}"
- type: soap_call
endpoint: https://erp.example.com/contracts
soapAction: urn:GetContract
operation: GetContract
body: |
<m:GetContract xmlns:m="urn:contracts">
<m:CustomerId>{{record.customerId}}</m:CustomerId>
</m:GetContract>
dataField: Envelope.Body.GetContractResponse.Contract
mergeStrategy: append
resultKey: contract
cache:
enabled: true
maxSize: 1000
ttlSeconds: 300
key: "contract:{{record.customerId}}"
- type: sql_call
connectionStringRef: ${REFERENCE_DATABASE_URL}
driver: postgres
query: SELECT tier, discount FROM pricing WHERE customer_id = $1
parameters:
- record.customerId
mergeStrategy: append
resultKey: pricing
cache:
enabled: true
maxSize: 1000
ttlSeconds: 300
key: "pricing:{{record.customerId}}"
output:
type: httpRequest
endpoint: https://destination.example.com/api/orders/enriched
method: POST
requestMode: batch
headers:
Content-Type: application/json