Shopify: Push Local Fulfillment Changes, Reliable Reconciliation, Structural Sync
On a Shopify order, the Fulfillments tab opens with a banner naming who currently manages the fulfillment orders — the source or Endless. When Endless holds the order and you have restructured the fulfillments locally — split one, canceled one, added a new one — an Update fulfillment orders button pushes those changes back to Shopify. The sync runs in the background, the tab re-fetches when it finishes, and the button disappears once both systems match. The button stays disabled while any line has unassigned or over-covered units — a partial set is not something the source can accept — and lists the offending lines so you know what still needs coverage. When a sync fails, order timeline events show an expandable More details row with the Shopify error, the fulfillment order it refers to, and the destination location, instead of a bare failure line.
Fulfillment changes made in Endless — moving items between buildings, splitting a fulfillment, merging fulfillments — did not always take hold on the Shopify side. A structural edit could invalidate the link between the two systems in cases where Shopify could not rebuild its own fulfillment plan, so the change looked applied in Endless while Shopify still held the previous plan. The sync now rebuilds Shopify’s fulfillment orders by moving, merging, and splitting locations until they match Endless, and only rebinds the two together after Shopify confirms. Partial or rejected changes stay visibly unsynchronized on the order rather than looking done, so a divergence is obvious and retrying the sync resolves it. Source-owned changes and integrations that do not sync fulfillment plans behave as before.
The Shopify order reconciliation was reading the same window of recent orders on every run and reprocessing orders it had already handled, which added duplicate fulfillment warnings to the timeline for the same underlying condition. Each successful run now moves its start point to when that run began, so the next pass reads new activity forward from there instead of re-reading everything since the day started. A failed run holds its position, so nothing is skipped. Webhook handling is unchanged.