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 & Fulfilment

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

This document is the primary technical reference for the FullSpec team building the Order Processing and Fulfilment automation. It covers the current-state process map with bottleneck flags, the full specification for each of the three agents, the end-to-end data flow across all tool integrations, and the recommended build stack. The FullSpec team owns every build, integration, and test task described here. The process owner's responsibilities are limited to supplying API credentials, confirming courier service-level mapping, and approving wholesale invoice logic before the Finance Agent goes live.

01Process snapshot

Step
Name
Description
1
Check New Orders Dashboard
Fulfilment Coordinator logs into Shopify at shift start and reviews new paid orders. Overnight and weekend orders are batched. Time cost: 10 min/order.
2 BOTTLENECK
Verify Stock Availability
Each line item is cross-checked against Linnworks. Discrepancies require a call to the warehouse manager before the order can proceed. Time cost: 8 min/order.
3
Flag Out-of-Stock Orders
Orders with unavailable items are separated and a manual email is drafted to the customer with no standard template. Tone and content vary by staff member. Time cost: 12 min/order.
4 BOTTLENECK
Enter Shipping Details Into Courier Portal
Coordinator copies recipient name, address, and parcel weight from Shopify into ShipStation by hand. Typos at this stage cause failed deliveries and returns. Time cost: 5 min/order.
5
Select Shipping Method and Rate
Coordinator reviews available courier rates in ShipStation and selects the service level matching the customer's checkout choice. Expedited orders must be visually identified. Time cost: 3 min/order.
6
Print Packing Slip and Shipping Label
Labels and packing slips are printed from ShipStation and physically attached to the parcel. Mislabelled parcels are only discovered after dispatch. Time cost: 4 min/order.
7
Pick and Pack Order
Warehouse picker locates items, packs to standard, and seals the parcel. Item count discrepancies require the coordinator to recheck stock. Time cost: 6 min/order.
8 BOTTLENECK
Update Order Status to Fulfilled
Coordinator returns to Shopify after dispatch and manually marks the order fulfilled, entering the tracking number from ShipStation. This step is frequently delayed by hours. Time cost: 4 min/order.
9
Send Tracking Confirmation to Customer
Shipping confirmation email with tracking link is copied and sent manually from Gmail. High-volume periods cause batched delays until end of day. Time cost: 3 min/order.
10
Deduct Stock in Inventory System
Linnworks stock levels are adjusted manually after dispatch. When skipped or delayed, overselling occurs on the next order cycle. Time cost: 5 min/order.
11
Log Fulfilment Data for Reporting
Operations Manager enters order totals, dispatch times, and courier selections into a spreadsheet for weekly reporting. Provides no real-time visibility. Time cost: 10 min/order.
12
Raise Invoice for Wholesale Orders
Operations Manager raises a Xero invoice cross-referencing the Shopify order number for wholesale and B2B orders. Mismatches create reconciliation problems at month end. Time cost: 8 min/order.
Time cost summary: Total manual time per order cycle is 78 minutes across all 12 steps. At approximately 220 orders per month (roughly 55 per week), the process consumes approximately 9 hours of staff time per week and 39 hours per 30-day period. The automation replaces steps 1, 2, 3, 4, 5, 8, 9, 10, and 12 entirely. Steps 6 and 7 (print label, pick and pack) remain human. Step 11 (reporting log) is eliminated as a manual task because structured dispatch data is available in real time from the automated flow.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Order Intake Agent

Monitors Shopify for new paid orders via webhook, retrieves the full order payload including all line items and customer details, then queries Linnworks in real time to verify stock availability for every SKU. If all items are in stock the agent passes a verified order record downstream to the Dispatch Agent. If any item is unavailable the agent sends a structured Slack alert to the fulfilment channel containing the order ID, the affected SKUs, quantities requested, and a direct Shopify order URL so the team can act immediately. This agent is the entry point for every order in the automated flow and must be atomic in its stock check to prevent race conditions under concurrent load. Estimated build time: 10 hours. Complexity: Moderate.

Trigger
Shopify order/paid webhook fires on creation of a new paid order
Tools
Shopify (webhook inbound), Linnworks (stock query API), Slack (alert outbound)
Replaces steps
Steps 1, 2, and 3
Estimated build
10 hours / Moderate
// Input
shopify.order.id            // string, e.g. '5678901234'
shopify.order.line_items[]  // array: { sku, quantity, variant_id, title }
shopify.order.shipping_address // { name, address1, city, province, zip, country }
shopify.order.shipping_lines[0].title  // customer-selected service level
shopify.order.tags          // string, e.g. 'wholesale, B2B'
shopify.order.total_price   // string
shopify.order.email         // customer email address

// Output (all in stock)
verified_order.order_id     // passed to Dispatch Agent
verified_order.line_items[] // { sku, quantity, linnworks_stock_id }
verified_order.shipping_address
verified_order.service_level // mapped courier service label
verified_order.tags
verified_order.customer_email
verified_order.status       // 'ready_for_dispatch'

// Output (stock failure)
slack.alert.channel         // '#fulfilment-alerts'
slack.alert.order_id
slack.alert.oos_skus[]      // { sku, title, requested_qty, available_qty }
slack.alert.shopify_order_url
verified_order.status       // 'on_hold_oos'
  • Shopify plan tier must support real-time order webhooks (Basic and above). If the store is on a legacy plan that does not surface the order/paid event reliably, build a five-minute polling fallback using the REST Orders API filtered by financial_status=paid and created_at_min set to the last successful poll timestamp.
  • Linnworks stock check must call GET /api/Stock/GetStockItemsFull with the exact SKU values from the Shopify line_items array. Confirm that Shopify SKUs match Linnworks SKU references one-to-one before build; if they differ, a mapping table must be created and stored in the automation platform's credential/data store.
  • The stock check for multiple line items must be treated as a single atomic operation. If two orders for the same low-stock SKU arrive within the webhook processing window, a database-level lock or a short-lived mutex flag in the automation platform's key-value store must prevent both from passing validation simultaneously.
  • Slack alert must post to the exact channel name '#fulfilment-alerts' (confirm with the process owner before build). The Slack bot token requires the chat:write OAuth scope and must be installed to the workspace with permission to post in that channel.
  • Dedupe: if a Shopify webhook is delivered more than once for the same order ID (Shopify guarantees at-least-once delivery), the agent must check a processed-order log before re-executing. Log the order ID on first successful processing and short-circuit on duplicate receipt.
  • The out-of-stock email to the customer (original step 3) is replaced by the Slack alert to the fulfilment team only. A separate customer-facing notification for out-of-stock orders is out of scope for this build unless explicitly added to scope by the process owner.
Dispatch Agent

Receives the verified order record from the Order Intake Agent and executes the full dispatch sequence without manual input. It creates the shipment in ShipStation using the mapped courier and service level, retrieves the generated tracking number, writes the fulfilment record and tracking URL back to Shopify, sends the customer a personalised confirmation email via Gmail, and decrements the dispatched quantities in Linnworks immediately after dispatch confirmation. This agent replaces the highest-volume manual steps in the process and must handle the courier service-level mapping table as a configurable input rather than hard-coded logic. Estimated build time: 18 hours. Complexity: Complex.

Trigger
Order Intake Agent emits a verified_order record with status 'ready_for_dispatch'
Tools
ShipStation (shipment creation), Shopify (fulfilment write-back), Gmail (customer email), Linnworks (stock deduction)
Replaces steps
Steps 4, 5, 8, 9, and 10
Estimated build
18 hours / Complex
// Input
verified_order.order_id
verified_order.line_items[]          // { sku, quantity, linnworks_stock_id }
verified_order.shipping_address      // { name, address1, city, province, zip, country_code }
verified_order.service_level         // e.g. 'Standard', 'Express', 'Overnight'
verified_order.customer_email
verified_order.tags

// ShipStation intermediate
shipstation.shipment_id              // returned on POST /shipments/createlabel
shipstation.tracking_number          // string
shipstation.carrier_code             // e.g. 'ups', 'fedex', 'usps'
shipstation.label_url                // PDF label URL for warehouse printer

// Output
shopify.fulfillment.tracking_number  // written via POST /fulfillments.json
shopify.fulfillment.tracking_url     // courier tracking URL
shopify.fulfillment.status           // 'success'
gmail.message.to                     // verified_order.customer_email
gmail.message.subject                // 'Your order is on its way'
gmail.message.body                   // includes carrier_code, tracking_number, tracking_url
linnworks.stock_deduction[]          // { linnworks_stock_id, quantity_deducted }
  • ShipStation courier service-level mapping must be fully documented before build starts. The mapping table must link each Shopify shipping_lines[0].title value (e.g. 'Standard Shipping', 'Express Delivery') to the corresponding ShipStation carrier_code and service_code pair. This table must be confirmed by the process owner and stored as a configurable data structure in the automation platform, not hard-coded.
  • ShipStation API authentication uses HTTP Basic Auth with the API key and API secret encoded as base64. The store must be on a ShipStation plan that includes API access (Bronze and above). The POST /shipments/createlabel endpoint must be used, not the deprecated /orders endpoint, to receive a tracking number synchronously.
  • Shopify fulfilment write-back uses POST /admin/api/2024-01/orders/{order_id}/fulfillments.json. The request body must include the tracking_number, tracking_company, and tracking_url fields. Confirm that the Shopify store does not have third-party fulfilment services configured that would intercept or reject this API call.
  • Gmail sending uses OAuth 2.0 with the gmail.send scope. The sending account must be a Google Workspace account belonging to [YourCompany.com]. A draft email template must be approved by the process owner before build. The template must include the courier name, tracking number, and a hyperlinked tracking URL. Do not use a personal Gmail account.
  • Linnworks stock deduction calls POST /api/Orders/SetOrderItemsQuantity or the equivalent stock adjustment endpoint. Deduction must occur only after Shopify fulfilment write-back returns HTTP 201. If the Shopify write-back fails, the deduction must not proceed and the error must be logged with the order ID.
  • Fallback behaviour: if ShipStation returns an error on shipment creation (e.g. unrecognised service code, address validation failure), the agent must post a Slack alert to '#fulfilment-alerts' with the order ID and the exact ShipStation error message, then halt without deducting stock or marking Shopify fulfilled.
  • Rate limits: ShipStation enforces 40 API calls per minute per account. At peak volume (55 orders per week, roughly 8 to 10 per day in a single processing window), the agent is well within this limit. Build in a retry with exponential backoff (1 s, 2 s, 4 s) for any 429 response.
Finance Agent

Runs in parallel with the Dispatch Agent for any order carrying a wholesale or B2B tag in Shopify. It reads the order line items and total from the Shopify order payload, creates a draft invoice in Xero with all line items, amounts, tax codes, and a reference to the Shopify order ID, and leaves the invoice in draft state for the Operations Manager to review and approve before sending. This agent does not send invoices autonomously. The draft-and-approve model is a deliberate design constraint confirmed in the process template. Estimated build time: 12 hours. Complexity: Moderate.

Trigger
Shopify order payload contains the tag 'wholesale' or 'B2B' (case-insensitive match)
Tools
Shopify (order data source), Xero (draft invoice creation)
Replaces steps
Step 12
Estimated build
12 hours / Moderate
// Input
shopify.order.id                    // used as Xero invoice reference
shopify.order.tags                  // must contain 'wholesale' or 'b2b'
shopify.order.line_items[]          // { title, sku, quantity, price }
shopify.order.total_price           // string, e.g. '540.00'
shopify.order.total_tax             // string
shopify.order.customer.email        // buyer contact for invoice
shopify.order.billing_address       // used to match or create Xero contact

// Output
xero.invoice.type                   // 'ACCREC'
xero.invoice.status                 // 'DRAFT'
xero.invoice.contact.name           // matched from billing_address.company or customer name
xero.invoice.line_items[]           // { description, quantity, unit_amount, account_code }
xero.invoice.reference              // shopify.order.id as string
xero.invoice.invoice_id             // Xero-generated UUID returned on POST

// On approval
// Operations Manager reviews draft in Xero and sets status to AUTHORISED
// No automation action required post-draft; approval is a manual step
  • Xero API authentication uses OAuth 2.0 with PKCE. The Xero app must be registered in the Xero Developer Portal under [YourCompany.com]'s Xero organisation. Required scopes are accounting.transactions and accounting.contacts. The access token expires after 30 minutes; the automation platform must use the refresh token to obtain a new access token before each API call.
  • The Xero contact lookup must attempt to match the buyer by company name (billing_address.company) before creating a new contact. If no match is found, a new Xero contact is created using the billing name and email. Confirm the Xero account codes to use for product line items and tax with the Operations Manager before build. Do not use a placeholder account code.
  • Wholesale tag detection must be case-insensitive. The tag string from Shopify is comma-separated; the agent must split on commas, trim whitespace, and check for 'wholesale' or 'b2b' as exact substring matches.
  • The Finance Agent must run regardless of whether the Dispatch Agent succeeds. Tag detection happens at the Order Intake Agent output stage so that a wholesale order with an out-of-stock item still triggers a draft invoice for the relevant lines if confirmed by the process owner. Confirm this edge-case behaviour before build.
  • Xero rate limit is 60 API calls per minute per connection. At 220 orders per month with an estimated 15 to 20 percent wholesale proportion (33 to 44 orders), peak load is minimal. Standard retry logic with a 2-second delay on 429 is sufficient.
  • The invoice must not be submitted or sent by the automation. Status must remain 'DRAFT' at all times. Confirm with the Operations Manager (Priya Mehta) that the Xero notification settings will alert her when a new draft invoice appears, or build a supplementary Slack notification to '#finance-alerts' as a prompt.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow across Order Intake Agent, Dispatch Agent, and Finance Agent. Field names match API request/response keys for each named tool.
// ============================================================
// ORDER PROCESSING & FULFILMENT  —  END-TO-END DATA FLOW
// ============================================================

// TRIGGER
// Shopify emits order/paid webhook on customer checkout completion
SHOPIFY_WEBHOOK  POST /webhooks/orders/paid
  -> payload.id                        // shopify_order_id
  -> payload.email                     // customer_email
  -> payload.tags                      // raw tag string, e.g. 'wholesale, B2B'
  -> payload.shipping_address          // { name, address1, city, province, zip, country_code }
  -> payload.shipping_lines[0].title   // service_level_label
  -> payload.line_items[]              // [{ sku, quantity, variant_id, title, price }]
  -> payload.total_price               // order_total
  -> payload.total_tax                 // order_tax
  -> payload.billing_address           // { company, name, email }

// ============================================================
// AGENT 1: ORDER INTAKE AGENT
// ============================================================

// Dedupe check
DATASTORE.lookup(shopify_order_id)
  -> IF found: short-circuit, log duplicate, halt
  -> IF not found: DATASTORE.write(shopify_order_id, timestamp_utc)

// Stock verification — one call per line item
FOR each line_item IN payload.line_items[]:
  LINNWORKS  GET /api/Stock/GetStockItemsFull
    -> request.SKU = line_item.sku
    -> response.Available         // available_qty
    -> response.StockItemId       // linnworks_stock_id
    -> stock_check[sku] = { available_qty, linnworks_stock_id, requested_qty: line_item.quantity }

// Branch decision: all SKUs in stock?
IF any stock_check[sku].available_qty < stock_check[sku].requested_qty:
  // Out-of-stock branch
  SLACK  POST /api/chat.postMessage
    -> channel = '#fulfilment-alerts'
    -> text.order_id = shopify_order_id
    -> text.oos_skus = [{ sku, title, requested_qty, available_qty }]
    -> text.shopify_order_url = 'https://[YourCompany.com].myshopify.com/admin/orders/{shopify_order_id}'
  verified_order.status = 'on_hold_oos'
  HALT dispatch pipeline
ELSE:
  // All in stock — build verified_order record
  verified_order = {
    order_id:        shopify_order_id,
    customer_email:  customer_email,
    shipping_address: payload.shipping_address,
    service_level:   SERVICE_LEVEL_MAP[service_level_label], // mapped to { carrier_code, service_code }
    line_items:      [{ sku, quantity, linnworks_stock_id }],
    tags:            parse_tags(payload.tags),
    order_total:     payload.total_price,
    order_tax:       payload.total_tax,
    billing_address: payload.billing_address,
    status:          'ready_for_dispatch'
  }
  // Emit verified_order to Dispatch Agent AND (if wholesale) to Finance Agent in parallel

// ============================================================
// AGENT 2: DISPATCH AGENT  (receives verified_order)
// ============================================================

// Step 1: Create shipment in ShipStation
SHIPSTATION  POST /shipments/createlabel
  -> request.carrierCode      = verified_order.service_level.carrier_code
  -> request.serviceCode      = verified_order.service_level.service_code
  -> request.shipTo.name      = verified_order.shipping_address.name
  -> request.shipTo.street1   = verified_order.shipping_address.address1
  -> request.shipTo.city      = verified_order.shipping_address.city
  -> request.shipTo.state     = verified_order.shipping_address.province
  -> request.shipTo.postalCode= verified_order.shipping_address.zip
  -> request.shipTo.country   = verified_order.shipping_address.country_code
  -> response.shipmentId      // shipstation_shipment_id
  -> response.trackingNumber  // tracking_number
  -> response.labelData       // base64 PDF label
  -> response.carrierCode     // carrier_code (confirmed)
  IF error: POST Slack alert to '#fulfilment-alerts', log error, HALT

// Step 2: Write fulfilment and tracking back to Shopify
SHOPIFY  POST /admin/api/2024-01/orders/{shopify_order_id}/fulfillments.json
  -> request.fulfillment.tracking_number  = tracking_number
  -> request.fulfillment.tracking_company = carrier_code
  -> request.fulfillment.tracking_url     = tracking_url (constructed from carrier_code + tracking_number)
  -> request.fulfillment.notify_customer  = false  // Gmail handles notification
  -> response.fulfillment.id              // shopify_fulfillment_id
  -> response.fulfillment.status          // must equal 'success'
  IF error: log, do NOT proceed to Gmail or Linnworks

// Step 3: Send tracking confirmation via Gmail
GMAIL  POST /gmail/v1/users/me/messages/send
  -> request.to      = verified_order.customer_email
  -> request.subject = 'Your order is on its way'
  -> request.body    = TEMPLATE(carrier_code, tracking_number, tracking_url)
  -> response.id     // gmail_message_id (logged)

// Step 4: Deduct stock in Linnworks
FOR each item IN verified_order.line_items[]:
  LINNWORKS  POST /api/Orders/SetOrderItemsQuantity (or stock adjustment endpoint)
    -> request.StockItemId     = item.linnworks_stock_id
    -> request.Quantity        = item.quantity
    -> request.Note            = 'Auto-deducted: Shopify order ' + shopify_order_id
  IF error: log with order_id, alert Slack '#fulfilment-alerts'

// ============================================================
// AGENT 3: FINANCE AGENT  (parallel branch, wholesale only)
// ============================================================

// Tag check (case-insensitive)
IF 'wholesale' OR 'b2b' IN verified_order.tags:

  // Xero contact lookup or create
  XERO  GET /api.xro/2.0/Contacts?where=Name='{billing_address.company}'
    -> IF match: xero_contact_id = response.Contacts[0].ContactID
    -> IF no match:
         XERO  POST /api.xro/2.0/Contacts
           -> request.Name  = billing_address.company OR billing_address.name
           -> request.EmailAddress = customer_email
           -> response.ContactID // xero_contact_id

  // Create draft invoice
  XERO  POST /api.xro/2.0/Invoices
    -> request.Type            = 'ACCREC'
    -> request.Status          = 'DRAFT'
    -> request.Contact.ContactID = xero_contact_id
    -> request.Reference       = shopify_order_id
    -> request.LineItems[]     = [{ Description: sku+title, Quantity, UnitAmount: price, AccountCode }]
    -> request.TaxType         = confirmed with Operations Manager pre-build
    -> response.InvoiceID      // xero_invoice_id (logged)
    -> response.Status         // must equal 'DRAFT'

  // Optional: Slack prompt to Operations Manager
  SLACK  POST /api/chat.postMessage
    -> channel = '#finance-alerts'
    -> text = 'Draft Xero invoice created for Shopify order ' + shopify_order_id

// ============================================================
// ERROR LOGGING  (all agents)
// ============================================================
ON any unhandled exception:
  ERROR_LOG.insert({
    timestamp_utc,
    agent_name,
    shopify_order_id,
    step,
    error_code,
    error_message,
    payload_snapshot
  })
  SLACK  POST '#fulfilment-alerts' with error summary
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation tool running one scenario (workflow) per agent: Order Intake Agent scenario, Dispatch Agent scenario, Finance Agent scenario. All three scenarios share a single credential store so API keys and OAuth tokens are entered once and referenced by name. The orchestration layer must support webhook inbound triggers, HTTP request modules for REST API calls, conditional branching, loop/iterator handling for line item arrays, and a key-value or data store module for order-ID deduplication.
Webhook configuration
Shopify sends the order/paid webhook to the inbound webhook URL generated by the Order Intake Agent scenario. In the Shopify Admin under Settings > Notifications > Webhooks, create a webhook for the 'Order payment' event pointing to this URL. Set the format to JSON. Verify the webhook secret header (X-Shopify-Hmac-Sha256) in the automation platform's webhook trigger module to reject unsigned requests. If the store plan does not reliably trigger this event, configure a supplementary five-minute polling job using GET /admin/api/2024-01/orders.json?financial_status=paid&created_at_min={last_poll_timestamp} as a fallback.
Templating approach
The customer tracking confirmation email and the Slack alert messages must be defined as named text templates stored in the automation platform's data store or as inline template strings within the scenario, with placeholders for tracking_number, carrier_code, tracking_url, shopify_order_id, and oos_skus. The Gmail template must be reviewed and approved by the process owner before the Dispatch Agent goes live. The courier service-level mapping table (Shopify service_level_label to ShipStation carrier_code plus service_code) must be stored as a JSON object in the data store and loaded at runtime, not hard-coded into the scenario logic.
Error logging
All agent errors, fallback triggers, and unhandled exceptions must be written to a structured error log. The recommended approach is a Supabase table named 'fulfilment_error_log' with columns: id (uuid), created_at (timestamptz), agent_name (text), shopify_order_id (text), step (text), error_code (text), error_message (text), payload_snapshot (jsonb). Each agent scenario includes an error-catch module that inserts a row into this table and simultaneously posts a Slack alert to '#fulfilment-alerts' with the order ID and a one-line error summary. The FullSpec team will configure read access to this table for the process owner's dashboard view.
Testing approach
All three agent scenarios must be built and validated in sandbox environments before connecting to production credentials. Shopify: use a Shopify development store or the store's test order feature (place an order with the Shopify Bogus Gateway). ShipStation: use the ShipStation sandbox endpoint (sandbox.shipstation.com) with test carrier credentials. Xero: use the Xero Demo Company organisation (separate OAuth app registration required). Linnworks: test against a staging environment if available, or use a dedicated test location with zero live stock impact. Gmail: send to an internal test address only during QA. Promote to production credentials only after all test cases in the Test and QA Plan pass.
Estimated total build time
Order Intake Agent: 10 hours. Dispatch Agent: 18 hours. Finance Agent: 12 hours. End-to-end integration testing and error-handling validation: 12 hours. Total: 52 hours across the five-week delivery schedule.
Before any build work begins, the following must be confirmed in writing: (1) Shopify plan tier and webhook reliability for the order/paid event; (2) ShipStation carrier account details and full service-level mapping for every shipping option offered at checkout; (3) Linnworks SKU reference format and confirmation that Shopify SKUs match exactly; (4) Xero account codes and tax types for wholesale invoice line items, confirmed by the Operations Manager (Priya Mehta); (5) Slack workspace bot token with chat:write scope installed to both '#fulfilment-alerts' and '#finance-alerts'; (6) Gmail OAuth credentials for the sending account with gmail.send scope. Contact support@gofullspec.com if any of these items cannot be resolved before the build start date.
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