Dynamic XML and SOAP Request Bodies

Build a single HTTP Action whose XML or SOAP body changes at runtime, using Mustache sections, RENDER() templates, or a Script Action.

View as Markdown

Some XML and SOAP APIs accept a large superset of optional elements and decide which ones are required at runtime. Workday Web Services (WWS) operations such as Change_Job_Request and Submit_Payment_Election_Enrollment work this way: nearly every field is optional in the schema, and the tenant enforces per-country requirements server-side. This guide shows how to send a runtime-selected subset of XML elements from one HTTP Action instead of building a separate action for every variant.

For conditional JSON bodies and query parameters, see Optional Query Parameters in HTTP Actions. For parsing XML responses, see Event streaming (SSE) and XML responses.

Key concept: XML bodies are strings

Data Mapper and EVAL() produce JSON. Nothing converts a mapped object into XML for you. To call an XML or SOAP endpoint, you build the request body as a string and send it with the right headers:

  • Content-Type: text/xml (or the content type your API expects)
  • SOAPAction header, if the SOAP service requires it

XML responses are converted to JSON automatically at the HTTP Action layer, so you do not need to parse the response yourself.

Choose a technique

TechniqueBest forWhere the logic lives
Mustache conditional sectionsDropping individual elements when a slot or variable is emptyHTTP Action Body
RENDER() templateA fixed envelope with values computed by DSL (dates, formatting)Data Mapper (input arguments)
Script ActionLoops over runtime-fetched field lists, merging existing data, XML escapingCompound Action script step

You can combine them. For example, a Script Action can build a fragment that a Mustache template inserts into the envelope.

Technique 1: Mustache conditional sections

The HTTP Action Body uses the Mustache templating language. A section tag ({{#variable}}...{{/variable}}) renders its content only when the variable has a value. Wrap each optional element in a section so that the entire element disappears when the variable is empty, null, or not provided.

Template the full superset of fields once. Elements whose variables are not populated do not render.

Abbreviated examples

The Workday envelopes on this page are shortened to show the pattern. Use the element names and structure from your tenant’s WWS schema when building a real request.

<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Body>
<wd:Submit_Payment_Election_Enrollment_Request xmlns:wd="urn:com.workday/bsvc" wd:version="v46.1">
<wd:Payment_Election_Enrollment_Data>
<wd:Worker_Reference>
<wd:ID wd:type="Employee_ID">{{{employee_id}}}</wd:ID>
</wd:Worker_Reference>
{{#iban}}<wd:IBAN>{{{iban}}}</wd:IBAN>{{/iban}}
{{#routing}}<wd:Routing_Transit_or_Institution_Number>{{{routing}}}</wd:Routing_Transit_or_Institution_Number>{{/routing}}
{{#account_number}}<wd:Bank_Account_Number>{{{account_number}}}</wd:Bank_Account_Number>{{/account_number}}
</wd:Payment_Election_Enrollment_Data>
</wd:Submit_Payment_Election_Enrollment_Request>
</env:Body>
</env:Envelope>

If iban is set and routing is empty, the request contains an <wd:IBAN> element and no <wd:Routing_Transit_or_Institution_Number> element.

Guidelines:

  • Use triple braces ({{{variable}}}) for the values. Double braces HTML-escape the variable, which can corrupt values that contain characters like &. See Variable Escaping.
  • Conditional variables must be scalars (string, number, or boolean). If a section variable holds an object or list, Mustache iterates over it instead of treating it as a conditional. To gate an element on an object or list, add a separate boolean variable and use that as the section variable.
  • Set Content-Type: text/xml (and SOAPAction, if required) in the Headers table.

Technique 2: RENDER() template

RENDER() in the Data Mapper substitutes {{variable}} placeholders in a template string with values from args. Because the args are Data Mapper expressions, you can compute values with DSL, such as formatting today’s date, before they are inserted into the envelope. Map the result to the input argument your HTTP Action uses as its body.

The following example builds a Workday Change_Job_Request that updates a contingent worker’s contract end date. effective_date is computed at runtime with $FORMAT_TIME, while contractor_employee_id and new_contract_end_date come from slots.

RENDER():
args:
contractor_employee_id: contractor_employee_id
effective_date: $FORMAT_TIME($TIME(), "%Y-%m-%d", "US/Eastern")
new_contract_end_date: new_contract_end_date
template: |-
<?xml version="1.0" encoding="UTF-8"?> <env:Envelope
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<env:Body>
<wd:Change_Job_Request xmlns:wd="urn:com.workday/bsvc" wd:version="v46.1">
<wd:Business_Process_Parameters>
<wd:Auto_Complete>true</wd:Auto_Complete>
<wd:Run_Now>true</wd:Run_Now>
<wd:Discard_On_Exit_Validation_Error>true</wd:Discard_On_Exit_Validation_Error>
<wd:Comment_Data>
<wd:Comment>Submitted by Moveworks</wd:Comment>
</wd:Comment_Data>
</wd:Business_Process_Parameters>
<wd:Change_Job_Data>
<wd:Worker_Reference>
<wd:ID wd:type="Contingent_Worker_ID">{{contractor_employee_id}}</wd:ID>
</wd:Worker_Reference>
<wd:Effective_Date>{{effective_date}}</wd:Effective_Date>
<wd:Change_Job_Detail_Data>
<wd:Reason_Reference>
<wd:ID wd:type="Change_Job_Subcategory_ID">CHANGE_JOB_SUBCATEGORY-3-12</wd:ID>
</wd:Reason_Reference>
<wd:Contract_End_Date>{{new_contract_end_date}}</wd:Contract_End_Date>
</wd:Change_Job_Detail_Data>
</wd:Change_Job_Data>
</wd:Change_Job_Request>
</env:Body>
</env:Envelope>

To build list-shaped XML (a repeated element for every item in an array), use $CONCAT and $MAP instead. See Examples: Sending an XML Request.

Technique 3: Build the payload in a Script Action

When the set of fields is itself data (for example, a list of field names fetched from an earlier action), or when you need to merge existing values or escape user input, build the envelope in a Script Action inside a Compound Action. The script’s last expression becomes its output_key, and the HTTP Action body is simply {{{payload_xml}}}.

The example below loops over a field map, skips empty values, and escapes user-entered text without importing any libraries, so it runs in APIthon.

steps:
- script:
output_key: payload_xml
input_args:
employee_id: data.employee_id
fields: data.collected_fields
code: |
def esc(value):
return str(value).replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
elements = ""
for tag, value in fields.items():
if value is None or value == "":
continue
elements += "<wd:" + tag + ">" + esc(value) + "</wd:" + tag + ">"
(
'<?xml version="1.0" encoding="UTF-8"?>'
'<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">'
'<env:Body>'
'<wd:Submit_Payment_Election_Enrollment_Request xmlns:wd="urn:com.workday/bsvc" wd:version="v46.1">'
'<wd:Payment_Election_Enrollment_Data>'
'<wd:Worker_Reference><wd:ID wd:type="Employee_ID">' + esc(employee_id) + '</wd:ID></wd:Worker_Reference>'
+ elements +
'</wd:Payment_Election_Enrollment_Data>'
'</wd:Submit_Payment_Election_Enrollment_Request>'
'</env:Body>'
'</env:Envelope>'
)
- action:
action_name: workday_submit_payment_election
output_key: submit_result
input_args:
payload_xml: data.payload_xml

Here data.collected_fields is an object such as {"IBAN": "DE89...", "Bank_Account_Number": ""}. Only keys with a value become elements.

Keep these constraints in mind:

  • Latency: each Script Action adds latency to the plugin. Use Techniques 1 or 2 when the logic is simple.
  • Size limits: APIthon code is limited to 4096 bytes and strings to 4096 characters. A long envelope, especially one that merges an existing record for a full-replace submit, can exceed the string limit. Keep the static parts of the envelope in the HTTP Action Body and generate only the variable fragment in the script. See APIthon: A Special Kind of Python.
  • Imports: APIthon does not allow imports, so escape user-entered values with a replace chain like esc() above. If the Python language option is enabled in your Script Action editor, you can use the standard library instead, as shown in Examples: Sending an XML Request.

Supporting patterns

Fetch which fields apply at runtime

The set of required fields often depends on the user. In the Workday example, the required payment election fields vary by country. Rather than encoding those rules in the plugin:

  1. Add an earlier action that returns the applicable fields for the current user, for example a Workday RaaS report keyed by the user’s country.
  2. Collect the values. Slot definitions are static, so use an object slot and let the assistant guide the user through the fields returned in step 1. See Slots and Resolver Strategies.
  3. Pass the collected map into any of the three techniques above.

Strip null fields from a JSON superset

For JSON APIs, the equivalent of Technique 1 is EVAL() with $FILTER to remove null or empty keys from a superset object. See Omit null or empty keys from a payload.

Let the server validate

If you do not want to maintain field metadata, send the superset payload and relay the server’s validation message to the user. Workday, for example, returns human-readable errors such as “X is required for this country”. This needs no metadata maintenance, but the user only learns about a missing field after a failed submit.