PunchOut
WooCommerce PunchOut
WooCommerce has no PunchOut of its own and no contract prices per customer in its core: one regular and one sale price per product, no endpoint for a setup request and no purchase order payment method. The sign-in, the prices per buyer, the cart transfer and the order intake are built as a plugin and an integration around the store. We build them on your existing WooCommerce store and run the connection.
What moves between the procurement system and WooCommerce
A PunchOut connection on WooCommerce has the same flows as on any store. WooCommerce keeps the products, the stock and the orders; the PunchOut documents run in the integration.
| Flow | Direction | Where it runs |
|---|---|---|
| Setup request and response | procurement system to the integration | outside WordPress, in the integration |
| Sign-in as the buyer | integration to the storefront | a plugin route that sets the WordPress auth cookie |
| Shopping at contract prices | in the storefront | the plugin sets the price per cart line |
| Cart transfer | storefront to the procurement system, through the browser | a checkout replacement and the integration |
| Purchase order | procurement system to the integration to WooCommerce | REST API v3, order with the PO number as meta data |
| Confirmation, ship notice, invoice | WooCommerce or your ERP to the procurement system | the integration, as cXML |
Facts on this page were checked against the WooCommerce and WordPress developer documentation and source on October 3, 2026. The documents themselves are on cXML PunchOut and OCI PunchOut.
What WooCommerce offers and what is built around it
| Need | What WooCommerce has | What is built |
|---|---|---|
| Endpoint for a setup request | nothing | the integration receives and answers it |
| Sign-in without a form | wp_set_auth_cookie() in WordPress |
a plugin route that signs in the user of a one-time start URL |
| Prices per buying organization | regular_price and sale_price per product |
contract price per cart line, from the integration's price table |
| Cart data | Store API GET /wp-json/wc/store/v1/cart, prices in minor units |
a checkout replacement that sends the cart to the integration |
| Cart in cXML or OCI | nothing | the integration builds the PunchOutOrderMessage or the NEW_ITEM form |
| Order with a PO number | REST API v3 orders with meta_data; no PO payment method |
the integration maps the OrderRequest to it |
Signing the buyer in
WordPress signs a user in with wp_set_auth_cookie(). Called with $remember false, the cookie is a browser session cookie, and WordPress still ends the login after two days or when the browser closes, whichever comes first. With $remember true it lasts 14 days by default. A PunchOut sign-in uses the first: the session ends with the browser, and a link copied from it opens nothing a week later.
The plugin adds one route. The setup request for customer 10432 arrives at the integration, which stores the session with its return URL and BuyerCookie and answers with a start URL that carries a one-time identifier. The route checks the identifier, calls wp_set_auth_cookie() for the WordPress user linked to that session and opens the catalog.
Which user is linked matters more on WooCommerce than elsewhere. A signed-in user's cart is saved in the user meta _woocommerce_persistent_cart_ and follows that user to every device. Ten buyers at one customer on one shared WordPress account fill one cart between them. The integration links each PunchOut session to its own user, created from the UserEmail or Contact in the setup request when the buyer sends one.
WooCommerce's own session lasts 7 days for a signed-in user and 2 days for a guest, set in WC_Session_Handler. That is longer than SAP Ariba's six-hour PunchOut session, so the cart does not disappear on the store side during a session.
Prices per buying organization
WooCommerce core has one regular_price and one sale_price per product and no customer groups. A contract price per buyer comes from outside the product. The integration keeps a price table per buying organization, filled from the ERP, and the plugin sets that price on each cart line of a PunchOut session:
| Item | List price in WooCommerce | Customer 10432 | Source |
|---|---|---|---|
| 10482, duplex receptacle | 12.50 | 9.60 | price agreement in the ERP |
| Panelboard 100 A | 165.00 | 149.00 | price agreement in the ERP |
| Cable ties, pack of 100 | 4.20 | 3.75 | price agreement in the ERP |
The product pages and the catalog show the same prices in the session, so the buyer never sees 12.50 and then 9.60 in the cart. Product data itself is written through the REST API's batch endpoints, which take up to 100 objects per request by default: 4,000 products is 4,000 / 100 = 40 requests.
From the WooCommerce cart to the PunchOut cart
The checkout button is replaced by a transfer button in a PunchOut session. The plugin reads the cart from the Store API, which returns the cart of the current session. A line of the cart of Bakker Installatie B.V., shortened:
{
"sku": "10482",
"name": "Duplex receptacle 20 A, white",
"quantity": 120,
"prices": {
"price": "960",
"currency_code": "USD",
"currency_minor_unit": 2
},
"totals": { "line_total": "115200", "line_total_tax": "0" }
}
- Prices are strings in minor units. With
currency_minor_unit2, "960" is 960 / 10^2 = 9.60 and "115200" is 1,152.00. Check: 120 x 9.60 = 1,152.00. line_totalexcludes tax, whichline_total_taxholds. The cXMLTotalexcludes tax as well, so the line totals add up without correction: 1,152.00 + 298.00 + 18.75 = 1,468.75.skubecomesSupplierPartIDorNEW_ITEM-VENDORMAT. A variation without its own SKU has no part number for the buyer and is held back with an error in the log.- WooCommerce has no unit field.
UnitOfMeasureand the classification come from product meta fields, filled once per product.
The Store API's rate limiting is off by default; when a store switches it on, it counts only POST requests, 25 per 10 seconds by default. Reading the cart is a GET and is not counted.
The purchase order back into WooCommerce
The OrderRequest arrives at the integration, which answers it and creates the order with POST /wp-json/wc/v3/orders. WooCommerce's built-in offline gateways, bank transfer (bacs), cheque and cash on delivery, carry no PO number. The order is created with set_paid false and the PO number goes into meta_data:
OrderRequest |
WooCommerce order |
|---|---|
orderID 4500001234 |
meta_data, key for example _po_number |
buyer identity in From |
customer_id of the user linked to customer 10432 |
ItemOut lines |
line_items with product_id or variation_id, quantity and the contract price |
ShipTo |
shipping |
BillTo |
billing |
| payment terms | payment_method, for example bacs, with set_paid false |
set_paid true would set the order to processing and reduce stock as if it were paid. A PunchOut order is on account, so the integration leaves it false and the order status follows the ERP. The reply to the procurement system comes from the integration's queue, not from WordPress, so a slow WordPress host does not turn into a timeout at the buyer.
When it goes wrong
| What happens | Cause | Handling |
|---|---|---|
| Colleague's items in the cart | one shared WordPress user, persistent cart in user meta | one user per buyer or per session |
| List price 12.50 in the PunchOut cart | the cart line was not repriced, for example from a cached page | price set on the cart line, PunchOut pages not cached |
| Blank frame | a security plugin or the host adds X-Frame-Options |
header removed for the PunchOut routes, or redirect |
| Prices 100 times too high in the cart | "960" read as 960.00 | divide by 10 to the power currency_minor_unit |
| Order marked paid and stock reduced | set_paid true |
set_paid false for every PunchOut order |
| PO number missing on the order | no field for it in WooCommerce | order meta data, passed to the ERP |
The same pieces on other platforms: Shopify, Magento and BigCommerce. What a store needs in full is on the PunchOut hub, and how amounts in minor units and tax travel between systems is on money, tax and rounding. Tell us which procurement system your customer uses.
Frequently asked questions
Is there a WooCommerce extension that adds PunchOut?
There are third-party plugins for it. WooCommerce itself has none, and a plugin still needs what this page lists: a setup endpoint, a session per buyer, contract prices, a cart in the buyer's format and an order intake. We build those to your buyers' setups and your ERP, as a plugin on your store and an integration next to it.
Can several buyers at one customer share a WooCommerce login?
Not safely. WooCommerce keeps the cart of a signed-in user in the user meta _woocommerce_persistent_cart_, so two colleagues on one account fill the same cart. The integration signs in one WordPress user per PunchOut session, or per person named in the setup request, never one shared account.
How do contract prices work without a B2B plugin?
The integration keeps the price agreements per buying organization, filled from the ERP, and a small plugin sets that price on each cart line of a PunchOut session. The product keeps its list price for everyone else. Customer 10432 sees item 10482 at 9.60; a regular visitor sees 12.50.
Can the procurement system show my WooCommerce store in a frame?
WordPress core sends X-Frame-Options: SAMEORIGIN on the login page only, not on storefront pages, so a framed store usually loads. A security plugin or the host can add the header to every page; the integration checks the live headers of the PunchOut pages before the first test with the buyer.
Where does the PO number go on a WooCommerce order?
WooCommerce has no field for it. The integration creates the order through POST /wp-json/wc/v3/orders and stores the orderID from the OrderRequest as order meta data, shown on the order screen and passed to the ERP with the order.
Tell us what you need
Tell us which systems you use and what needs to work better. We reply personally within one working day. We help you define the work, then agree the price and timing before you commit.
- You know the price and the timeline before you commit to anything
- We work out the connection that takes the work off your hands
- We handle the build, hosting, monitoring and maintenance
- Your team has less information to retype and fewer discrepancies to chase