Why one ampersand breaks the whole SOAP request
A Create Text Template writes each {{ }} value into the message exactly as it is, so a cost center called R&D or a comment containing < produces a SOAP body that is not well-formed XML, and Workday rejects the whole request. The fix is to escape every free-text value as you insert it, with escapeXml(), or to read it with xmlString() when it comes from another XML document, which returns it already escaped.
Workday's documentation for text templates does not describe any automatic escaping of inserted values, and Workday's own samples escape explicitly. The Employee Relations Incident Management sample builds an XML document from a REST response and calls escapeXml() on the title, type and action text, while leaving IDs and dates as they are. Follow the same rule: anything a person typed, or that comes from a system you do not control, gets escaped.
Which characters break XML and which do not
In XML element text, & and < are the two characters that must always be escaped (as & and <). An unescaped & starts what the parser reads as an entity reference, so R&D Lab fails at the &. Inside an attribute value, the quote character that delimits the attribute must be escaped too. These are rules of XML itself, so they apply to every Workday SOAP operation.
Accented and other non-ASCII letters are legal XML text and do not need escaping. Names like Jürgen Müller or Ana María López are not an escaping problem. If the receiving field or a file produced from it needs a restricted character set, that is a different fix, covered below.
Control characters are the third category. XML 1.0 does not allow most characters below U+0020, other than tab, line feed and carriage return, even when escaped. Escaping does not make them legal, so they have to be removed.
Escape free-text values with escapeXml()
escapeXml() is a String member function that escapes a string so it can go into a valid XML document. Apply it to the value, not to the template:
<bsvc:Change_Business_Title_Request xmlns:bsvc="urn:com.workday/bsvc">
<bsvc:Change_Business_Title_Business_Process_Data>
<bsvc:Worker_Reference>
<bsvc:ID bsvc:type="Employee_ID">{{data.readRow.employeeId}}</bsvc:ID>
</bsvc:Worker_Reference>
<bsvc:Change_Business_Title_Data>
<bsvc:Event_Effective_Date>{{data.readRow.effectiveDate}}</bsvc:Event_Effective_Date>
<bsvc:Proposed_Business_Title>{{data.readRow.title.escapeXml()}}</bsvc:Proposed_Business_Title>
</bsvc:Change_Business_Title_Data>
</bsvc:Change_Business_Title_Business_Process_Data>
</bsvc:Change_Business_Title_Request>
With JSON input, chain it after the accessor, the way Workday's sample does: {{this.stringAtJsonPathOrEmptyString("$.title").escapeXml()}}. Inside an {{#each}} loop, escape each item's text fields the same way.
Never wrap the whole message in escapeXml(). That escapes your own tags as well, and Workday receives text instead of a request. If you ever need the reverse, unescapeXml() turns escaped text back into plain characters.
Values copied from another XML response are already escaped with xmlString()
When the value comes from an XML document, such as a Get operation's response or an inbound file, choose the accessor by what you will do with it:
stringAtXPath(xpath)returns the plain text value. Use it for comparisons and logic, and escape it before writing it into XML.xmlString(xpath),xmlStringWithDefault(xpath, default)andxmlStrings(xpath)return the value escaped for inclusion in XML. Use them when the value goes straight into the next request.
Do not combine the two. xmlString(...).escapeXml() escapes twice, and the record ends up holding the literal text R&D. The documentation for xmlStringWithDefault also notes that the default is used as supplied, so a default containing & needs escaping by you.
Double escaping is the reason a value can look right in the request yet wrong on the saved record, so check the record, not only the call status.
Replace or remove characters the receiver cannot take
When the target field, or a file Workday produces from it, needs plain ASCII, or text contains control characters, use Orchestrate's character mapping functions. They work on String, Text, Csv, Json and Xml values and return the same type.
replaceCharactersUsingMapping(mapping)swaps the characters you list (for exampleütou) and leaves everything else.includeCharactersUsingList(inclusionList, defaultReplacement)keeps only characters on the list and replaces the rest with your default. With an empty string as the default it deletes them, which is how to drop control characters or anything outside an allowed set. Applied to a whole Xml, Json or Csv document, Workday documents that the format's own control characters are returned unchanged, so the structure survives.replaceAndIncludeCharacters(mapping, defaultReplacement)does both: mapped characters are swapped and any others become the default.
The mapping and the list come from the Mapping global functions. mapping.createCharacterReplacementMapping(csv) reads a two-column CSV of character and replacement; an overload takes a second StringMap whose entries override the CSV. mapping.createCharacterInclusionList(csv) reads a one-column CSV of allowed characters, with an overload that adds a StringList. Build them once in a Create Values step before the loop and reference them from the template:
<bsvc:Proposed_Business_Title>{{data.readRow.title.includeCharactersUsingList(data.charRules.allowed, "").escapeXml()}}</bsvc:Proposed_Business_Title>
Filter first, then escape. Escaping first would turn & into &, and the inclusion list would then have to allow &, ; and the entity letters for the escaped text to survive.
Check the envelope while you are in the body
The SOAP envelope is a separate source of invalid requests. Send Paged Workday SOAP Call adds the <soapenv:Envelope> itself, and Workday states that adding one by hand makes the request invalid, so its Create Body holds only the operation element. With Send Workday API Request, Workday's samples build the operation in a Create Text Template, then send it as a POST to a path such as /soap/v42.2/Staffing with the body set to data.SOAPRequest.message.wrapSoapV11().
Prove the fix with a value that used to fail
- Pick or create a source row whose text contains
&,<, a quote and an accented letter, for example a proposed business title ofR&D Lead <EMEA> "Müller". - In Orchestration Builder, debug the Create Text Template step (Debug Step, then Debug Current Step) and read its
messagein Debug Outputs. You should see&and<, exactly one level deep, and the accented letter as itself. - Run the SOAP step and test its response with
isSoapFault(); if it is true,soapFaultCode()returns the fault code to log. When the request references other objects, set Include Invalid Reference Responses on Send Paged Workday SOAP Call and check fragments withisWorkdayInvalidReference(), so a bad ID is not mistaken for a character problem. - Read the worker's business title back (for example with Get_Workers) and compare the stored text with the source. It should match the original characters, not their escaped form.
If the call still fails with escaping in place, look at the rest of the body: the Create Text Template guide covers XPath, loops and quoting, and Orchestrate errors covers run and step failures.
Sources checked
- Reference: String Member Functions
- Reference: Xml Member Functions
- Reference: Data Member Functions
- Concept: Character Mapping
- Reference: Mapping Global Functions
- Concept: Text Templates
- Reference: Send Paged Workday SOAP Call
- Reference: Send Workday API Request
- Debug Orchestrations
- Workday App Catalog sample: Employee Relations Incident Management
- Workday App Catalog sample: Update Service Date Boomerang