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_xmlinbound handler with Basic or OAuth2 client-credentials authentication - Control-record automation — the
EDI_DC40envelope (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 200even 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
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.
| Term | What it means |
|---|---|
| IDoc | SAP'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-XML | The IDoc serialized as XML. These are the exact bytes on the wire. |
| Basic type | The IDoc's schema name — MBGMCR03, DELVRY07, LOIPRO01. It is the XML root element. |
| Message type | The IDoc's business intent — MBGMCR, DELVRY, LOIPRO. Usually one message type maps to one basic type. |
| Segment | A 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.
| Direction | What it is | MaestroHub side | SAP side |
|---|---|---|---|
| Send (MaestroHub → SAP) | You post a document into SAP — a goods movement, a delivery, an order, an invoice | This connector's send / send_batch functions | SICF 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 acknowledgment | A Webhook Trigger + this connector's parse function | HTTP destination, XML HTTP port, outbound partner profile |
| Codec only | You 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 all | Nothing |
Prerequisites for Send (MaestroHub → SAP)
| # | What | SAP transaction | Why it matters |
|---|---|---|---|
| 1 | Activate the inbound IDoc-XML service at default_host → sap → bc → idoc_xml | SICF | The handler is inactive by default. Without this, every send returns 404 / handler not found. |
| 2 | Confirm the ICM HTTP/HTTPS port | SMICM → Goto → Services | This is the port that goes into SAP Base URL. HTTPS (usually 443xx) is strongly preferred over plain HTTP. |
| 3 | Note the SAP client (mandant) — e.g. 100 | login screen | Sent as the ?sap-client= query parameter. A wrong client posts into the wrong dataset — or fails. |
| 4 | Create a technical/service user for MaestroHub, with a non-expiring password | SU01 | The connector authenticates as this user on every POST. Use a dedicated service user, never a named person's account. |
| 5 | Grant that user IDoc inbound authorization — in particular B_ALE_RECV, restricted to the message types you will actually send | PFCG / SU01 | Without 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. |
| 6 | Define the logical systems — one for MaestroHub (e.g. MAESTROHUB) and confirm SAP's own (e.g. P01CLNT100) | BD54 | These become SNDPRN and RCVPRN in the control record. They must be names SAP already knows. |
| 7 | Create the inbound partner profile for the MaestroHub logical system, with an inbound parameter (message type + process code) for each message type you will send | WE20, partner type LS | For 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 certificate | STRUST | Paste 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)
| # | What | SAP transaction | Why it matters |
|---|---|---|---|
| 1 | Create 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 check | SM59 | This is how SAP reaches you. The MaestroHub instance must be reachable from the SAP host (firewall, proxy, VPN). |
| 2 | Create an XML HTTP port referencing that destination | WE21 → Ports → XML HTTP | The 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. |
| 3 | Create the outbound partner profile for the MaestroHub logical system, with an outbound parameter per message type, pointing at that port | WE20, partner type LS | Decides which documents get pushed to you. |
| 4 | Create the MaestroHub webhook trigger and copy its URL and auth header into the SM59 destination | (MaestroHub) | See Receiving IDocs from SAP below. |
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.)
Optional but recommended: the acknowledgment return path (ALEAUD)
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:
| # | What | SAP transaction |
|---|---|---|
| 1 | Maintain a distribution model for message type ALEAUD, sender = SAP, receiver = MaestroHub | BD64 |
| 2 | Add ALEAUD as an outbound parameter on the MaestroHub partner profile, pointing at the XML HTTP port | WE20 |
| 3 | Schedule report RBDSTATE (variant SAP_AUDIT_SEND) periodically to emit the audit IDocs | SM36 / 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.
| Level | Question | How you get it | Does MaestroHub's send give it? |
|---|---|---|---|
| 1. Transport | Did SAP's HTTP handler accept the POST? | HTTP status code | ✅ Yes, synchronously |
| 2. Technical | Was the IDoc structurally valid and dispatched into SAP? | SAP's technical-ack response body | ✅ Yes, synchronously |
| 3. Application | Did 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 |
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→ acceptedIDoc-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.
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
| Field | Default | Description |
|---|---|---|
| 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)
| Field | Default | Description |
|---|---|---|
| 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_xml | SAP'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-Type | text/xml; charset=utf-8 | Content-Type header for the POST |
| Request Timeout (seconds) | 60 | Maximum time to wait for SAP's technical-ack response (1–300 s) |
| Verify TLS certificate | true | Disable 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.
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.
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)
| Field | Default | Description |
|---|---|---|
| Authentication | basic | basic (username/password — on-premise and ECC) or oauth2_client_credentials (S/4HANA private cloud Communication Arrangements) |
(Only displayed when Authentication = "Basic")
| Field | Default | Description |
|---|---|---|
| Username | - | SAP service user. Needs IDoc inbound authorization — required |
| Password | - | Password for Basic authentication (stored encrypted) — required |
(Only displayed when Authentication = "OAuth2 Client Credentials")
| Field | Default | Description |
|---|---|---|
| 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.
| Field | Control-record field | Description |
|---|---|---|
| Sender Logical System (SNDPRN) | SNDPRN | Who 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) | SNDPOR | Optional sender port name (free text) |
| Receiver Logical System (RCVPRN) | RCVPRN | Who SAP is — SAP's own logical system name |
| Receiver Port (RCVPOR) | RCVPOR | Optional receiver port name (SAP's port) |
| Default Message Type (MESTYP) | MESTYP | Fallback 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:
| Field | Value | Why |
|---|---|---|
TABNAM | EDI_DC40 | The control-record structure name |
IDOCTYP | the IDoc's basic type | IDOCTYP must equal the basic type or SAP rejects the document |
DIRECT | 2 | Direction 2 = inbound to SAP. A send is always into SAP |
SNDPRT / RCVPRT | LS | Partner type = logical system |
DOCNUM | a fresh 16-digit number | See below |
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)
| Field | Default | Description |
|---|---|---|
| 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
| Condition | Required fields |
|---|---|
| Base URL blank (codec-only) | None — the connection is valid as-is |
| Base URL set, Authentication = Basic | Username and Password |
| Base URL set, Authentication = OAuth2 Client Credentials | Token URL and Client ID and Client Secret |
- Test Connection checks reachability, not credentials. It performs a bare
GETagainst the Base URL; any completed HTTP round-trip counts as "the host is up", because SAP commonly answers a bareGETwith a 4xx. Credential problems surface on the first real send, asHTTP 401/403with 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.
| Function | Contacts SAP? | Purpose |
|---|---|---|
| Parse IDoc-XML | No | IDoc-XML → structured JSON. The receive bridge |
| Build IDoc-XML | No | Template + values / segment JSON / raw → IDoc-XML string |
| Send IDoc to SAP | Yes | Build one IDoc and POST it, with control-record automation |
| Send IDoc Batch to SAP | Yes | Bundle many IDocs into one packet and POST once |
| Save as Template | No | Turn 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" } }
]
}
]
}
basicTypeis the XML root element and is required on every hand-authored IDoc.segmentsis an ordered list; nesting is preserved viachildren.segmentNocarries SAP'sSEGMENTattribute (the occurrence counter); it defaults to1when omitted.fieldsis 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.
| Field | Type | Required | Description |
|---|---|---|---|
| IDoc-XML | String | Yes | The 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 field | Description |
|---|---|
idocs | Array of parsed IDocs — one entry per <IDOC> block, so a bundled packet returns several |
count | Number of IDocs parsed |
basicType | Basic type of the first IDoc |
messageType | MESTYP of the first IDoc's control record |
idocNumber | DOCNUM 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.
| Mode | Field | Type | Description |
|---|---|---|---|
| Template | Template | String | Name of a built-in template — MBGMCR, WMMBXY, DELVRY, ORDERS, INVOIC, LOIPRO (case-insensitive) |
| Values (JSON) | Object | Values merged into the template's ((placeholder)) markers | |
| Segment JSON | IDoc | Object | A full IDoc in the IDoc JSON shape. Use ((idoc)) to feed the whole document from a pipeline |
| Raw XML | Raw IDoc-XML | String | A complete IDoc-XML string, passed through verbatim |
When more than one is somehow present, precedence is raw → template → idoc.
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:
| Field | Description |
|---|---|
xml | The serialized IDoc-XML string |
byteCount | Length 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.
| Mode | Field | Type | Description |
|---|---|---|---|
| Template | Template | String | Built-in template name |
| Values (JSON) | Object | Values merged into the template's ((placeholder)) markers | |
| Segment JSON | IDoc | Object | A full IDoc as JSON. Use ((idoc)) to feed it from the pipeline |
| Raw XML | Raw IDoc-XML | String | A complete IDoc-XML string, POSTed verbatim — no control-record 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 field | Description |
|---|---|
httpStatus | Raw HTTP status returned by SAP |
ackTitle | SAP's technical-ack <title>, verbatim (empty when the response carries no title) |
message | Human, remediation-bearing explanation |
mode | structured (built from template or segment JSON) or raw (passthrough) |
basicType / messageType | Present in structured mode only |
Outcome classification:
| Situation | Outcome | Retried by the pipeline? |
|---|---|---|
200 + ok title, or 200 with no title | success | — |
200 + not ok title | permanent | No — fix the partner profile or the document |
401 / 403 | permanent | No — fix credentials, B_ALE_RECV, or the SICF service |
409 | permanent | No — duplicate DOCNUM |
Other 4xx | permanent | No |
5xx, 408, 429 | transient | Yes |
| Network / TLS / timeout failure | transient | Yes |
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.
| Mode | Field | Type | Description |
|---|---|---|---|
| Segment JSON array | IDocs | Array | Array of IDocs in the IDoc JSON shape. Use ((idocs)) to feed the array from the pipeline |
| Raw XML | Raw IDoc-XML batch | String | A 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".
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.
| Field | Type | Required | Description |
|---|---|---|---|
| IDoc (segment JSON) | Object | Yes | A parsed IDoc to templatize — typically the output of a Parse function |
| Placeholder Fields | String (comma-separated) | Yes | Field 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.
| Template | Message type | Basic type | Direction | What it does |
|---|---|---|---|---|
MBGMCR | MBGMCR | MBGMCR03 | → SAP | Post a goods movement (creates a material document via BAPI_GOODSMVT_CREATE) |
WMMBXY | WMMBXY | WMMBID02 | → SAP | Post a goods movement through the older MM-IM interface (MB_CREATE_GOODS_MOVEMENT) |
DELVRY | DELVRY | DELVRY07 | both | Inbound/outbound delivery |
ORDERS | ORDERS | ORDERS05 | → SAP | Send a purchase order into a supplier's SAP — it becomes a sales order there |
INVOIC | INVOIC | INVOIC02 | → SAP | Post an incoming supplier invoice via Logistics Invoice Verification (the MIRO equivalent) |
LOIPRO | LOIPRO | LOIPRO01 | ← SAP | Production / 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.
| Template | Header placeholders | Repeat key → segment | Item placeholders |
|---|---|---|---|
MBGMCR | DOC_DATE, GM_CODE, PSTNG_DATE, REF_DOC_NO | items → E1BP2017_GM_ITEM_CREATE | ENTRY_QNT, ENTRY_UOM_ISO, MATERIAL, MOVE_TYPE, PLANT, PO_ITEM, PO_NUMBER, STGE_LOC |
WMMBXY | TCODE | items → E1MBXYI | BWART, EBELN, EBELP, ERFME, ERFMG, LGORT, MATNR, WERKS |
DELVRY | LFART, VBELN, VSTEL | items → E1EDL24 | LFIMG, MATNR, POSNR, VRKME, WERKS |
ORDERS | CURRENCY, DELIVERY_DATE, DIST_CHANNEL, DIVISION, DOC_DATE, ORDER_NUMBER, ORDER_TYPE, SALES_ORG, SOLD_TO | items → E1EDP01 | MATERIAL, ORDER_QTY, POSEX, UOM |
INVOIC | BILL_TO, CURRENCY, DOC_DATE, INVOICE_NUMBER, INVOICING_PARTY, POSTING_DATE, TAX_AMOUNT, TAX_CODE, TAX_RATE | items → E1EDP01 | ITEM_NET_VALUE, MATERIAL, POSEX, QTY, UOM |
LOIPRO | ARBPL, 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.
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 kind | Format | Example | Notes |
|---|---|---|---|
| Date | YYYYMMDD, 8 digits, no separators | 20260729 | Not 29.07.2026, not 2026-07-29. Applies to PSTNG_DATE, DOC_DATE, DATUM, BLDAT, BUDAT… |
| Time | HHMMSS, 6 digits, 24-hour | 141530 | |
| Currency | ISO 4217, 3 characters | USD, EUR | |
| Unit of measure | The ISO code, not SAP's internal code | PCE, KGM, LTR, MTR, HUR | Use 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 / amounts | Decimal point .; negatives carry a trailing minus | 120.000, 125.00- | Send quantities positive — direction comes from the movement type |
| Material number | As SAP stores it | 000000000000100042 or 100-200 | Under 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 number | 10 characters | 0000001234 | Numeric POs are zero-padded to 10 |
| PO / line item number | Zero-filled numeric | 00010 (PO item, 5), 000010 (sales/delivery item, 6) | |
| Plant / storage location | 4 characters | 1000 / 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_CODE | Context | Typical MOVE_TYPE |
|---|---|---|
01 | Goods receipt for a purchase order | 101 |
02 | Goods receipt for a production order | 101 |
03 | Goods issue | 261 |
04 | Transfer posting | 311 |
05 | Other goods receipt | 501, 561 |
06 | General movement | — |
07 | Subcontracting adjustment | — |
Goods movement: TCODE drives the posting (WMMBXY)
TCODE | Meaning |
|---|---|
MB01 | Goods receipt for a purchase order |
MB31 | Goods receipt for a production order |
MB1A | Goods issue |
MB1B | Transfer posting |
MB1C | Other goods receipt |
MB11 | Generic 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:
ORDERSinbound 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 tableEDPAR), and the order type, sales area, currency and unit must exist in the target client's customizing.INVOICinbound 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 status51. The shipped skeleton is minimal; depending on the target system you may need to add the PO reference (E1EDP02) and totals (E1EDS01) segments.
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:
- Create a pipeline whose trigger is a Webhook Trigger.
- On the webhook's Authentication tab, add an auth header — for example
Authorization: Basic <base64 of user:password>— matching what yourSM59destination will send. Leave Public Webhook off; an unauthenticated endpoint that writes into SAP-adjacent pipelines is not something you want exposed. - 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. - Optionally restrict the webhook by IP allowlist to your SAP host's address.
- Add a SAP Parse IDoc-XML node and set its
xmlparameter to(($trigger._metadata.raw_body)). ((($trigger.result.raw))also works — the trigger wraps a non-JSON body as{"raw": …}underpayload— butraw_bodyis the byte-exact copy.) - Branch on
$node["Parse IDoc"].result.messageTypeto route production orders, deliveries, and acknowledgments to different logic. - Copy the webhook URL and the auth header into your SAP
SM59destination (see the receive prerequisites).
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:
| Field | Meaning |
|---|---|
E1ADHDR.MESTYP | Which message type this acknowledgment is about (MBGMCR here) |
E1STATE.DOCNUM | The IDoc number this status refers to |
E1STATE.STATUS | SAP's status — 53 posted, 51 application error, 56 partner profile not found, 41 posted in the receiving system |
E1STATE.STATXT | The human status text, e.g. "Material document 4900001234 posted" |
E1PRTOB.OBJKEY | The 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:
| Parameter | Expression |
|---|---|
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:
- 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 fromWE02. - Run Parse IDoc-XML on it. You now have the exact structure SAP produces, field for field, including any customer-specific segments.
- Run Save as Template, naming the fields that vary between documents — e.g.
MATNR, ERFMG, WERKS. - Paste the resulting
templateobject 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
| Symptom | Likely cause | Fix |
|---|---|---|
HTTP 404 / handler not found | /sap/bc/idoc_xml not activated | SICF → default_host → sap → bc → idoc_xml → Activate Service |
HTTP 401 / 403 | Wrong credentials, or the service user lacks IDoc inbound authorization | Check the username/password; grant B_ALE_RECV for the message types being sent; confirm the user is active in SU01 |
HTTP 409 duplicate | Sender IDoc number already received | Leave 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 ok | No inbound partner profile for this sender + message type, or a structural problem | WE20 → 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 SAP | Technical acceptance is not business posting; the document failed later | Check WE02/WE05 for status 51/56; configure the ALEAUD return path so you see it in MaestroHub |
| "SAP endpoint unreachable" on Test Connection | Wrong host/port, ICM down, firewall | Confirm the port in SMICM; check network path from the MaestroHub host |
| TLS handshake failure | Private CA not trusted | Export 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 needs | Fill 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 URL | Add the Base URL and credentials, or run the codec functions instead |
| "a batch must share one basic type" | Mixed basic types in a Send Batch | Split into one batch per basic type |
| "idoc is missing basicType" | Hand-authored segment JSON without the root element name | Add "basicType": "MBGMCR03" at the top level |
| "segment X has an unresolved 'items' repeat annotation" | A repeat key was left in hand-authored segment JSON | repeat 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 posts | Content-Type is application/x-sap.idoc, which accepts one IDoc per request | Set Content-Type back to text/xml; charset=utf-8 |
| A placeholder renders empty | Spaces inside the marker | Write ((fieldName)), never (( fieldName )) |
Webhook returns 413 | IDoc packet exceeds the 1 MiB body cap | Raise the ingress body limit, or have SAP send smaller packets |
Limits and Scope
| Supported systems | SAP ECC, S/4HANA on-premise, S/4HANA private cloud (RISE) |
| Not supported | S/4HANA Cloud public edition (SAP removed transactional IDocs) — use OData via REST or SOAP APIs via SOAP |
| Transport | IDoc over HTTP/HTTPS only. Classic tRFC IDoc transport is not supported (it requires SAP's RFC SDK) |
| Send endpoint | SAP's plain XML-HTTP handler (/sap/bc/idoc_xml). The SOAP IDoc port is not supported |
| Receive endpoint | Webhook trigger + parse. SAP's SOAP IDoc port is not supported, because it requires a SOAP-envelope acknowledgment |
| Validation | Structural only — no SAP-schema, mandatory-segment, field-length, or code-list validation |
| Acknowledgment | Transport + technical, synchronously. Application posting is asynchronous, via ALEAUD |
| Concurrency | Connections are shared across replicas; the Store & Forward drainer is capped at 20 concurrent sends |
| Sizes | Request timeout 1–300 s (default 60 s); SAP ack response read up to 4 MB; inbound webhook body 1 MiB by default |
| Message types | Any — 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:
- XML-HTTP: Activating/Deactivating Inbound Processing — SAP Help Portal (
SICFactivation ofidoc_xml) - Monitoring the Status of Inbound IDocs Using ALE Audit — SAP Help Portal (ALEAUD,
BD64,RBDSTATE) - SAP Note 1487606 — IDoc inbound processing via HTTP/SOAP (ICF service exposure and IDoc authorization)
- Post IDoc to SAP ERP over HTTP from any application — SAP Community (inbound XML-HTTP endpoint and
WE20partner profiles) - Sending IDoc as XML (Outbound API) — SAP Community (
SM59type G →WE21XML HTTP port →WE20outbound profile)