Back to Order Processing & Fulfilment

Developer Handover Pack

Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.

4 pagesPDF · Technical
FS-DOC-04Technical

Developer Handover Pack

Order Processing and Fulfilment

[YourCompany.com] · Fulfilment Department · Prepared by FullSpec · [Today's Date]

This document gives the FullSpec build team every specification needed to construct, connect, and validate the three-agent Order Processing and Fulfilment automation. It covers the current-state step map with identified bottlenecks, full agent specifications with IO contracts, the end-to-end data flow trace, and the recommended build stack. The owner-side team does not need to act on anything in this document; it is written for the engineers handling Make scenario construction, API credential configuration, and agent sequencing.

01Process snapshot

Step
Name
Description
01 BOTTLENECK
Export New Orders from Storefront
Fulfilment Coordinator downloads a CSV of paid orders from Shopify, typically once or twice per shift. Orders placed between exports wait in a queue. Time cost: 15 min/cycle.
02 BOTTLENECK
Check Inventory Availability
Coordinator manually cross-references each order line against current Linnworks stock counts. Out-of-stock items are flagged in a separate tab. Time cost: 20 min/cycle.
03
Resolve Out-of-Stock Orders
Fulfilment Manager reviews flagged orders and decides to hold, substitute, or cancel, then notifies the customer by email. Time cost: 25 min/cycle (exception path).
04
Generate and Print Pick Lists
Coordinator formats the order CSV into a pick-list layout and prints it. Sorting is done by eye and is inconsistent. Time cost: 15 min/cycle.
05
Pick Items in the Warehouse
Warehouse Operative walks racking to collect each item on the printed pick list and brings them to the packing bench. Time cost: 30 min/cycle.
06
Pack and Verify Order Contents
Packers compare physical items to the pick list by eye before sealing the box. No barcode scan step exists. Time cost: 20 min/cycle.
07 BOTTLENECK
Create Shipping Label in ShipStation
Coordinator manually enters or imports order details into ShipStation, selects the carrier service, and prints the label. Time cost: 15 min/cycle.
08
Apply Label and Hand to Carrier
Labels are affixed to boxes and staged for carrier pickup or dropped at the depot. Time cost: 10 min/cycle.
09
Update Order Status in Shopify
Once the carrier collects, the coordinator manually marks orders as fulfilled in Shopify and pastes in the tracking number. Time cost: 15 min/cycle.
10
Send Tracking Notification to Customer
Coordinator triggers a tracking email through Klaviyo or relies on the default Shopify notification, which is often delayed or skipped. Time cost: 10 min/cycle.
11
Reconcile Inventory Counts
At end of shift, coordinator updates stock quantities in Linnworks to correct drift from the day's orders. Time cost: 20 min/cycle.
12
Log Fulfilment Costs to Accounts
Shipping costs from ShipStation are manually entered into Xero against each order for margin reporting. Time cost: 20 min/cycle.
Time cost summary: Total manual time per full cycle is 215 minutes (3 hours 35 minutes), not counting the exception-path step 03. At approximately 110 orders/day across shift batches this accumulates to 14 hours/week of coordinator and accounts time. The automation replaces steps 01, 02, 04, 07, 09, 10, 11, and 12 in full. Steps 05 and 06 (physical pick and pack) remain manual by design. Step 03 (exception resolution) remains with the Fulfilment Manager but is now triggered by a structured Slack alert rather than a discovered flag in a spreadsheet. Step 08 (label application and carrier handoff) is a physical warehouse step and is outside automation scope.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Order Routing Agent

Monitors the Shopify paid-order webhook stream continuously. On receipt of each new webhook payload, it queries Linnworks via API to check live available stock for every SKU in the order. If all line items are in stock it routes the order downstream to the Label and Despatch Agent and simultaneously triggers a zone-sorted pick list in Linnworks. If any SKU is out of stock it posts a structured exception alert to Slack for the Fulfilment Manager and halts further automated processing for that order. This is the entry point of the automation and must be resilient to duplicate webhook deliveries from Shopify, which are normal behaviour under retries.

Trigger
Shopify paid-order webhook (topic: orders/paid) fired on payment confirmation
Tools
Shopify, Linnworks, Slack
Replaces steps
01 (CSV export), 02 (inventory check), 03 (exception alert path), 04 (pick-list generation)
Estimated build
28 hours / Moderate complexity
// Input
shopify.order_id          : string   // e.g. '5123456789'
shopify.line_items[]      : array
  .sku                   : string   // must match Linnworks SKU exactly
  .quantity              : integer
shopify.shipping_address : object   // full address block
shopify.total_weight_g   : integer  // used downstream for carrier selection

// Linnworks stock check (per SKU)
linnworks.available_qty  : integer  // live available stock

// Output (in-stock path)
routed_order.order_id    : string
routed_order.line_items  : array    // confirmed in-stock items with SKU + qty
routed_order.pick_list_id: string   // Linnworks pick list reference
routed_order.ship_addr   : object
routed_order.weight_g    : integer

// Output (exception path)
slack.channel            : '#fulfilment-exceptions'
slack.message            : string   // order ID + out-of-stock SKUs + quantities
  • Shopify tier requirement: any plan that enables webhook subscriptions (Basic and above). Register the webhook programmatically via the Shopify Admin API rather than the dashboard to ensure it survives store migrations.
  • Dedupe guard: store each processed order_id in a Supabase seen_orders table and skip re-processing if the same ID arrives again within a 24-hour window. Shopify may fire the same webhook up to three times on retry.
  • Linnworks API tier: Standard plan or above is required for the Inventory Service endpoints. Confirm active API key and location ID before build begins.
  • SKU alignment: the Linnworks SKU field must exactly match the Shopify variant SKU field. Any mismatch produces a false out-of-stock result. A pre-build SKU audit is mandatory and must be signed off by Rachel Osei before this agent is built.
  • Pick list sort: when writing the pick list to Linnworks, pass warehouse zone as the sort key so operative walking distance is minimised. Zone mapping must be documented by the warehouse team before build.
  • Fallback behaviour: if the Linnworks API returns a timeout or 5xx error, do not route or reject the order. Park it in a Supabase pending_orders table and fire a Slack alert to '#fulfilment-exceptions' for manual action. Retry the stock check after 5 minutes up to three times.
Label and Despatch Agent

Receives a stock-confirmed order record from the Order Routing Agent and orchestrates label creation, Shopify fulfilment write-back, and Xero cost posting. It sends the order weight, dimensions (if available), and destination to ShipStation, applies the pre-configured carrier selection ruleset to choose the correct service, and retrieves the label PDF and tracking number. The tracking number is written back to the Shopify order via the Fulfilment API, setting status to Fulfilled. The ShipStation shipment cost is then posted as a line item to the matching Xero sales order. This agent must complete its sequence atomically: if the Xero write fails, it should log the failure and retry rather than leave the Shopify order in an inconsistent fulfilled state.

Trigger
Order Routing Agent posts a confirmed routed_order object to the Make scenario webhook
Tools
ShipStation, Shopify, Xero
Replaces steps
07 (label creation), 09 (Shopify status update), 12 (Xero cost entry)
Estimated build
22 hours / Moderate complexity
// Input
routed_order.order_id    : string
routed_order.line_items  : array    // SKU, qty, weight per item
routed_order.ship_addr   : object   // name, address1, city, postcode, country_code
routed_order.weight_g    : integer

// ShipStation label request
shipstation.order_id     : string   // mapped from shopify.order_id
shipstation.service_code : string   // resolved via carrier selection ruleset
shipstation.carrier_code : string   // e.g. 'fedex', 'usps', 'ups'

// Output (ShipStation response)
shipstation.tracking_num : string
shipstation.label_url    : string   // PDF download URL
shipstation.shipment_cost: float    // in USD

// Output (Shopify write-back)
shopify.fulfillment.tracking_number : string
shopify.fulfillment.tracking_company: string
shopify.fulfillment.status          : 'success'

// Output (Xero posting)
xero.invoice_id          : string   // matched by order_id reference
xero.line_item.description: string  // 'Shipping: {carrier} {service}'
xero.line_item.unit_amount: float   // shipstation.shipment_cost
xero.line_item.account_code: string // pre-configured shipping expense account
  • ShipStation tier: the Gold plan or above is required for API-based label creation at the expected volume of approximately 110 orders/day. Confirm carrier accounts are connected and test label generation in ShipStation sandbox before build.
  • Carrier selection ruleset: carrier_code and service_code must be resolved from a lookup table maintained in Make as a data store. Initial rule set covers: weight under 1 kg uses USPS First Class; 1 to 5 kg uses USPS Priority; over 5 kg or international uses FedEx Ground. Tom Briggs must confirm and sign off the full ruleset before this agent is built.
  • Shopify Fulfillment API: use the POST /orders/{order_id}/fulfillments endpoint with notify_customer set to false, as the Customer Notification Agent handles all customer-facing communication separately.
  • Xero matching: the Xero sales order is looked up by a custom order reference field populated at the time Shopify syncs to Xero. If no matching Xero invoice is found, log the mismatch to Supabase and alert Priya Nair via Slack rather than silently skipping the cost entry.
  • Atomic error handling: if ShipStation returns a label successfully but the Shopify write-back fails, the tracking number must be stored in Supabase so a retry or manual paste can be performed. Never leave a label created without a corresponding Shopify fulfilment record.
  • Label PDF storage: download and store the ShipStation label PDF URL in the Supabase orders table for audit purposes. Do not rely solely on ShipStation's own label archive.
Customer Notification Agent

Listens for the Shopify orders/fulfilled webhook event that is fired by the Label and Despatch Agent's write-back. On receipt, it identifies the tracking number, carrier name, and customer email from the Shopify fulfillment payload and fires a pre-built Klaviyo transactional event to trigger the branded tracking email. The Klaviyo event carries the tracking link, carrier display name, and an estimated delivery window calculated from the service level. This agent is the simplest of the three but is time-sensitive: the tracking email must fire within 90 seconds of the fulfilment event to meet the stated KPI.

Trigger
Shopify orders/fulfilled webhook fires when Label and Despatch Agent marks order as Fulfilled
Tools
Klaviyo, Shopify
Replaces steps
10 (tracking email send), 11 (inventory reconciliation notification path)
Estimated build
10 hours / Simple complexity
// Input
shopify.fulfillment.order_id         : string
shopify.fulfillment.tracking_number  : string
shopify.fulfillment.tracking_company : string   // carrier display name
shopify.fulfillment.tracking_url     : string   // pre-formed carrier tracking link
shopify.order.email                  : string   // customer email address
shopify.order.shipping_address.name  : string   // customer first name for personalisation

// Klaviyo event payload (POST to Track API)
klaviyo.event              : 'Order Shipped'
klaviyo.customer_properties.$email  : string
klaviyo.properties.tracking_number  : string
klaviyo.properties.tracking_url     : string
klaviyo.properties.carrier_name     : string
klaviyo.properties.estimated_delivery_window : string  // e.g. '3-5 business days'
klaviyo.properties.order_id         : string

// Output
klaviyo.event_id           : string   // confirmation of event receipt
klaviyo.email_status       : 'queued' // Klaviyo sends asynchronously
  • Klaviyo plan requirement: any paid Klaviyo plan supports the server-side Track API. Confirm the private API key has 'Events: Write' scope before build. The public API key is insufficient for server-side calls.
  • Email template: the 'Order Shipped' Klaviyo flow and email template must be built and approved in the Klaviyo dashboard before this agent is activated. The Make scenario only fires the event; Klaviyo owns the send logic and unsubscribe compliance.
  • Estimated delivery window: calculate from a static carrier-to-days lookup stored in Make's data store (matching the same service_code values used in the Label and Despatch Agent). Do not call a live carrier ETA API at this stage; that is an Enterprise-tier enhancement.
  • Deduplication: Shopify may fire multiple fulfilled webhooks for the same order if partial fulfilments are used. Check the fulfillment.id against a Supabase notified_fulfilments table before firing the Klaviyo event to prevent duplicate tracking emails.
  • Fallback: if the Klaviyo Track API returns a non-200 response, log the order_id and error to Supabase and retry once after 60 seconds. If the second attempt fails, alert the fulfilment team in Slack so a manual email can be triggered.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

End-to-end data flow: Shopify paid order to customer tracking email, with all field names and agent handoff points
// ============================================================
// ORDER PROCESSING AND FULFILMENT: END-TO-END DATA FLOW
// ============================================================

// TRIGGER
// Shopify fires orders/paid webhook on payment confirmation
SHOPIFY_WEBHOOK_PAYLOAD {
  order_id          : '5123456789'
  line_items[]      : [{ sku: 'SKU-001', quantity: 2 }, { sku: 'SKU-047', quantity: 1 }]
  shipping_address  : { name, address1, city, zip, country_code }
  total_weight_g    : 1850
  customer.email    : 'customer@example.com'
  financial_status  : 'paid'
}

// ---- AGENT 1: ORDER ROUTING AGENT -------------------------
// Step 1a: Dedupe check against Supabase seen_orders table
SUPABASE.seen_orders.SELECT(order_id) -> if found: HALT (duplicate)

// Step 1b: Per-SKU stock query to Linnworks Inventory API
LINNWORKS_REQUEST {
  endpoint  : GET /api/Stock/GetStockItemsFull
  params    : { SKUs: ['SKU-001', 'SKU-047'], location_id: '{warehouse_location_id}' }
}
LINNWORKS_RESPONSE {
  'SKU-001' : { available_qty: 14 }
  'SKU-047' : { available_qty: 0 }   // <- out of stock
}

// Step 1c: Decision branch
IF all line_items.available_qty >= line_items.quantity
  -> ROUTE TO: Label and Despatch Agent (routed_order object)
  -> CREATE:   Linnworks pick list (zone-sorted, pick_list_id returned)
ELSE
  -> POST TO:  Slack #fulfilment-exceptions
     slack.message : 'Exception: Order 5123456789 | Out-of-stock: SKU-047 (need 1, have 0)'
  -> WRITE TO: Supabase exceptions table
  -> HALT automated flow for this order_id

// HANDOFF: Order Routing Agent -> Label and Despatch Agent
// Field: routed_order
routed_order {
  order_id     : '5123456789'
  line_items   : [{ sku: 'SKU-001', qty: 2, weight_g: 900 }]  // only in-stock items
  ship_addr    : { name, address1, city, zip, country_code }
  total_weight_g: 1850
  pick_list_id : 'LW-PL-20240505-001'
}

// ---- AGENT 2: LABEL AND DESPATCH AGENT --------------------
// Step 2a: Carrier selection from Make data store ruleset
CARRIER_RULESET_LOOKUP(total_weight_g=1850) {
  1000 < weight <= 5000 : carrier_code='usps', service_code='usps_priority'
}
-> resolved: carrier_code='usps', service_code='usps_priority'

// Step 2b: ShipStation label creation
SHIPSTATION_REQUEST {
  endpoint     : POST /orders/createshipment
  body {
    orderId         : '5123456789'
    carrierCode     : 'usps'
    serviceCode     : 'usps_priority'
    weight          : { value: 1850, units: 'grams' }
    shipTo          : { ...ship_addr }
  }
}
SHIPSTATION_RESPONSE {
  shipmentId      : 'SS-88291'
  trackingNumber  : '9400111899223456789012'
  labelUrl        : 'https://ssapi.shipstation.com/shipments/label/SS-88291.pdf'
  shipmentCost    : 7.85   // USD
  carrierCode     : 'usps'
}

// Step 2c: Store label URL and tracking number in Supabase orders table
SUPABASE.orders.INSERT {
  order_id, tracking_number, label_url, shipment_cost, carrier_code, created_at
}

// Step 2d: Shopify fulfilment write-back
SHOPIFY_REQUEST {
  endpoint : POST /orders/5123456789/fulfillments.json
  body {
    fulfillment {
      tracking_number  : '9400111899223456789012'
      tracking_company : 'USPS'
      tracking_url     : 'https://tools.usps.com/go/TrackConfirmAction?tLabels=9400...'
      notify_customer  : false
    }
  }
}
SHOPIFY_RESPONSE {
  fulfillment.id     : 'FUL-774432'
  fulfillment.status : 'success'
}

// Step 2e: Xero shipping cost posting
XERO_REQUEST {
  endpoint : PUT /invoices/{xero_invoice_id}/lineitems
  body {
    description  : 'Shipping: USPS Priority'
    unitAmount   : 7.85
    accountCode  : '420'   // shipping expense account, confirm with Priya Nair
    quantity     : 1
  }
}
XERO_RESPONSE {
  lineItemId : 'XI-99201'
  status     : 'OK'
}

// HANDOFF: Label and Despatch Agent -> Customer Notification Agent
// Triggered by: Shopify orders/fulfilled webhook (auto-fired by Shopify on fulfillment write)
SHOPIFY_FULFILLED_WEBHOOK {
  order_id             : '5123456789'
  fulfillment_id       : 'FUL-774432'
  tracking_number      : '9400111899223456789012'
  tracking_company     : 'USPS'
  tracking_url         : 'https://tools.usps.com/go/TrackConfirmAction?tLabels=9400...'
  customer.email       : 'customer@example.com'
  shipping_address.name: 'Jane Smith'
}

// ---- AGENT 3: CUSTOMER NOTIFICATION AGENT -----------------
// Step 3a: Dedupe check against Supabase notified_fulfilments
SUPABASE.notified_fulfilments.SELECT(fulfillment_id) -> if found: HALT

// Step 3b: Estimated delivery window lookup from Make data store
DELIVERY_WINDOW_LOOKUP(service_code='usps_priority') -> '3-5 business days'

// Step 3c: Klaviyo Track API event fire
KLAVIYO_REQUEST {
  endpoint : POST https://a.klaviyo.com/api/track
  body {
    token : '{klaviyo_public_key}'
    event : 'Order Shipped'
    customer_properties {
      $email      : 'customer@example.com'
      $first_name : 'Jane'
    }
    properties {
      order_id                  : '5123456789'
      tracking_number           : '9400111899223456789012'
      tracking_url              : 'https://tools.usps.com/go/TrackConfirmAction?tLabels=9400...'
      carrier_name              : 'USPS Priority'
      estimated_delivery_window : '3-5 business days'
    }
  }
}
KLAVIYO_RESPONSE {
  status : 1   // 1 = accepted
}

// Step 3d: Write fulfillment_id to Supabase notified_fulfilments (dedupe record)
SUPABASE.notified_fulfilments.INSERT { fulfillment_id, order_id, sent_at }

// ============================================================
// FLOW COMPLETE
// Elapsed time trigger-to-notification: target < 90 seconds
// Manual steps remaining: Pick (Step 05), Pack (Step 06), Label apply + carrier handoff (Step 08)
// ============================================================
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
Make (formerly Integromat). One Make scenario per agent: 'FS-FUL-01 Order Routing', 'FS-FUL-02 Label and Despatch', 'FS-FUL-03 Customer Notification'. All three scenarios share a single Make team workspace with a centralised credential store. No credentials are hard-coded in scenario modules; all API keys and tokens are stored as Make Connections or Environment variables within the workspace.
Webhook configuration
Shopify webhooks are registered programmatically via the Shopify Admin REST API (POST /webhooks.json). Two webhooks required: topic 'orders/paid' pointing to the Make FS-FUL-01 scenario webhook URL, and topic 'orders/fulfilled' pointing to the FS-FUL-03 scenario webhook URL. Both must use HTTPS endpoints. Shopify HMAC signature validation must be implemented in each Make scenario's webhook module to reject unsigned requests. ShipStation does not receive webhooks in this architecture; it is called outbound only.
Templating approach
Carrier selection rules and estimated delivery windows are stored as Make Data Store tables (key-value format) so they can be updated by Tom Briggs without modifying scenario logic. Slack message formatting uses Make's built-in text templating. Xero account codes and Klaviyo event names are stored as Make scenario constants at the top of each relevant scenario for easy amendment.
Error logging
All error states (Linnworks timeout, ShipStation failure, Shopify write-back failure, Klaviyo rejection, Xero mismatch) write a structured row to a Supabase 'automation_errors' table with columns: order_id, agent_name, error_type, error_detail, occurred_at, resolved (boolean). A Make scenario monitors this table every 15 minutes and posts a Slack digest to '#fulfilment-ops' if any unresolved errors exist. Critical errors (ShipStation label created but Shopify write-back failed) additionally fire an immediate Slack DM to Tom Briggs.
Testing approach
Each agent is built and validated in Make's sandbox environment against Shopify's test order flow and ShipStation's sandbox carrier. Linnworks test data uses a staging location ID. Klaviyo test events are fired against a seed email address in a suppressed Klaviyo list. No real labels are printed and no real Xero invoices are modified until the parallel-run phase begins in week 5.
Estimated total build time
Order Routing Agent: 28 hours. Label and Despatch Agent: 22 hours. Customer Notification Agent: 10 hours. End-to-end integration testing and parallel-run support: 20 hours. Total: 80 hours across a 4 to 5 week delivery window.
Pre-build blockers: three items must be confirmed before any Make scenario build begins. First, SKU alignment between Shopify and Linnworks must be verified and signed off by Rachel Osei (the SKU audit is recorded as completed on Apr 9 but the audit log should be reviewed to confirm zero mismatches remain). Second, the full carrier selection ruleset covering all weight bands, destination zones, and restricted product types must be documented and signed off by Tom Briggs. Third, API access must be confirmed for all five platforms: Shopify (Admin API key with Orders write and Fulfillments write scopes), Linnworks (Standard plan API key with Inventory read and Pick List write), ShipStation (Gold plan API key), Klaviyo (private key with Events write), and Xero (OAuth 2.0 app with Accounting scope). Contact support@gofullspec.com if any platform access is outstanding.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Order Processing & Fulfilment.

Launch Plan
Operations · Owner
View
ROI and Business Case
Finance · Owner
View
Process Runbook / SOP
Operations · Owner
View
Integration and API Spec
Technical · Developer
View
Test and QA Plan
Quality · Developer
View