Skip to main content
Version: 3.0 (next)

SAP IDoc SAP IDoc Integration Guide

The IDoc (Intermediate Document) is SAP's native asynchronous document interface — the door that on-premise SAP landscapes actually run their machine-to-ERP traffic through. MaestroHub's SAP IDoc connector speaks IDoc-XML over HTTP directly to SAP's inbound handler, and decodes IDocs SAP pushes back to you.

Nothing extra to deploy: no SAP JCo or RFC SDK to license and install, no side-car service to run, no middleware in between. A connection is an HTTPS endpoint and a set of credentials.

Overview​

The SAP IDoc connector supports:

  • A generic IDoc-XML ↔ JSON codec — any message type, any release. The control record, full segment hierarchy, segment occurrence, and field values are preserved verbatim (no trimming, no per-type schema to install)
  • Send IDocs into SAP — POST to SAP's /sap/bc/idoc_xml inbound handler with Basic or OAuth2 client-credentials authentication
  • Control-record automation — the EDI_DC40 envelope (sender/receiver logical systems, message type, direction, IDoc number) is filled from the connection on every send, which removes the single most common cause of SAP rejection
  • Six pre-built Send templates — MBGMCR, WMMBXY, DELVRY, ORDERS, INVOIC, LOIPRO — with multi-line-item support
  • Template-from-sample — turn an IDoc you received into a reusable Send skeleton, so you never author SAP structure from a blank page
  • Bulk send — many IDocs bundled into one IDoc-XML packet, one round-trip
  • Raw IDoc-XML passthrough for power users who already have a document
  • Technical-ack awareness — SAP's plain XML-HTTP handler returns HTTP 200 even when it rejects an IDoc; the connector inspects the response body, not just the status code
  • TLS / mutual TLS with custom CA and client certificates
Check your SAP edition first

This connector targets SAP ECC, S/4HANA on-premise, and S/4HANA private cloud (RISE).

S/4HANA Cloud, public edition does not support transactional IDocs — SAP removed them; only a handful of master-data IDocs remain. If your system is public-cloud, use the REST connector for OData APIs or the SOAP connector for SOAP APIs instead. Nothing in this guide will work against public-cloud S/4HANA.


Before You Start — SAP-Side Prerequisites​

Read this section before creating a connection. Almost every "it doesn't work" with IDoc integration is SAP-side configuration, not connectivity — and most of it must be done by someone with SAP Basis / ALE authorization, not by you in MaestroHub. Budget for that.

A five-minute IDoc vocabulary​

You don't need to be an SAP expert, but these seven terms make the rest of the guide readable.

TermWhat it means
IDocSAP's standard message envelope — a structured document carrying one business event (a goods movement, a delivery, a production order). Think "SAP's version of a JSON message."
IDoc-XMLThe IDoc serialized as XML. These are the exact bytes on the wire.
Basic typeThe IDoc's schema name — MBGMCR03, DELVRY07, LOIPRO01. It is the XML root element.
Message typeThe IDoc's business intent — MBGMCR, DELVRY, LOIPRO. Usually one message type maps to one basic type.
SegmentA section of the document. Segments nest — a header segment contains item segments. Names look like E1MBGMCR, E1BP2017_GM_ITEM_CREATE, or Z1… for customer-specific ones.
Control record (EDI_DC40)The envelope header: who sent it, who receives it, direction, message type. This is the first thing SAP validates. MaestroHub fills it for you.
Logical System (LS)The name SAP uses to identify a partner. The sender LS (SNDPRN) and receiver LS (RCVPRN) in the control record must match a partner profile configured in SAP (transaction WE20). A mismatch is the number-one cause of rejection.

Which direction do you need?​

IDoc integration is inherently bidirectional, and the two directions have completely different SAP-side prerequisites. Work out which you need before asking your Basis team for anything.

DirectionWhat it isMaestroHub sideSAP side
Send (MaestroHub → SAP)You post a document into SAP — a goods movement, a delivery, an order, an invoiceThis connector's send / send_batch functionsSICF service active, service user with IDoc authorization, logical systems, inbound partner profile
Receive (SAP → MaestroHub)SAP pushes a document out to you — a production order, a delivery, an acknowledgmentA Webhook Trigger + this connector's parse functionHTTP destination, XML HTTP port, outbound partner profile
Codec onlyYou already have IDoc-XML from a file, SFTP drop, or EDI partner and just need it as JSON (or vice versa)parse / build / save_template — no SAP endpoint needed at allNothing

Prerequisites for Send (MaestroHub → SAP)​

#WhatSAP transactionWhy it matters
1Activate the inbound IDoc-XML service at default_host → sap → bc → idoc_xmlSICFThe handler is inactive by default. Without this, every send returns 404 / handler not found.
2Confirm the ICM HTTP/HTTPS portSMICM → Goto → ServicesThis is the port that goes into SAP Base URL. HTTPS (usually 443xx) is strongly preferred over plain HTTP.
3Note the SAP client (mandant) — e.g. 100login screenSent as the ?sap-client= query parameter. A wrong client posts into the wrong dataset — or fails.
4Create a technical/service user for MaestroHub, with a non-expiring passwordSU01The connector authenticates as this user on every POST. Use a dedicated service user, never a named person's account.
5Grant that user IDoc inbound authorization — in particular B_ALE_RECV, restricted to the message types you will actually sendPFCG / SU01Without it: HTTP 401/403. B_ALE_RECV is the object that governs inbound ALE/IDoc receipt; scope it to your message types rather than granting broad access.
6Define the logical systems — one for MaestroHub (e.g. MAESTROHUB) and confirm SAP's own (e.g. P01CLNT100)BD54These become SNDPRN and RCVPRN in the control record. They must be names SAP already knows.
7Create the inbound partner profile for the MaestroHub logical system, with an inbound parameter (message type + process code) for each message type you will sendWE20, partner type LSFor inbound IDocs SAP looks up the profile by sender partner + message type. No matching profile → rejection (not ok, or status 56 later). This is the #1 failure.
8(HTTPS with a private CA) Export the SAP server certificateSTRUSTPaste it into the connection's CA Certificate (PEM) field so TLS verification can stay on.
9(optional) Confirm the target module is actually installed—A Basis-only system will accept an MBGMCR technically and then fail to post it, because MM isn't there. Technical acceptance ≠ business posting.

Prerequisites for Receive (SAP → MaestroHub)​

#WhatSAP transactionWhy it matters
1Create an HTTP destination of connection type G (HTTP connection to external server) pointing at your MaestroHub webhook host and path, with the Basic credentials MaestroHub will checkSM59This is how SAP reaches you. The MaestroHub instance must be reachable from the SAP host (firewall, proxy, VPN).
2Create an XML HTTP port referencing that destinationWE21 → Ports → XML HTTPThe XML port type is what makes SAP emit IDoc-XML rather than the flat-file segment format. Pick the wrong port type and you get bytes this connector cannot parse.
3Create the outbound partner profile for the MaestroHub logical system, with an outbound parameter per message type, pointing at that portWE20, partner type LSDecides which documents get pushed to you.
4Create the MaestroHub webhook trigger and copy its URL and auth header into the SM59 destination(MaestroHub)See Receiving IDocs from SAP below.
Receive supports SAP's plain XML-HTTP port only

SAP also offers a SOAP IDoc port (/sap/bc/srt/idoc), which expects a SOAP-envelope acknowledgment in the HTTP response. MaestroHub's webhook responds with JSON, so the SOAP port is not supported for receive today. Configure a plain XML HTTP port in WE21.

(This applies to the receive direction only. On the send direction MaestroHub is the HTTP client, so the constraint doesn't arise — but note the connector's send path targets the plain XML handler, /sap/bc/idoc_xml.)

A successful send means "SAP received the document." Whether SAP actually posted the business document happens later, asynchronously. If you want that answer back in MaestroHub, ask your Basis team to configure the ALE audit return path:

#WhatSAP transaction
1Maintain a distribution model for message type ALEAUD, sender = SAP, receiver = MaestroHubBD64
2Add ALEAUD as an outbound parameter on the MaestroHub partner profile, pointing at the XML HTTP portWE20
3Schedule report RBDSTATE (variant SAP_AUDIT_SEND) periodically to emit the audit IDocsSM36 / SE38

MaestroHub then receives ALEAUD01 IDocs on the same webhook and parses them like any other IDoc — see Closing the loop with ALEAUD.

Prerequisites checklist​

Hand this to whoever owns your SAP system:

SEND  (MaestroHub -> SAP)
[ ] SICF: /default_host/sap/bc/idoc_xml activated
[ ] SMICM: HTTP(S) port confirmed + reachable from MaestroHub
[ ] SAP client (mandant) number: ______
[ ] SU01: service user created, password non-expiring
[ ] PFCG: B_ALE_RECV granted for message types: ______________
[ ] BD54: logical system for MaestroHub created: ______________
[ ] BD54: SAP's own logical system name: ______________
[ ] WE20 (LS, inbound): partner profile + inbound parameter per message type
[ ] STRUST: server certificate exported (if HTTPS with a private CA)

RECEIVE (SAP -> MaestroHub)
[ ] SM59: HTTP destination type G -> MaestroHub webhook URL + Basic creds
[ ] WE21: XML HTTP port referencing that destination
[ ] WE20 (LS, outbound): outbound parameter per message type -> that port
[ ] Network path SAP -> MaestroHub open (firewall / proxy / VPN)

ACKNOWLEDGMENTS (optional)
[ ] BD64: distribution model for ALEAUD
[ ] WE20: ALEAUD as outbound parameter
[ ] RBDSTATE scheduled (variant SAP_AUDIT_SEND)

What "Success" Means — Three Levels of Acknowledgment​

This is the single most important thing to understand before you rely on the connector in production, and it is a property of SAP, not of MaestroHub.

LevelQuestionHow you get itDoes MaestroHub's send give it?
1. TransportDid SAP's HTTP handler accept the POST?HTTP status code✅ Yes, synchronously
2. TechnicalWas the IDoc structurally valid and dispatched into SAP?SAP's technical-ack response body✅ Yes, synchronously
3. ApplicationDid SAP actually post the business document?SAP status 53 (posted) or 51 (error), asynchronously❌ No — arrives later via an ALEAUD IDoc on the receive path
HTTP 200 does not mean "accepted"

SAP's plain XML-HTTP handler answers with an HTML page and returns HTTP 200 even when it rejects the IDoc. The real answer is in the page's <title>:

  • IDoc-XML-inbound ok → accepted
  • IDoc-XML-inbound not ok → rejected, despite the 200

The connector reads the body title, not just the status code, so a rejected IDoc surfaces as a failed function result with a remediation message — never as a silent success. The raw title is returned to you in ackTitle.

A green send is not proof the document posted

Even a genuine IDoc-XML-inbound ok only means SAP received and structurally accepted the IDoc. If SAP's inbound partner profile is set to "Trigger by Background Program" (common at high volume) rather than "Process Immediately", SAP returns ok right away and the document may still fail later with status 51 (application error) or 56 (partner profile not found), visible in transaction WE02/WE05.

If you need end-to-end confirmation, configure the ALEAUD return path. Do not treat a green send node as a posted material document.


Connection Configuration​

Creating a SAP IDoc Connection​

From Connections → New Connection → SAP IDoc. The form is organized into seven tabs: Connection, Authentication, Control Record, TLS, Functions, Scaling, and Health. The last three unlock once the connection is saved.

1. Profile Information​

FieldDefaultDescription
Profile Name-A descriptive name for this connection profile (required, max 100 characters)
Description-Optional description for this SAP IDoc connection
Connection Labels-Key-value pairs to categorize and organize this connection (max 10 labels)

2. SAP Endpoint (Connection tab)​

FieldDefaultDescription
SAP Base URL-The SAP host IDocs are POSTed to, e.g. https://sap-erp.contoso.com:44300. Must start with http:// or https://. Leave blank for a codec-only connection
Inbound IDoc Path/sap/bc/idoc_xmlSAP's inbound IDoc-XML handler path (the SICF service)
SAP Client-SAP client / mandant, e.g. 100. Appended as the sap-client query parameter
Content-Typetext/xml; charset=utf-8Content-Type header for the POST
Request Timeout (seconds)60Maximum time to wait for SAP's technical-ack response (1–300 s)
Verify TLS certificatetrueDisable only for a self-signed test system (insecure)

The final request URL is composed as <Base URL><Inbound IDoc Path>?sap-client=<SAP Client> — for example https://sap-erp.contoso.com:44300/sap/bc/idoc_xml?sap-client=100.

Don't change Content-Type if you use Send Batch

SAP's handler is lenient about Content-Type — text/xml, application/xml, and application/x-sap.idoc are all seen in the wild. But application/x-sap.idoc accepts only one IDoc per request: send a bundled packet with it and SAP takes the first IDoc and ignores the rest, silently. Keep the text/xml; charset=utf-8 default unless you have a specific reason, and especially if you use Send IDoc Batch.

No SAP endpoint yet? Leave Base URL blank

A connection with no Base URL is a valid, supported codec-only connection: parse, build, and save_template all work with no live SAP system. Test Connection succeeds instantly (there is nothing to reach), and send / send_batch fail with a clear message — "base_url is required to send IDocs to SAP (this connection is codec-only)" — rather than silently doing nothing.

This is genuinely useful: IDoc-XML arrives over SFTP, EDI VANs, and file drops as often as it does over HTTP.

3. Authentication (Authentication tab)​

FieldDefaultDescription
Authenticationbasicbasic (username/password — on-premise and ECC) or oauth2_client_credentials (S/4HANA private cloud Communication Arrangements)

(Only displayed when Authentication = "Basic")

FieldDefaultDescription
Username-SAP service user. Needs IDoc inbound authorization — required
Password-Password for Basic authentication (stored encrypted) — required

(Only displayed when Authentication = "OAuth2 Client Credentials")

FieldDefaultDescription
OAuth2 Token URL-Token endpoint — required
Client ID-OAuth2 client identifier — required
Client Secret-OAuth2 client secret (stored encrypted) — required
Scopes-Optional space-separated OAuth2 scopes

The OAuth2 flow is standard client-credentials: MaestroHub form-encodes grant_type=client_credentials plus the client ID, secret and scopes to the token URL, reads access_token from the JSON response, and sends it as Authorization: Bearer <token>.

4. Control Record (Control Record tab) — the headline feature​

These five values are stamped into every outbound IDoc's EDI_DC40 control record on send, so you configure the envelope once instead of on every message. An explicit value inside the IDoc always wins over the connection default — these only fill fields that are empty.

FieldControl-record fieldDescription
Sender Logical System (SNDPRN)SNDPRNWho MaestroHub is, from SAP's point of view. Must match the partner profile configured in SAP (WE20) — the #1 field to get right
Sender Port (SNDPOR)SNDPOROptional sender port name (free text)
Receiver Logical System (RCVPRN)RCVPRNWho SAP is — SAP's own logical system name
Receiver Port (RCVPOR)RCVPOROptional receiver port name (SAP's port)
Default Message Type (MESTYP)MESTYPFallback message type, e.g. MBGMCR, used when the IDoc being sent doesn't carry one

In addition to those five, every structured send automatically sets:

FieldValueWhy
TABNAMEDI_DC40The control-record structure name
IDOCTYPthe IDoc's basic typeIDOCTYP must equal the basic type or SAP rejects the document
DIRECT2Direction 2 = inbound to SAP. A send is always into SAP
SNDPRT / RCVPRTLSPartner type = logical system
DOCNUMa fresh 16-digit numberSee below
Why MaestroHub numbers your IDocs

SAP runs a duplicate check on the sender's IDoc number (EDI_DC40/DOCNUM). A blank DOCNUM is accepted once and every later blank one is refused with "IDoc '' has already been received, therefore reception is refused" (HTTP 409) — so a naive integration works on the first send and fails on the second.

MaestroHub assigns a unique, strictly increasing 16-digit DOCNUM whenever you leave it blank, so repeated sends always pass the duplicate check. If you set DOCNUM yourself, your value is kept untouched.

5. TLS (TLS tab)​

FieldDefaultDescription
CA Certificate (PEM)-Custom CA certificate used to verify the SAP server (export from STRUST)
Client Certificate (PEM)-Client certificate for mutual TLS
Client Private Key (PEM)-Private key matching the client certificate (stored encrypted)

Use the CA field rather than turning Verify TLS certificate off — a private CA is normal in SAP landscapes and does not require disabling verification.

Validation rules​

ConditionRequired fields
Base URL blank (codec-only)None — the connection is valid as-is
Base URL set, Authentication = BasicUsername and Password
Base URL set, Authentication = OAuth2 Client CredentialsToken URL and Client ID and Client Secret
Notes
  • Test Connection checks reachability, not credentials. It performs a bare GET against the Base URL; any completed HTTP round-trip counts as "the host is up", because SAP commonly answers a bare GET with a 4xx. Credential problems surface on the first real send, as HTTP 401/403 with a message pointing at the user's IDoc authorization and the SICF service.
  • Secret masking: password, client secret, and the client private key are masked with ******** when editing an existing connection, and are only updated if you change them.
  • Scaling: IDoc-over-HTTP is stateless per request, so SAP IDoc connections are shared across MaestroHub replicas rather than pinned to one instance.
  • Drain concurrency: when buffered sends are replayed by Store & Forward, the connector caps the drainer at 20 concurrent sends — a deliberate ceiling so a backlog doesn't flood an enterprise SAP host's inbound handler after an outage.
  • Response size: SAP's technical-ack body is read up to 4 MB; anything beyond that is truncated rather than buffered.

Function Builder​

Once the connection is saved, go to its Functions tab and create functions. Five function types are available.

FunctionContacts SAP?Purpose
Parse IDoc-XMLNoIDoc-XML → structured JSON. The receive bridge
Build IDoc-XMLNoTemplate + values / segment JSON / raw → IDoc-XML string
Send IDoc to SAPYesBuild one IDoc and POST it, with control-record automation
Send IDoc Batch to SAPYesBundle many IDocs into one packet and POST once
Save as TemplateNoTurn a parsed IDoc into a reusable Send skeleton

The IDoc JSON shape​

Every function that consumes or produces a structured IDoc uses one shape — the faithful JSON projection of IDoc-XML:

{
"basicType": "MBGMCR03",
"controlRecord": {
"segment": "EDI_DC40",
"fields": { "MESTYP": "MBGMCR", "SNDPRN": "MAESTROHUB" }
},
"segments": [
{
"name": "E1MBGMCR",
"segmentNo": "1",
"fields": {},
"children": [
{ "name": "E1BP2017_GM_CODE", "segmentNo": "1", "fields": { "GM_CODE": "01" } }
]
}
]
}
  • basicType is the XML root element and is required on every hand-authored IDoc.
  • segments is an ordered list; nesting is preserved via children.
  • segmentNo carries SAP's SEGMENT attribute (the occurrence counter); it defaults to 1 when omitted.
  • fields is a name → value map. Values are preserved byte-for-byte — never trimmed, because IDoc fields are fixed-width-derived and space-sensitive. Field order within a segment is not significant (SAP maps by element name), so MaestroHub emits them alphabetically for stable, diff-friendly output.

Parse IDoc-XML​

Function type: sap_idoc.parse · Contacts SAP: no

Converts a raw IDoc-XML string into structured JSON. This is the receive bridge — a webhook trigger receives SAP's POSTed IDoc-XML and this function decodes it — and it also works standalone for IDoc-XML that arrives over a file, SFTP drop, or queue.

FieldTypeRequiredDescription
IDoc-XMLStringYesThe raw IDoc-XML to parse (supports ((parameters))). Usually the webhook body: (($trigger._metadata.raw_body)) or (($trigger.result.raw))

Result — parsing the LOIPRO01 production order below:

<?xml version="1.0" encoding="UTF-8"?>
<LOIPRO01>
<IDOC BEGIN="1">
<EDI_DC40 SEGMENT="1">
<TABNAM>EDI_DC40</TABNAM>
<MANDT>100</MANDT>
<DOCNUM>0000000000098765</DOCNUM>
<STATUS>03</STATUS>
<DIRECT>1</DIRECT>
<IDOCTYP>LOIPRO01</IDOCTYP>
<MESTYP>LOIPRO</MESTYP>
<SNDPRN>P01CLNT100</SNDPRN>
<RCVPRN>MAESTROHUB</RCVPRN>
</EDI_DC40>
<E1AFKOL SEGMENT="1">
<AUFNR>000070004711</AUFNR>
<AUART>PP01</AUART>
<WERKS>1000</WERKS>
<GAMNG>100.000</GAMNG>
<GMEIN>EA</GMEIN>
<E1AFFLL SEGMENT="1">
<E1AFVOL SEGMENT="1">
<VORNR>0010</VORNR>
<LTXA1>Assemble housing</LTXA1>
<ARBPL>WC01</ARBPL>
</E1AFVOL>
</E1AFFLL>
</E1AFKOL>
</IDOC>
</LOIPRO01>

produces:

{
"count": 1,
"basicType": "LOIPRO01",
"messageType": "LOIPRO",
"idocNumber": "0000000000098765",
"idocs": [
{
"basicType": "LOIPRO01",
"controlRecord": {
"segment": "EDI_DC40",
"fields": {
"TABNAM": "EDI_DC40", "MANDT": "100", "DOCNUM": "0000000000098765",
"STATUS": "03", "DIRECT": "1", "IDOCTYP": "LOIPRO01",
"MESTYP": "LOIPRO", "SNDPRN": "P01CLNT100", "RCVPRN": "MAESTROHUB"
}
},
"segments": [
{
"name": "E1AFKOL",
"segmentNo": "1",
"fields": {
"AUFNR": "000070004711", "AUART": "PP01", "WERKS": "1000",
"GAMNG": "100.000", "GMEIN": "EA"
},
"children": [
{
"name": "E1AFFLL",
"segmentNo": "1",
"fields": {},
"children": [
{
"name": "E1AFVOL",
"segmentNo": "1",
"fields": { "VORNR": "0010", "LTXA1": "Assemble housing", "ARBPL": "WC01" }
}
]
}
]
}
]
}
]
}
Result fieldDescription
idocsArray of parsed IDocs — one entry per <IDOC> block, so a bundled packet returns several
countNumber of IDocs parsed
basicTypeBasic type of the first IDoc
messageTypeMESTYP of the first IDoc's control record
idocNumberDOCNUM of the first IDoc — present only when the control record carries one

Malformed input fails cleanly with a permanent error (parse idoc-xml: …) rather than producing a half-parsed document. Customer-specific Z1… segments and fields parse exactly like standard ones — the codec has no per-type schema.


Build IDoc-XML​

Function type: sap_idoc.build · Contacts SAP: no

The inverse of parse: serializes an IDoc into an IDoc-XML string. Composes with Send, or with a file / SFTP / SMB sink when your partner wants a file rather than an HTTP POST.

Choose one of three IDoc Source modes in the function editor; only the active source is saved.

ModeFieldTypeDescription
TemplateTemplateStringName of a built-in template — MBGMCR, WMMBXY, DELVRY, ORDERS, INVOIC, LOIPRO (case-insensitive)
Values (JSON)ObjectValues merged into the template's ((placeholder)) markers
Segment JSONIDocObjectA full IDoc in the IDoc JSON shape. Use ((idoc)) to feed the whole document from a pipeline
Raw XMLRaw IDoc-XMLStringA complete IDoc-XML string, passed through verbatim

When more than one is somehow present, precedence is raw → template → idoc.

Build does not fill the control record

build is deliberately generic — it does not apply control-record automation and does not force a direction, because the same function is used to produce documents for file drops and EDI partners, not only for SAP. Only send and send_batch fill the envelope.

If you build and then post the XML yourself, you own DIRECT, SNDPRN, RCVPRN, and DOCNUM.

Result:

FieldDescription
xmlThe serialized IDoc-XML string
byteCountLength of that string in bytes

Send IDoc to SAP​

Function type: sap_idoc.send · Contacts SAP: yes

Builds one IDoc, applies control-record automation, and POSTs it to SAP's inbound handler. Same three IDoc Source modes as Build.

ModeFieldTypeDescription
TemplateTemplateStringBuilt-in template name
Values (JSON)ObjectValues merged into the template's ((placeholder)) markers
Segment JSONIDocObjectA full IDoc as JSON. Use ((idoc)) to feed it from the pipeline
Raw XMLRaw IDoc-XMLStringA complete IDoc-XML string, POSTed verbatim — no control-record automation
Raw mode bypasses the envelope automation

In Raw XML mode you own the whole document, including DIRECT, SNDPRN, RCVPRN, MESTYP and DOCNUM. In particular the automatic DOCNUM assignment does not apply, so if your raw XML has a blank or repeated DOCNUM, SAP's duplicate check will reject the second send with HTTP 409. Set a unique DOCNUM yourself, or use Template / Segment JSON mode.

Result on success:

{
"httpStatus": 200,
"ackTitle": "IDoc-XML-inbound ok",
"message": "SAP accepted the IDoc (technical ack: \"IDoc-XML-inbound ok\").",
"mode": "structured",
"basicType": "MBGMCR03",
"messageType": "MBGMCR"
}

Result on a 200-but-rejected send:

{
"httpStatus": 200,
"ackTitle": "IDoc-XML-inbound not ok",
"message": "SAP accepted the HTTP request but rejected the IDoc (technical ack: \"IDoc-XML-inbound not ok\"). Check the IDoc-XML structure and the control record (SNDPRN/RCVPRN/MESTYP) against SAP's inbound partner profile (WE20).",
"mode": "structured",
"basicType": "MBGMCR03",
"messageType": "MBGMCR"
}

with the function result marked failed and the outcome classified as permanent.

Result fieldDescription
httpStatusRaw HTTP status returned by SAP
ackTitleSAP's technical-ack <title>, verbatim (empty when the response carries no title)
messageHuman, remediation-bearing explanation
modestructured (built from template or segment JSON) or raw (passthrough)
basicType / messageTypePresent in structured mode only

Outcome classification:

SituationOutcomeRetried by the pipeline?
200 + ok title, or 200 with no titlesuccess—
200 + not ok titlepermanentNo — fix the partner profile or the document
401 / 403permanentNo — fix credentials, B_ALE_RECV, or the SICF service
409permanentNo — duplicate DOCNUM
Other 4xxpermanentNo
5xx, 408, 429transientYes
Network / TLS / timeout failuretransientYes

Send IDoc Batch to SAP​

Function type: sap_idoc.send_batch · Contacts SAP: yes

Bundles several IDocs into a single IDoc-XML packet — one root element with N <IDOC> blocks — and POSTs it in one round-trip. All IDocs must share one basic type.

ModeFieldTypeDescription
Segment JSON arrayIDocsArrayArray of IDocs in the IDoc JSON shape. Use ((idocs)) to feed the array from the pipeline
Raw XMLRaw IDoc-XML batchStringA complete IDoc-XML packet with multiple <IDOC> blocks

Control-record automation is applied per IDoc, in both modes — including Raw, where the packet is parsed, each envelope completed, and the packet re-serialized. (This differs from single-IDoc Raw send, which is byte-for-byte passthrough.) Every bundled IDoc that arrives without a DOCNUM therefore gets its own unique one; any DOCNUM already present is kept.

Result on success:

{
"httpStatus": 200,
"ackTitle": "IDoc-XML-inbound ok",
"message": "SAP accepted the IDoc (technical ack: \"IDoc-XML-inbound ok\").",
"idocCount": 24,
"basicType": "MBGMCR03"
}

Mixing basic types fails before anything is sent, with "a batch must share one basic type".

The ack is for the packet, not per IDoc

SAP's technical ack covers the whole POST. If you need per-document outcomes, either send individually, or configure the ALEAUD return path — ALEAUD reports status per IDoc number.


Save as Template​

Function type: sap_idoc.save_template · Contacts SAP: no

Turns a parsed IDoc into a reusable Send skeleton: the fields you name are replaced with ((MARKER)) placeholders, everything else keeps its literal value. This is the "receive one → save as template → edit values → send" workflow, and it is the fastest route to a correct IDoc for a message type MaestroHub doesn't ship a template for — because the structure is inherited from SAP's own output rather than authored from documentation.

FieldTypeRequiredDescription
IDoc (segment JSON)ObjectYesA parsed IDoc to templatize — typically the output of a Parse function
Placeholder FieldsString (comma-separated)YesField names to replace with markers, e.g. MATERIAL, ENTRY_QNT. Matching is case-insensitive and applies at every depth

Example — given this input IDoc and MATERIAL, ENTRY_QNT:

{
"basicType": "MBGMCR03",
"controlRecord": { "segment": "EDI_DC40", "fields": { "MESTYP": "MBGMCR" } },
"segments": [
{
"name": "E1BP2017_GM_ITEM_CREATE",
"fields": { "MATERIAL": "000000000000100042", "ENTRY_QNT": "5", "PLANT": "1000" }
}
]
}

the result is:

{
"placeholders": ["ENTRY_QNT", "MATERIAL"],
"template": {
"basicType": "MBGMCR03",
"controlRecord": { "segment": "EDI_DC40", "fields": { "MESTYP": "MBGMCR" } },
"segments": [
{
"name": "E1BP2017_GM_ITEM_CREATE",
"fields": {
"ENTRY_QNT": "((ENTRY_QNT))",
"MATERIAL": "((MATERIAL))",
"PLANT": "1000"
}
}
]
}
}

PLANT kept its literal 1000 because it wasn't named. Paste the template object into a Build or Send function's Segment JSON field and fill the markers from the pipeline.


Built-in Templates​

Six templates ship with the connector as a starting point for the flows customers actually run. In the function editor, choose Template mode and click Load. Picking a template shows its basic type, message type, description and header placeholders, and scaffolds the Values map for you — including a starter row for any line-item segment.

TemplateMessage typeBasic typeDirectionWhat it does
MBGMCRMBGMCRMBGMCR03→ SAPPost a goods movement (creates a material document via BAPI_GOODSMVT_CREATE)
WMMBXYWMMBXYWMMBID02→ SAPPost a goods movement through the older MM-IM interface (MB_CREATE_GOODS_MOVEMENT)
DELVRYDELVRYDELVRY07bothInbound/outbound delivery
ORDERSORDERSORDERS05→ SAPSend a purchase order into a supplier's SAP — it becomes a sales order there
INVOICINVOICINVOIC02→ SAPPost an incoming supplier invoice via Logistics Invoice Verification (the MIRO equivalent)
LOIPROLOIPROLOIPRO01← SAPProduction / process order. Primarily a receive document; shipped as a parse/build reference skeleton

Placeholders and repeat groups​

Each template exposes header placeholders (scalar, once per document) and, where it has a repeatable line-item segment, a repeat group driven by an array key.

TemplateHeader placeholdersRepeat key → segmentItem placeholders
MBGMCRDOC_DATE, GM_CODE, PSTNG_DATE, REF_DOC_NOitems → E1BP2017_GM_ITEM_CREATEENTRY_QNT, ENTRY_UOM_ISO, MATERIAL, MOVE_TYPE, PLANT, PO_ITEM, PO_NUMBER, STGE_LOC
WMMBXYTCODEitems → E1MBXYIBWART, EBELN, EBELP, ERFME, ERFMG, LGORT, MATNR, WERKS
DELVRYLFART, VBELN, VSTELitems → E1EDL24LFIMG, MATNR, POSNR, VRKME, WERKS
ORDERSCURRENCY, DELIVERY_DATE, DIST_CHANNEL, DIVISION, DOC_DATE, ORDER_NUMBER, ORDER_TYPE, SALES_ORG, SOLD_TOitems → E1EDP01MATERIAL, ORDER_QTY, POSEX, UOM
INVOICBILL_TO, CURRENCY, DOC_DATE, INVOICE_NUMBER, INVOICING_PARTY, POSTING_DATE, TAX_AMOUNT, TAX_CODE, TAX_RATEitems → E1EDP01ITEM_NET_VALUE, MATERIAL, POSEX, QTY, UOM
LOIPROARBPL, AUART, AUFNR, GAMNG, GMEIN, LTXA1, VORNR, WERKS(none)—

Selecting MBGMCR scaffolds this Values map, with one starter row in the items array:

{
"DOC_DATE": "((DOC_DATE))",
"GM_CODE": "((GM_CODE))",
"PSTNG_DATE": "((PSTNG_DATE))",
"REF_DOC_NO": "((REF_DOC_NO))",
"items": [
{
"ENTRY_QNT": "((ENTRY_QNT))",
"ENTRY_UOM_ISO": "((ENTRY_UOM_ISO))",
"MATERIAL": "((MATERIAL))",
"MOVE_TYPE": "((MOVE_TYPE))",
"PLANT": "((PLANT))",
"PO_ITEM": "((PO_ITEM))",
"PO_NUMBER": "((PO_NUMBER))",
"STGE_LOC": "((STGE_LOC))"
}
]
}

Each ((NAME)) becomes a function parameter you bind on the pipeline node. Replace any of them with a literal to hard-code it — "PLANT": "1000" — or rename the parameter — "MATERIAL": "((sku))".

Switching to a different template replaces the Values map with the new scaffold (you're warned if you had hand-edited it); re-selecting the same template leaves your edits alone.

Line items: fixed rows vs. a dynamic array​

Fixed number of rows — write them out:

{
"PSTNG_DATE": "20260729", "DOC_DATE": "20260729",
"REF_DOC_NO": "DN-88213", "GM_CODE": "01",
"items": [
{ "MATERIAL": "000000000000100042", "PLANT": "1000", "STGE_LOC": "0001",
"MOVE_TYPE": "101", "ENTRY_QNT": "120.000", "ENTRY_UOM_ISO": "PCE",
"PO_NUMBER": "4500000123", "PO_ITEM": "00010" },
{ "MATERIAL": "000000000000100043", "PLANT": "1000", "STGE_LOC": "0001",
"MOVE_TYPE": "101", "ENTRY_QNT": "40.000", "ENTRY_UOM_ISO": "KGM",
"PO_NUMBER": "4500000123", "PO_ITEM": "00020" }
]
}

Variable number of rows — replace the whole array with a single whole-field parameter and feed it an array from upstream:

{
"PSTNG_DATE": "((postingDate))",
"DOC_DATE": "((docDate))",
"REF_DOC_NO": "((deliveryNote))",
"GM_CODE": "01",
"items": "((lineItems))"
}

Bind ((lineItems)) on the node to an upstream array of objects whose keys are the item placeholder names — e.g. {{ $node["Aggregate Pallets"].result.rows }}. One E1BP2017_GM_ITEM_CREATE segment is emitted per element. An empty array emits zero item segments (header segments still render).

Omitting the items key entirely keeps the single prototype segment and fills it from the top-level values — so a flat, single-item Values map still works exactly as before.

Every placeholder must get a value​

A template placeholder with no value is a hard failure, not a silent blank:

template placeholders without values: DOC_DATE, ENTRY_QNT, ENTRY_UOM_ISO, GM_CODE,
MATERIAL, MOVE_TYPE, PLANT, PO_ITEM, PO_NUMBER, REF_DOC_NO, STGE_LOC

Missing item values are reported with their row index — items[1].MOVE_TYPE — so you know which line is short. MaestroHub refuses to send SAP a document containing literal ((MATERIAL)) text.

Write placeholders without spaces

Placeholders must be written tight — ((fieldName)), never (( fieldName )). The template engine matches the name verbatim between the delimiters, so a spaced form references a parameter whose name includes the spaces, never matches, and renders empty. The scaffolder always emits tight markers; keep that convention when editing by hand.


Field Formats SAP Expects​

A structurally valid IDoc can still be rejected hours later with status 51. These are the formatting rules behind most of those rejections. They are SAP-wide conventions; where a rule is system- or configuration-dependent, that is called out.

Field kindFormatExampleNotes
DateYYYYMMDD, 8 digits, no separators20260729Not 29.07.2026, not 2026-07-29. Applies to PSTNG_DATE, DOC_DATE, DATUM, BLDAT, BUDAT…
TimeHHMMSS, 6 digits, 24-hour141530
CurrencyISO 4217, 3 charactersUSD, EUR
Unit of measureThe ISO code, not SAP's internal codePCE, KGM, LTR, MTR, HURUse PCE, not EA/ST; KGM, not KG. SAP converts ISO to its internal code. This is the second most common cause of a status-51
Quantities / amountsDecimal point .; negatives carry a trailing minus120.000, 125.00-Send quantities positive — direction comes from the movement type
Material numberAs SAP stores it000000000000100042 or 100-200Under default configuration a purely numeric material is right-justified and zero-padded to 18; an alphanumeric one is used as-is. Don't hand-pad unless you know the target's setting (transaction OMSL can disable padding entirely)
Purchase order number10 characters0000001234Numeric POs are zero-padded to 10
PO / line item numberZero-filled numeric00010 (PO item, 5), 000010 (sales/delivery item, 6)
Plant / storage location4 characters1000 / 0001

BAPI-mapped segments and the _ISO companion​

Segments named E1BP2017_* map onto a BAPI structure and carry both an internal unit field and an _ISO companion — ENTRY_UOM and ENTRY_UOM_ISO. If both are filled, the internal one wins. For a sender that must work across systems, fill the _ISO field and leave the internal one blank. The shipped MBGMCR template does exactly this: its placeholder is ENTRY_UOM_ISO.

Goods movement: GM_CODE and MOVE_TYPE must agree (MBGMCR)​

GM_CODE selects the transaction context; MOVE_TYPE is the specific action, and it must be permitted under that context.

GM_CODEContextTypical MOVE_TYPE
01Goods receipt for a purchase order101
02Goods receipt for a production order101
03Goods issue261
04Transfer posting311
05Other goods receipt501, 561
06General movement—
07Subcontracting adjustment—

Goods movement: TCODE drives the posting (WMMBXY)​

TCODEMeaning
MB01Goods receipt for a purchase order
MB31Goods receipt for a production order
MB1AGoods issue
MB1BTransfer posting
MB1COther goods receipt
MB11Generic movement

MIGO is not valid here. BWART is the movement type and is the real determinant of the posting; quantity and unit go in ERFMG / ERFME (quantity and unit of entry).

Direction traps in ORDERS and INVOIC​

These two are the templates people most often reach for with the wrong expectation:

  • ORDERS inbound creates a SALES order in the receiving system. The classic EDI-850 shape: you are the buyer, the SAP owner is the seller. It does not create a purchase order in your own SAP. The sold-to customer must already exist as a customer master (external numbers are resolved via table EDPAR), and the order type, sales area, currency and unit must exist in the target client's customizing.
  • INVOIC inbound posts an incoming SUPPLIER invoice via Logistics Invoice Verification, matched against a purchase order and goods receipt. It does not create a billing document — those are outbound, SAP → you. Because it is matched, the PO reference and tax code must reconcile or SAP rejects with status 51. The shipped skeleton is minimal; depending on the target system you may need to add the PO reference (E1EDP02) and totals (E1EDS01) segments.
MaestroHub does not validate against SAP's schema

The connector validates structure (well-formed XML, valid segment and field names, a complete control record) and that every template placeholder got a value. It does not perform SAP-schema validation — mandatory-segment checks, field-length checks, or code-list checks. A structurally valid document can still be rejected by SAP at the application layer. That is why the ALEAUD return path matters for production flows.


Receiving IDocs from SAP (Webhook → Parse)​

Receiving is not a function on this connector. In MaestroHub, receiving an IDoc is a two-part pipeline:

SAP (WE20 outbound → WE21 XML HTTP port → SM59 destination)
│ HTTP POST, body = IDoc-XML
▼
Webhook Trigger ──► SAP Parse IDoc-XML node ──► your logic

Set it up like this:

  1. Create a pipeline whose trigger is a Webhook Trigger.
  2. On the webhook's Authentication tab, add an auth header — for example Authorization: Basic <base64 of user:password> — matching what your SM59 destination will send. Leave Public Webhook off; an unauthenticated endpoint that writes into SAP-adjacent pipelines is not something you want exposed.
  3. On the webhook's Advanced tab, enable Raw Body Mode. IDoc-XML is not JSON, so the trigger wraps it as {"raw": "<the XML>"} — but Raw Body Mode also preserves the exact bytes at $trigger._metadata.raw_body, which is what you want for a byte-exact parse.
  4. Optionally restrict the webhook by IP allowlist to your SAP host's address.
  5. Add a SAP Parse IDoc-XML node and set its xml parameter to (($trigger._metadata.raw_body)). ((($trigger.result.raw)) also works — the trigger wraps a non-JSON body as {"raw": …} under payload — but raw_body is the byte-exact copy.)
  6. Branch on $node["Parse IDoc"].result.messageType to route production orders, deliveries, and acknowledgments to different logic.
  7. Copy the webhook URL and the auth header into your SAP SM59 destination (see the receive prerequisites).
Webhook body size limit

The webhook request body is capped at 1 MiB by default; larger requests are rejected with 413 Payload Too Large. A single IDoc is comfortably inside this. A large bundled IDoc packet from SAP may not be — if you expect big batches, raise the ingress body limit on your deployment, or configure SAP to send packets rather than one giant collection.

Closing the loop with ALEAUD​

Once the ALEAUD return path is configured in SAP, acknowledgments arrive at the same webhook as ordinary IDocs. Parsing one gives you:

{
"count": 1,
"basicType": "ALEAUD01",
"messageType": "ALEAUD",
"idocs": [
{
"basicType": "ALEAUD01",
"controlRecord": {
"segment": "EDI_DC40",
"fields": {
"TABNAM": "EDI_DC40", "DIRECT": "1", "IDOCTYP": "ALEAUD01",
"MESTYP": "ALEAUD", "SNDPRN": "P01CLNT100", "RCVPRN": "MAESTROHUB",
"CREDAT": "20260720", "CRETIM": "101530"
}
},
"segments": [
{ "name": "E1ADHDR", "segmentNo": "1", "fields": { "MESTYP": "MBGMCR" } },
{
"name": "E1STATE",
"segmentNo": "1",
"fields": {
"DOCNUM": "0000000000012345",
"STATUS": "53",
"STATYP": "S",
"STAMID": "B1",
"STAMNO": "888",
"STACOD": "B1888",
"STATXT": "Material document 4900001234 posted",
"STAPA1": "4900001234"
}
},
{
"name": "E1PRTOB",
"segmentNo": "1",
"fields": { "DOCNUM": "0000000000067890", "OBJTYPE": "BUS2017", "OBJKEY": "4900001234" }
}
]
}
]
}

The pieces that matter:

FieldMeaning
E1ADHDR.MESTYPWhich message type this acknowledgment is about (MBGMCR here)
E1STATE.DOCNUMThe IDoc number this status refers to
E1STATE.STATUSSAP's status — 53 posted, 51 application error, 56 partner profile not found, 41 posted in the receiving system
E1STATE.STATXTThe human status text, e.g. "Material document 4900001234 posted"
E1PRTOB.OBJKEYThe business object key SAP created — the material document number, delivery number, or order number

Route on STATUS to raise an alert on 51, and use OBJKEY to write the resulting SAP document number back to your MES, historian, or UNS.


Pipeline Integration​

Every function you build here becomes a node in the Pipeline Designer. See SAP IDoc Nodes for the full node reference, node types, output shapes, and error-handling behavior.

Send and Send Batch are write operations and are eligible for Store & Forward — if SAP is unreachable during a maintenance window, goods movements buffer durably and are delivered when it comes back, instead of being lost. Both operations declare their writes unordered, so by default the buffer delivers them in any order; if IDocs must post in sequence, set Delivery order to In order on the node (see Delivery order).


Real-World Use Cases​

1. Automated goods receipt at the dock​

The problem. A barcode scanner or WMS at the receiving dock confirms a pallet against a purchase order. Someone then keys the same numbers into SAP MIGO, hours later, with typos.

The pipeline.

Webhook / MQTT / OPC UA trigger (scan event)
└─► Set / JavaScript (normalize to the item shape)
└─► SAP Send IDoc (template MBGMCR)

Send function configuration — Template mode, MBGMCR:

{
"PSTNG_DATE": "((postingDate))",
"DOC_DATE": "((docDate))",
"REF_DOC_NO": "((deliveryNote))",
"GM_CODE": "01",
"items": "((lineItems))"
}

Node parameter bindings:

ParameterExpression
postingDate{{ new Date().toISOString().slice(0,10).replace(/-/g,'') }}
docDate{{ $trigger.result.deliveryDate }}
deliveryNote{{ $trigger.result.deliveryNoteNo }}
lineItems{{ $node["Normalize"].result }}

where Normalize produces:

[
{ "MATERIAL": "000000000000100042", "PLANT": "1000", "STGE_LOC": "0001",
"MOVE_TYPE": "101", "ENTRY_QNT": "120.000", "ENTRY_UOM_ISO": "PCE",
"PO_NUMBER": "4500000123", "PO_ITEM": "00010" },
{ "MATERIAL": "000000000000100043", "PLANT": "1000", "STGE_LOC": "0001",
"MOVE_TYPE": "101", "ENTRY_QNT": "40.000", "ENTRY_UOM_ISO": "KGM",
"PO_NUMBER": "4500000123", "PO_ITEM": "00020" }
]

What goes on the wire — control record filled from the connection, one item segment per row:

<?xml version="1.0" encoding="UTF-8"?>
<MBGMCR03>
<IDOC BEGIN="1">
<EDI_DC40 SEGMENT="1">
<DIRECT>2</DIRECT>
<DOCNUM>1785305156223001</DOCNUM>
<IDOCTYP>MBGMCR03</IDOCTYP>
<MESTYP>MBGMCR</MESTYP>
<RCVPRN>P01CLNT100</RCVPRN>
<RCVPRT>LS</RCVPRT>
<SNDPRN>MAESTROHUB</SNDPRN>
<SNDPRT>LS</SNDPRT>
<TABNAM>EDI_DC40</TABNAM>
</EDI_DC40>
<E1MBGMCR SEGMENT="1">
<E1BP2017_GM_HEAD_01 SEGMENT="1">
<DOC_DATE>20260729</DOC_DATE>
<PSTNG_DATE>20260729</PSTNG_DATE>
<REF_DOC_NO>DN-88213</REF_DOC_NO>
</E1BP2017_GM_HEAD_01>
<E1BP2017_GM_CODE SEGMENT="1">
<GM_CODE>01</GM_CODE>
</E1BP2017_GM_CODE>
<E1BP2017_GM_ITEM_CREATE SEGMENT="1">
<ENTRY_QNT>120.000</ENTRY_QNT>
<ENTRY_UOM_ISO>PCE</ENTRY_UOM_ISO>
<MATERIAL>000000000000100042</MATERIAL>
<MOVE_TYPE>101</MOVE_TYPE>
<PLANT>1000</PLANT>
<PO_ITEM>00010</PO_ITEM>
<PO_NUMBER>4500000123</PO_NUMBER>
<STGE_LOC>0001</STGE_LOC>
</E1BP2017_GM_ITEM_CREATE>
<E1BP2017_GM_ITEM_CREATE SEGMENT="1">
<ENTRY_QNT>40.000</ENTRY_QNT>
<ENTRY_UOM_ISO>KGM</ENTRY_UOM_ISO>
<MATERIAL>000000000000100043</MATERIAL>
<MOVE_TYPE>101</MOVE_TYPE>
<PLANT>1000</PLANT>
<PO_ITEM>00020</PO_ITEM>
<PO_NUMBER>4500000123</PO_NUMBER>
<STGE_LOC>0001</STGE_LOC>
</E1BP2017_GM_ITEM_CREATE>
</E1MBGMCR>
</IDOC>
</MBGMCR03>

SAP-side prerequisites for this flow: WE20 inbound profile for MBGMCR with process code BAPI, and a service user with B_ALE_RECV for MBGMCR.


2. Production orders from SAP onto the line​

The problem. Operators read the day's production orders off a printed sheet. The MES doesn't know about order changes until someone tells it.

The pipeline.

Webhook Trigger  (SAP pushes LOIPRO)
└─► SAP Parse IDoc-XML
└─► Switch on messageType
└─► JavaScript (flatten to the MES/UNS shape)
└─► UNS Publish / MQTT Publish / SQL Insert

Parse node: xml = (($trigger._metadata.raw_body)).

Reading the order in the next node — the parsed shape from a LOIPRO01 is shown under Parse IDoc-XML. A JavaScript node flattens it:

const idoc = $node["Parse IDoc"].result.idocs[0];
const header = idoc.segments.find(s => s.name === "E1AFKOL");

// Operations live at E1AFKOL > E1AFFLL > E1AFVOL
const operations = (header.children || [])
.filter(s => s.name === "E1AFFLL")
.flatMap(seq => (seq.children || []).filter(s => s.name === "E1AFVOL"))
.map(op => ({
operation: op.fields.VORNR,
text: op.fields.LTXA1,
workCenter: op.fields.ARBPL,
}));

return [{
orderNumber: header.fields.AUFNR, // "000070004711"
orderType: header.fields.AUART, // "PP01"
plant: header.fields.WERKS, // "1000"
quantity: Number(header.fields.GAMNG), // 100
unit: header.fields.GMEIN, // "EA"
operations, // [{operation:"0010", text:"Assemble housing", workCenter:"WC01"}, …]
}];

SAP-side prerequisites: WE20 outbound profile for LOIPRO on the MaestroHub logical system, pointing at a WE21 XML HTTP port that references an SM59 type-G destination aimed at the webhook URL.


3. Closing the OT → IT loop with acknowledgments​

The problem. Goods movements are sent to SAP and everyone assumes they posted. Nobody notices the ones that failed with status 51 until inventory reconciliation at month end.

The pipeline.

Webhook Trigger  (same endpoint as use case 2)
└─► SAP Parse IDoc-XML
└─► IF messageType == "ALEAUD"
├─ status 53/41 ─► SQL Update (mark the movement confirmed, store OBJKEY)
└─ status 51/56 ─► Slack / Teams / Email alert + SQL Update (mark failed)

Routing expression on the IF node:

{{ $node["Parse IDoc"].result.idocs[0].segments
.find(s => s.name === "E1STATE").fields.STATUS === "53" }}

Extracting the SAP document number for the success branch:

const segs = $node["Parse IDoc"].result.idocs[0].segments;
const state = segs.find(s => s.name === "E1STATE");
const obj = segs.find(s => s.name === "E1PRTOB");

return [{
sentIdocNumber: state.fields.DOCNUM, // correlate back to your send
sapStatus: state.fields.STATUS, // "53"
statusText: state.fields.STATXT, // "Material document 4900001234 posted"
sapDocumentNo: obj ? obj.fields.OBJKEY : null, // "4900001234"
objectType: obj ? obj.fields.OBJTYPE : null, // "BUS2017"
}];

Correlate sentIdocNumber against the DOCNUM your send stamped — record it from the send node's outbound document if you need strict matching, or match on REF_DOC_NO (your own reference, e.g. the delivery note number) which survives the whole round trip.

SAP-side prerequisites: the ALEAUD return path — BD64, WE20 outbound ALEAUD, and a scheduled RBDSTATE.


4. End-of-shift batch posting​

The problem. A line produces hundreds of consumption postings per shift. Posting each one individually creates hundreds of SAP dialog work-process round-trips.

The pipeline.

Schedule Trigger  (end of shift)
└─► SQL Query (movements buffered since the last run)
└─► JavaScript (map rows to IDoc JSON, one per movement)
└─► SAP Send IDoc Batch
└─► SQL Update (mark the batch as submitted)

Mapping rows to IDocs in the JavaScript node — the batch takes an array in the IDoc JSON shape:

const rows = $node["Fetch Movements"].result;

return [ rows.map(r => ({
basicType: "MBGMCR03",
controlRecord: { fields: {} }, // MaestroHub fills the envelope per IDoc
segments: [{
name: "E1MBGMCR",
children: [
{ name: "E1BP2017_GM_HEAD_01",
fields: { PSTNG_DATE: r.posting_date, DOC_DATE: r.doc_date, REF_DOC_NO: r.reference } },
{ name: "E1BP2017_GM_CODE", fields: { GM_CODE: "03" } },
{ name: "E1BP2017_GM_ITEM_CREATE",
fields: {
MATERIAL: r.material, PLANT: r.plant, STGE_LOC: r.storage_location,
MOVE_TYPE: "261", ENTRY_QNT: r.quantity, ENTRY_UOM_ISO: r.uom_iso,
} },
],
}],
})) ];

Send Batch node: Segment JSON array mode, IDocs = ((idocs)), bound to {{ $node["Map to IDocs"].result }}.

All IDocs travel in one <MBGMCR03> root with N <IDOC> blocks, and each gets its own control record and unique DOCNUM. The result reports idocCount.

Turn on Delivery Mode: Always on this node so a SAP maintenance window buffers the shift's postings instead of dropping them.


5. EDI file drop → SAP, with no live connection at parse time​

The problem. A logistics partner drops IDoc-XML delivery files onto an SFTP or SMB share. Someone imports them by hand.

The pipeline.

Schedule Trigger
└─► SMB List / FTP List
└─► SMB Fetch / FTP Fetch (read the file)
└─► SAP Parse IDoc-XML (codec-only connection — no SAP needed)
└─► JavaScript (validate / enrich)
└─► SAP Send IDoc (send-capable connection)
└─► SMB Move (archive the file)

Two connections are useful here: a codec-only connection (no Base URL) doing the parsing, and a send-capable connection doing the posting. The parse half keeps working even when SAP is down, so files are never left unread.

To forward a document unchanged, use the Send function's Raw XML mode with the fetched file content — but remember raw mode is verbatim passthrough, so the file must already carry a correct control record with a unique DOCNUM. To re-envelope it with MaestroHub's automation instead, feed $node["Parse IDoc"].result.idocs[0] into Send's Segment JSON mode.


6. Learning a message type you have no template for​

The problem. You need to send WMMBXY, ZORDER, or some customer-specific Z1… document, and nobody can tell you the exact segment names.

The workflow — no SAP documentation required:

  1. Ask your SAP team to emit one real example of that IDoc, via WE19 (the IDoc test tool) through an XML port, or export one from WE02.
  2. Run Parse IDoc-XML on it. You now have the exact structure SAP produces, field for field, including any customer-specific segments.
  3. Run Save as Template, naming the fields that vary between documents — e.g. MATNR, ERFMG, WERKS.
  4. Paste the resulting template object into a Send function's Segment JSON field and bind the markers to pipeline parameters.

You inherit a correct structure from SAP's own output instead of authoring one from a manual — which is why this is the fastest path to a working send for any message type.


Troubleshooting​

SymptomLikely causeFix
HTTP 404 / handler not found/sap/bc/idoc_xml not activatedSICF → default_host → sap → bc → idoc_xml → Activate Service
HTTP 401 / 403Wrong credentials, or the service user lacks IDoc inbound authorizationCheck the username/password; grant B_ALE_RECV for the message types being sent; confirm the user is active in SU01
HTTP 409 duplicateSender IDoc number already receivedLeave DOCNUM blank so MaestroHub assigns a fresh one. A raw send keeps your DOCNUM verbatim — set a unique one yourself
HTTP 200 but ackTitle is IDoc-XML-inbound not okNo inbound partner profile for this sender + message type, or a structural problemWE20 → partner type LS → the sender logical system → inbound parameter for the message type. Verify SNDPRN/RCVPRN/MESTYP against it
Sends succeed, but nothing appears in SAPTechnical acceptance is not business posting; the document failed laterCheck WE02/WE05 for status 51/56; configure the ALEAUD return path so you see it in MaestroHub
"SAP endpoint unreachable" on Test ConnectionWrong host/port, ICM down, firewallConfirm the port in SMICM; check network path from the MaestroHub host
TLS handshake failurePrivate CA not trustedExport the server cert from STRUST and paste it into CA Certificate (PEM) — don't disable verification
"template placeholders without values: …"The Values map is missing fields the template needsFill every listed name. Item misses are reported as items[N].FIELD
"base_url is required to send IDocs to SAP (this connection is codec-only)"Running send on a connection with a blank Base URLAdd the Base URL and credentials, or run the codec functions instead
"a batch must share one basic type"Mixed basic types in a Send BatchSplit into one batch per basic type
"idoc is missing basicType"Hand-authored segment JSON without the root element nameAdd "basicType": "MBGMCR03" at the top level
"segment X has an unresolved 'items' repeat annotation"A repeat key was left in hand-authored segment JSONrepeat is a template-only annotation. Remove it and write out the item segments explicitly, or switch to Template mode
Only the first IDoc of a batch postsContent-Type is application/x-sap.idoc, which accepts one IDoc per requestSet Content-Type back to text/xml; charset=utf-8
A placeholder renders emptySpaces inside the markerWrite ((fieldName)), never (( fieldName ))
Webhook returns 413IDoc packet exceeds the 1 MiB body capRaise the ingress body limit, or have SAP send smaller packets

Limits and Scope​

Supported systemsSAP ECC, S/4HANA on-premise, S/4HANA private cloud (RISE)
Not supportedS/4HANA Cloud public edition (SAP removed transactional IDocs) — use OData via REST or SOAP APIs via SOAP
TransportIDoc over HTTP/HTTPS only. Classic tRFC IDoc transport is not supported (it requires SAP's RFC SDK)
Send endpointSAP's plain XML-HTTP handler (/sap/bc/idoc_xml). The SOAP IDoc port is not supported
Receive endpointWebhook trigger + parse. SAP's SOAP IDoc port is not supported, because it requires a SOAP-envelope acknowledgment
ValidationStructural only — no SAP-schema, mandatory-segment, field-length, or code-list validation
AcknowledgmentTransport + technical, synchronously. Application posting is asynchronous, via ALEAUD
ConcurrencyConnections are shared across replicas; the Store & Forward drainer is capped at 20 concurrent sends
SizesRequest timeout 1–300 s (default 60 s); SAP ack response read up to 4 MB; inbound webhook body 1 MiB by default
Message typesAny — the codec is generic. Six curated templates ship; customer-specific Z1… segments parse and build normally

Sources​

SAP-side configuration in the prerequisites section follows SAP's documented procedures: