Blog

SAP S/4HANA and OCI PunchOut: Setup, Field Mapping, and Gotchas

How OCI PunchOut differs from cXML, the NEW_ITEM- field set S/4HANA expects back, and the encoding and decimal-separator mistakes that break a working-looking connection.

SupplierForge team

Most PunchOut guides assume cXML, because most non-SAP procurement platforms use it. SAP’s own interface is different: S/4HANA (and its SAP-native predecessors) speaks OCI, the Open Catalog Interface, which solves the same problem as cXML (hand a user off to a supplier’s catalog and get line items back) with a plain HTTP request-and-form-post mechanism instead of an XML document exchange. If your existing PunchOut integration is built around cXML for Ariba, Coupa, or another platform, OCI isn’t a variant of that: it’s a genuinely different protocol with its own field names, its own escaping rules, and its own failure modes. BoreaTech’s primer on what OCI PunchOut is covers the concept; this guide covers the mechanics of getting it working against S/4HANA specifically.

OCI versus cXML in one table

OCI cXML
Wire format Flat key-value parameters, URL-encoded Structured XML documents
Outbound call HTTP GET with parameters in the query string HTTPS POST of a PunchOutSetupRequest document
Return trip Browser auto-submits an HTML form of hidden fields to a hook URL Browser auto-submits a PunchOutOrderMessage document to a BrowserFormPost URL
Item field convention NEW_ITEM-FIELDNAME[n] per line, by index ItemIn / ItemOut XML elements per line
Escaping URL encoding XML entity escaping
Document versioning No formal schema version; behavior varies by SAP release Explicit DTD version referenced in every document
Who uses it SAP systems (S/4HANA, SRM, and predecessors) natively Ariba, Coupa, Jaggaer, and most non-SAP procurement platforms

The practical consequence of this table is that a supplier serving both SAP and non-SAP buyers is really maintaining two separate PunchOut return-trip implementations behind one catalog, not one implementation with a configuration flag.

The OCI field set and the required NEW_ITEM- parameters

When a user finishes shopping and checks out, your catalog builds an HTML form and auto-submits it to the hook URL S/4HANA supplied when it sent the user to you. Every line item’s data goes into that form as hidden fields named with the NEW_ITEM- prefix, the field name, and a bracketed index tying each field to one line. The core set most integrations populate, with the character length the specification allows for each: DESCRIPTION (40), QUANTITY (15), UNIT (3), PRICE (15), PRICEUNIT (5), CURRENCY (5), VENDORMAT (40) for your own part number, and MATGROUP (10) for the buyer’s own material group, with the index incrementing for each additional item. UNIT and CURRENCY are both expected as ISO codes maintained in the buyer’s SRM system: the same “EA” versus “EACH” mismatch that breaks a cXML integration’s unit-of-measure handling breaks an OCI one for the identical reason.

DESCRIPTION’s 40-character limit deserves a sentence on its own: it’s shorter than almost any real product description, SAP truncates silently rather than rejecting the line, and your catalog never sees an error when it happens, which makes it a common, quiet explanation for a buyer reporting that an item’s description is cut off when your own system shows the full text going out. PRICEUNIT is the field most often left out by mistake: it’s the quantity basis the PRICE is quoted against, so a PRICE of 250.00 with a PRICEUNIT of 100 means 250 currency units per 100 items, not per single item; omit it and SAP defaults to 1, which silently produces the wrong extended price the moment a buyer’s own pricing convention assumes a larger basis.

NEW_ITEM-DESCRIPTION[1]=Bracket+assembly%2C+4in%2C+zinc-plated
NEW_ITEM-QUANTITY[1]=4
NEW_ITEM-UNIT[1]=EA
NEW_ITEM-PRICE[1]=113.00
NEW_ITEM-PRICEUNIT[1]=1
NEW_ITEM-CURRENCY[1]=USD
NEW_ITEM-VENDORMAT[1]=BRK-4521
NEW_ITEM-MATGROUP[1]=1105
# UNSPSC/eCl@ss codes belong in EXT_SCHEMA_TYPE + EXT_CATEGORY_ID, not MATGROUP -- see below
NEW_ITEM-EXT_SCHEMA_TYPE[1]=UNSPSC_Vendor1
NEW_ITEM-EXT_CATEGORY_ID[1]=31161701
# LEADTIME (5) is an optional field, not part of the core set above -- confirm with the buyer before relying on it
NEW_ITEM-LEADTIME[1]=5
HOOK_URL=https%3A%2F%2Fs4.example-buyer.com%2Fsap%2Fpunchout%2Freturn

(The classification code in that example is an illustrative placeholder, not a verified real-world UNSPSC lookup.)

The specification is specific about what a line actually requires, and it’s stricter than “more fields are always safer.” Exactly one of NEW_ITEM-DESCRIPTION[n] or NEW_ITEM-MATNR[n] (the buyer’s own material number for that item, also 40 characters) should be filled (the specification’s own wording is that only one of the two should be filled, not both) plus NEW_ITEM-QUANTITY[n], which is required unconditionally on every line regardless of anything else. Five more fields are required only under specific conditions, and each follows the same shape: an identifier becomes mandatory once the line-level detail that references it shows up. UNIT is required if MATNR hasn’t been filled; CURRENCY is required if PRICE has been filled; EXT_SCHEMA_TYPE is required if EXT_CATEGORY_ID or EXT_CATEGORY are used; EXT_QUOTE_ID is required if EXT_QUOTE_ITEM has been used; and CONTRACT is required if CONTRACT_ITEM has been used. “All other fields are optional,” in the specification’s own words, but treat that as a floor, not a target: MATNR can only be populated if the buyer has supplied a mapping from your part numbers to their material master, and most buyers haven’t done that work, which is why DESCRIPTION is what actually carries a requisition in practice, and why UNIT ends up required on nearly every line a supplier sends even though the rule only makes it conditional.

MATGROUP is where a specific, easy-to-ship mistake happens: its definition in the specification is simply the buyer’s “SRM material group for the item”: an internal grouping, not a slot for an external classification scheme at all. UNSPSC and eCl@ss codes have their own designated home in the OCI field set: if a buyer has imported a classification schema into their SRM system under a name like UNSPSC_Vendor1, that name is what goes into NEW_ITEM-EXT_SCHEMA_TYPE[n], paired with the actual code in NEW_ITEM-EXT_CATEGORY_ID[n], not into MATGROUP. A supplier that puts an eight-digit UNSPSC code into MATGROUP isn’t guessing the wrong convention, it’s populating the wrong field, and because the value still looks plausible sitting in a ten-character field, the mistake tends to ship unnoticed rather than get caught in review. What MATGROUP should actually hold is still something to confirm with the buyer before go-live (it’s their own SRM material group, with no meaning independent of how they’ve configured it) but that’s a separate question from whether an external commodity code belongs there at all, which per the specification it doesn’t.

The OCI specification defines a considerably larger set of fields beyond this core, most of them genuinely optional: CONTRACT and EXT_QUOTE_ID are the two conditional exceptions already covered above. One more is worth naming on its own: extended item text goes in NEW_ITEM-LONGTEXT_n:132[], not the NEW_ITEM-FIELDNAME[n] shape every other field uses, and unlike the fixed-length fields covered here it carries no length limit at all: the specification calls out both departures explicitly rather than leaving them to be discovered. Exactly which of these fields a given S/4HANA configuration expects or ignores depends on the release and the catalog interface customizing behind it, so treat your buyer’s own field mapping documentation as the authority on anything beyond what’s covered here rather than assuming every SAP system wants the same optional fields populated.

Two things trip up a first attempt with all of this. First, the index has to be applied consistently across every field belonging to one item: reusing [1] for a second line, or skipping an index, merges or drops data on the SAP side without producing an error your catalog would ever see. Second, this is a form post of plain parameters, not a structured document, so there’s no schema validation catching a missing required field before it reaches S/4HANA; a dropped NEW_ITEM-UNIT or NEW_ITEM-CURRENCY typically surfaces as a confusing value or an outright import failure well downstream of where the actual mistake was made.

Hook URL configuration in S/4HANA

OCI’s round trip starts with S/4HANA sending the user’s browser to your catalog URL as an HTTP GET, with parameters describing the session, including, critically, the hook URL your return-trip form needs to post back to. That hook URL isn’t something you configure once and hard-code; it’s supplied fresh on every outbound request, because it’s generated by S/4HANA’s own catalog interface configuration and can carry a session-specific token. Your catalog’s job is to read that parameter off the inbound request and use it, unmodified, as the form action on the return trip: hard-coding a previously seen hook URL instead of reading it fresh from each session is a common mistake, and it fails silently in testing if you happen to be testing against the same session repeatedly.

How S/4HANA itself is configured to send that initial GET (the catalog registration, the base URL it points at, and which OCI version’s parameter set it emits) is set up on the buyer’s side through S/4HANA’s own catalog or purchasing customizing. Which transaction or Fiori app that configuration lives behind varies enough across releases and procurement models (classic materials management versus newer sourcing and procurement apps) that this guide doesn’t name one; SAP’s own configuration documentation for the release in question is the reliable source for the current path, and confirming with the buyer’s SAP team which one applies to their system beats assuming an identifier that may have moved since. What’s constant regardless of how the buyer’s side is configured is the shape of the request that reaches you: a GET with query parameters including the hook URL, and an expectation that your catalog reads those parameters rather than requiring the user to re-enter session context. For where this fits alongside other procurement-system connections a catalog needs to support, see how integration with buyer systems works across formats.

The return-form post

Once the user has a cart ready, your catalog renders an HTML page containing a form whose hidden inputs are exactly the NEW_ITEM- fields above, with its action attribute set to the hook URL read from the original request and a script that submits it automatically as soon as the page loads: the user typically sees this only as a brief flash before landing back in S/4HANA with their cart populated. Because this is a same-origin-adjacent redirect happening entirely in the browser, the same class of environmental problems that break a cXML PunchOutOrderMessage hand-off apply here too: a browser extension or corporate proxy that blocks auto-submitting forms, or a session that doesn’t survive the redirect back to the SAP domain, breaks the return trip even when every field in the form is correctly named and encoded. Because nothing server-side observes this failure, it’s diagnosed by watching the actual browser session rather than by reading application logs on either side.

The most direct way to verify a return-form post before involving anyone on the SAP side is to inspect it in a browser’s developer tools rather than trust that the page rendered without error. Opening the network panel and watching the actual outgoing POST (its destination URL and its full list of form fields) confirms whether the hook URL was read correctly from the inbound request and whether every expected NEW_ITEM- field made it into the form before the auto-submit script fires, which is a faster way to catch an indexing mistake or a missing field than waiting for a confusing result to show up inside S/4HANA itself.

Encoding and decimal-separator gotchas

Two failure modes account for most working-looking OCI connections that still import bad data:

URL encoding, not XML escaping. Because OCI’s return trip is a form post of plain parameters rather than an XML document, special characters in a description need URL encoding (spaces as + or %20, ampersands as %26) not the XML-entity escaping a cXML integration would apply to the same text. A catalog codebase that serves both cXML and OCI buyers and shares an escaping function between them will corrupt data on whichever protocol it wasn’t originally written for.

Decimal separators. The specification itself constrains the shape of PRICE and QUANTITY before any locale question even comes up: up to 11 digits before the decimal point, 3 after it, and no thousands separator: a value like 1,250.00 is invalid input on its own terms, independent of what happens next. Assuming that constraint is met, how the remaining decimal point actually gets parsed on the SAP side depends on the number-format personalization of the user or system context receiving it: some locales expect a period as the decimal separator, others expect a comma, and a price sent in the wrong format for the receiving configuration doesn’t fail to import so much as import as the wrong number, often by a factor of 100. This is worth explicit verification during setup with a handful of real test prices rather than an assumption that “it’s just a number” carries no formatting risk.

Beyond these two, plain data-quality problems compound faster in OCI than in cXML precisely because there’s no schema validation step to catch them early: a missing CURRENCY, an unmapped MATGROUP, or an inconsistent index across fields all pass silently to S/4HANA and surface later as a confusing line item rather than a rejected request.

Where SupplierForge fits

OCI’s lack of built-in schema validation puts more weight on catalog data being correct before it ever reaches the return-trip form: there’s no document rejection to catch a malformed price or a missing unit of measure the way cXML’s structure sometimes does. That’s largely a data-quality and mapping problem: consistent unit-of-measure and commodity-code values per item, and monitoring that catches formatting drift before it reaches a buyer’s OCI session. SupplierForge’s feature set covers that catalog ingestion, item mapping, and health-monitoring layer, and the OCI return-trip mechanics themselves, alongside cXML and EDI, are handled by the PunchOut Gateway, a separately licensed component built to translate between these formats rather than requiring a bespoke OCI implementation maintained alongside a cXML one.

Frequently asked questions

What's the actual difference between OCI and cXML PunchOut?
OCI carries data as flat, URL-encoded key-value pairs in an HTTP GET and a form POST, while cXML carries the same kind of information as structured XML documents with a defined schema and DTD. OCI is lighter to implement for a simple catalog but has no formal document-versioning story of its own, whereas cXML documents are explicitly versioned and validated against a DTD. Neither is objectively better: SAP environments overwhelmingly favor OCI because it's SAP's own interface, while most other procurement platforms use cXML.
What does NEW_ITEM- mean in an OCI return?
It's the field-name prefix SAP's OCI specification uses for every line item a catalog sends back after checkout, followed by the field name and an index in square brackets: NEW_ITEM-DESCRIPTION[1], NEW_ITEM-PRICE[1], and so on for each item on the cart. The index groups all the fields belonging to the same line together, so item two's fields all carry [2], and a catalog that reuses an index across two different items will silently merge their data on the SAP side.
Why did my item prices import wrong into S/4HANA?
This is almost always a decimal-separator mismatch rather than a math error. OCI's PRICE field is a plain string, and how SAP parses that string depends on the requesting user's personal number-format settings: a user configured for a comma decimal separator can read a price your catalog sent as a period-separated value as a completely different number, often 100 times too large or too small. Formatting PRICE consistently and confirming the receiving system's expected format during setup is the fix, not adjusting the underlying price itself.
Does OCI use XML like cXML does?
No: OCI's outbound call is an HTTP GET with parameters in the query string, and its return trip is an HTML form auto-submitted from the user's browser with the item data as hidden form fields, not an XML document. This matters practically because escaping rules are different: values need URL encoding for OCI, not the XML-entity escaping a cXML PunchOutOrderMessage requires, and applying the wrong one to the wrong protocol produces garbled data that still looks like it worked.
Can one catalog support both OCI and cXML?
Yes, and it's common for a catalog serving both SAP and non-SAP buyers to need to. The two protocols don't share a wire format, but they represent largely the same underlying concept (a hosted catalog session that hands line items back to a buying system) so the same catalog data can be exposed through either the OCI form-field convention or the cXML PunchOutOrderMessage schema depending on which buyer is connecting, provided the integration layer treats them as two separate return-trip implementations rather than trying to share code that assumes one wire format.