How the UCP Order Is Built from the Shopper Order Document
This Page
All Documentation
This page explains how B2C Commerce builds the UCP order in each post-processing update from the shopper order document, and which order-document fields fill each part of the payload.
Why the UCP Order Is Built from the Shopper Order
When a shopper places an order through an agentic-commerce channel such as Google Gemini, B2C Commerce creates the order and sends the first update to the agent platform. From then on, your Order Management System (OMS) is the system of record: fulfillment, shipping, delivery, cancellation, and returns happen there. The agent platform keeps its own copy of the order to show to the shopper.
B2C Commerce keeps that copy current. Whenever the order changes in your OMS, B2C Commerce takes the order data from your OMS, turns it into the format the agent platform expects, and delivers it as a complete UCP order carrying a cryptographic signature. Your OMS integration only connects to B2C Commerce. Everything that’s specific to the agent platform is handled for you.
How post-processing events reach B2C Commerce, from Salesforce OMS or from a third-party OMS, is described in Order Post-Processing Events in the UCP Order Lifecycle. This page explains what goes into the UCP order of each update.
UCP requires each update to contain the complete, current state of the order, not an incremental delta. It contains what was bought, at what price, where it ships, and the order’s full history so far. The agent platform replaces its copy with each update it receives, so anything the update leaves out is also missing from what the shopper sees. As your OMS processes the order, the UCP order grows by the latest fulfillment step.
This has three consequences for how B2C Commerce builds the payload:
The UCP order is rebuilt for every update. B2C Commerce doesn’t store a UCP version of the order or patch a previous payload. For each post-processing event, it builds a fresh UCP order from the current state of the order.
Third-party OMS only. B2C Commerce never stores a history of the events your third-party OMS sends to oms-status-events. The order document that your retrieve hook returns is the source of truth for each UCP order. Any step that belongs in the agent’s view of the order, now or in a later update, must be part of that order document.
Note
The current state lives in the shopper order document. The shopper order document is the order shape that Shopper Orders API returns: line items, prices, shipments, totals, and the live fulfillment data under omsData. Building the UCP order from this one shape keeps the payload identical regardless of which system manages the order.
With Salesforce OMS, B2C Commerce fetches the order data automatically, including all the data this page describes. There is no retrieve hook to build. Model each delivery with a Shipment and its ShipmentItem records so that document includes fulfillment data. See Fulfillment Data Mapping. With a third-party OMS, your retrieve hook (sfcc.app.oms.order.retrieve) supplies this data.
Third-party OMS only. When your retrieve hook builds the order document, the OMS data it adds (omsData) must reuse the IDs that are already populated in the order, such as order_no, product_items[].item_id, and shipments[].shipment_id. For example, each omsData.shipments[].shipment_items[].product_item_id must match an existing product_items[].item_id. This keeps the order graph consistent. If the IDs don’t match, fulfillment events and quantities can’t be linked to the order’s line items.
Important
This page currently covers fulfillment only: shipping and delivery. Returns and cancellations (the adjustments[] part of the UCP order) can’t be built from the order document yet, because the order document has no model for them. As a result, each update carries at most one adjustment: the return or cancellation that triggered the update. Earlier adjustments aren’t repeated in later updates. This applies to both Salesforce OMS and a third-party OMS.
This is a temporary limitation. A future release will add a return and cancellation model to the order document so that B2C Commerce can build the complete adjustment history from it, the same way it builds fulfillment events.
Note
Salesforce OMS sends a signal, and a third-party OMS still posts the event. With Salesforce OMS, B2C Commerce receives an event for specific updates, then fetches the current order document. Which updates send an event is described in UCP Order Updates from Salesforce Order Management. With a third-party OMS, each call to oms-status-events still contains exactly one fulfillmentEvent or adjustment. B2C Commerce adds that entry on top of the order document your retrieve hook returns. How to remove a step that then appears twice is described in Removing Duplicate Events.
A later signal-only model is planned for third-party OMS calls. In that model, the call carries no order data, and B2C Commerce builds the UCP order only from what your retrieve hook returns. Build that hook to return the complete, current state of the order, as described on this page. When the call becomes a signal, you change only the call.
In both cases, B2C Commerce translates the whole document into a UCP order and delivers it with a cryptographic signature.
The quality of every update therefore depends on the order document. A field that is missing or wrong in the order document is missing or wrong in the agent’s view of the order. The rest of this page shows what a UCP order looks like when the order is placed, which order-document field fills each part of it, and how it grows as the OMS processes the order.
Example: The UCP Order Right After Placement
This order has two line items in one shipment. It has just been placed, so nothing has been fulfilled yet. The currency is USD. Amounts are in minor units (cents).
The next sections go through this payload one part at a time.
How Each Section Is Filled
In the tables, Source names the order-document field as your hook sets it (snake_case). Values marked platform are supplied by B2C Commerce, not by the order document.
Monetary amounts are converted to the currency’s minor units: ×100 for USD and EUR, ×1 for JPY, ×1000 for KWD. Fractions below one minor unit are truncated.
ucp — Protocol Metadata
UCP field
Source
Rule
version
platform
The UCP version the agent platform declared when the order was placed, if B2C Commerce still holds the order and supports that version. Otherwise, the platform’s configured default version.
The version also determines whether discount amounts are negative or positive. See line_items[].totals and totals.
Order Identity
UCP field
Source
Rule
id
order_no
Copied as is.
checkout_id
platform
The basket ID of the B2C Commerce order. If B2C Commerce no longer holds the order, oms- followed by order_no.
event_id
order_no
event- followed by order_no.
created_time
creation_date
The order’s creation time. If creation_date isn’t set, then B2C Commerce uses the time that the UCP order is built, so always set creation_date.
permalink_url
platform
The Order Lookup URL from the site’s UCP channel configuration, used as is.
line_items — What Was Ordered
Each entry in product_items[] becomes one UCP line item.
UCP field
Source
Rule
id
product_items[].item_id
Copied as is. Fulfillment events refer to line items by this ID, so it must be stable.
The name of the catalog product with that ID, in the shopper’s checkout locale when B2C Commerce still holds the order, otherwise in the site’s default locale. If the product isn’t in the site catalog, product_name is used.
In UCP version 2026-04-08 and later, B2C Commerce sends items_discount and discount as negative values. In earlier versions, they’re positive. All other totals are non-negative.
How the UCP Order Grows as the OMS Processes the Order
After placement, an order usually moves through the OMS the same way. It is fulfilled (picked and packed), handed over to the shipping provider, and shipped. The shopper receives the package, and the order is complete. After each step, the OMS signals B2C Commerce, which sends a new update. What was ordered and the totals stay the same. The fulfillment events log grows and the line items’ fulfillment state advances.
OMS step
fulfillment.events after the update
Line items
Order placed
processing
processing, fulfilled 0
Fulfilled (picked and packed)
processing
processing, fulfilled 0
Handed over to the shipping provider and shipped
processing, shipped (with tracking)
fulfilled, fulfilled = ordered
Shopper received the package
processing, shipped, delivered
fulfilled, fulfilled = ordered
Completed
No new event; the last update already shows the complete history
fulfilled, fulfilled = ordered
For Salesforce OMS, the delivered event isn’t available yet. See Shipment Completed.
After delivery, the example order carries this events log, and both lines show "status": "fulfilled" with fulfilled equal to total:
Each UCP order is rebuilt from scratch. See Why the UCP Order Is Built from the Shopper Order. The history comes from the order document every time. Salesforce OMS supplies these fields automatically. A third-party retrieve hook returns them:
Data in the UCP order
Source in the order document
processing event
Always present, one per order. occurred_at is the order’s creation date (creation_date on the order). id is order_no. line_items are the order’s product items (item_id, quantity).
shipped event
One per entry in omsData.shipments[]. occurred_at is the shipment’s creation date (omsData.shipments[].created_date), the moment the OMS created the shipment and handed it to the carrier. id is omsData.shipments[].id. line_items come from shipment_items[] (product_item_id → id, quantity). Tracking comes from tracking_number, tracking_url, and provider (→ carrier).
delivered event
One per entry in omsData.shipments[] that has an actual_delivery_date. occurred_at is the shipment’s actual delivery date (omsData.shipments[].actual_delivery_date). id, line_items, and tracking are the same as for the shipped event of that shipment.
line_items[].quantity.fulfilled
The sum of omsData.shipments[].shipment_items[].quantity per product_item_id.
With Salesforce OMS, B2C Commerce reads this document when one of the updates occurs. With a third-party OMS, update the state your retrieve hook reads before you call oms-status-events. Otherwise the update shows the previous state. The hook must return the complete history every time. A shipment that the hook no longer returns disappears from the agent’s view.
Removing Duplicate Events
Until the signal-only model is available, the event your OMS sends to oms-status-events is added to the UCP order in addition to the events built from your retrieve hook. If both describe the same step, the UCP order lists it twice.
Remove these duplicates with the sfcc.ucp.orderWebhook.v1.modifyEventPayload hook. B2C Commerce calls this hook with the finished UCP order right before the order is signed and delivered. Change the order in place. See Business Profile and Order Webhooks.
The payload argument is a script object. Read and write camelCase properties, such as checkoutId, eventId, and permalinkUrl. B2C Commerce serializes the order to snake_case when it sends the order. The JSON samples on this page show that sent format.
In the event you send to oms-status-events, use the same id as the matching entry in omsData.shipments[]. A shipped event and the shipment it reports then share the same type and id.
Register the hook in your site cartridge’s hooks.json:
In the hook, keep only the first event for each type and id pair. The events built from your retrieve hook come first, so that entry is kept and the duplicate that was sent is removed.
1"use strict";2var ArrayList = require("dw/util/ArrayList");3var Status = require("dw/system/Status");45exports.modifyEventPayload = function(payload){6 var fulfillment = payload.fulfillment;7 if(!fulfillment || !fulfillment.events){8 return new Status(Status.OK);9}1011 var seen = {};12 var filteredEvents = new ArrayList();1314 fulfillment.events.toArray().forEach(function(event){15 var key = String(event.type) + "|" + String(event.id);1617 if(!seen[key]){18 seen[key] = true;19 filteredEvents.add(event);20}21});2223 fulfillment.events = filteredEvents;24 return new Status(Status.OK);25};
Keep these rules for the hook in mind:
Always return Status.OK. If the hook throws or returns an error, the update isn’t sent and is retried later.
Don’t change id, checkoutId, or eventId. If any of them changes, the update isn’t delivered.
Keep it fast. The hook runs under a time limit. Don’t call your OMS from it.
Moving to the signal-only model. This filter also supports the switch. After oms-status-events becomes a signal, no event is added to the UCP order, so there are no duplicates. The filter then finds nothing to remove and changes nothing. Your integration keeps working while you change the call. Remove the hook afterwards.