Back to Order Processing & Fulfilment

Integration and API Spec

The reference during the build: every tool connection, auth scope, field mapping, and error-handling rule.

4 pagesPDF · Technical
FS-DOC-05Technical

Integration and API Spec

Order Processing & Fulfilment

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

This document is the authoritative technical reference for every integration point in the Order Processing and Fulfilment automation. It covers authentication methods, required OAuth scopes, webhook configurations, field mappings between tools, credential-store structure, orchestration layout, and error-handling behaviour for all six connected platforms. The FullSpec team uses this spec during build, QA, and any post-launch debugging. Your team should treat it as the source of truth whenever a connection is reconfigured or a credential is rotated.

01Tool inventory

Tool
Role in automation
Auth method
Min plan required
Used by agent(s)
Automation platform (orchestration layer)
Hosts all workflow scenarios; routes data between tools; manages credentials and retry logic
N/A (internal)
Core / Pro tier with webhook support and multi-scenario execution
All agents
Shopify
Order source; receives fulfilled status and tracking number write-back
OAuth 2.0 (custom app) or API key + secret
Basic Shopify or higher (webhook access included)
Agent 1, Agent 2
Linnworks
Inventory stock-check; pick-list generation
API key + application secret (Linnworks Open API)
Linnworks One or higher with Open API access enabled
Agent 1
Slack
Exception alert channel for out-of-stock orders
OAuth 2.0 (Slack app with Bot Token)
Free tier sufficient; Slack Pro recommended for audit log retention
Agent 1
ShipStation
Shipping label generation; carrier routing; tracking number retrieval
API key + API secret (HTTP Basic Auth over HTTPS)
Starter plan or higher with API access
Agent 2
Xero
Shipping cost line-item posting against sales orders
OAuth 2.0 (Xero connected app, PKCE flow)
Xero Starter or higher; accounting scope required
Agent 2
Klaviyo
Branded tracking email dispatch triggered by Shopify fulfilment event
Private API key (server-side)
Free tier supports API; paid plan required above 500 contacts
Agent 3
Before you connect anything: sandbox-test every integration using non-production credentials before switching to live API keys or tokens. Use Shopify's development store, ShipStation's test mode, Linnworks staging environment, Xero's demo company, Klaviyo's test profile suppression list, and a dedicated #fulfilment-test Slack channel. No production data should transit the automation until all three agents pass QA in staging.
Integration and API SpecPage 1 of 4
FS-DOC-05Technical

02Per-tool integration detail

Shopify

Acts as both the automation trigger (paid order webhook) and the write-back destination (fulfillment status and tracking number). Used by Agent 1 (Order Routing) and Agent 2 (Label and Despatch).

Auth method
OAuth 2.0 via a Shopify custom app installed on the store. The custom app generates an Admin API access token. Store this token in the credential store as SHOPIFY_API_TOKEN. Never hardcode.
Required scopes
read_orders, write_orders, read_fulfillments, write_fulfillments, read_inventory, read_products
Webhook setup
Subscribe to the orders/paid topic via the Shopify Admin API (POST /admin/api/2024-01/webhooks.json). Set format to JSON. The delivery URL is the automation platform's inbound webhook endpoint for Agent 1. Enable HMAC signature validation: Shopify signs each payload with the app's shared secret using SHA-256. The orchestration layer must compute HMAC-SHA256 over the raw request body using SHOPIFY_WEBHOOK_SECRET and compare to the X-Shopify-Hmac-Sha256 header before processing. Reject any request where the signature does not match. Also subscribe to fulfillments/create to trigger Agent 3.
Required configuration
Store the shop domain (e.g. yourcompany.myshopify.com) as SHOPIFY_SHOP_DOMAIN. Store the API version as SHOPIFY_API_VERSION (currently 2024-01). Fulfilment location ID must be stored as SHOPIFY_LOCATION_ID (retrieve via GET /admin/api/2024-01/locations.json and store the relevant location). Do not hardcode any of these values.
Rate limits
Shopify REST Admin API: 40 requests per app per store per 10 seconds (leaky bucket, refills at 2 requests/second). At ~110 orders/day the peak burst is well below the limit. Throttling is not required at current volume, but the orchestration layer should honour the Retry-After header on any 429 response.
Constraints
Webhook payloads are delivered at least once; idempotency logic must prevent duplicate label creation. Use the Shopify order ID as the idempotency key. HTTPS endpoint required for webhook delivery. Webhooks time out after 5 seconds; the orchestration layer must return HTTP 200 immediately and process asynchronously.
// Inbound webhook payload (orders/paid) - key fields
order.id              -> string  // Shopify order ID, used as idempotency key
order.line_items[]    -> array   // SKU, quantity, variant_id per line
order.shipping_address -> object // name, address1, city, province_code, zip, country_code
order.total_price     -> string  // order total in store currency
// Write-back (PUT /admin/api/2024-01/orders/{id}/fulfillments.json)
fulfillment.tracking_number -> string
fulfillment.tracking_company -> string
fulfillment.tracking_url    -> string
fulfillment.location_id     -> string  // from SHOPIFY_LOCATION_ID
Linnworks

Provides real-time inventory data for the stock check in Agent 1 and receives pick-list creation instructions after an order is confirmed in stock.

Auth method
Linnworks Open API uses an application token flow. POST to https://api.linnworks.net/api/Auth/AuthorizeByApplication with ApplicationId, ApplicationSecret, and Token. The response contains a SessionToken valid for 24 hours. The orchestration layer must re-authenticate automatically before expiry. Store ApplicationId as LINNWORKS_APP_ID, ApplicationSecret as LINNWORKS_APP_SECRET, and Token as LINNWORKS_APP_TOKEN.
Required scopes
Linnworks uses application-level permissions set at app registration. The registered app must have: Stock (read), Orders (read/write), and Inventory Locations (read). No per-call scope strings are passed; permissions are defined on the Linnworks developer portal for the registered application.
Webhook / trigger setup
Linnworks does not natively push webhooks for stock changes. Agent 1 queries Linnworks synchronously on each order event. No inbound webhook configuration is required on the Linnworks side.
Required configuration
Store the Linnworks API base URL as LINNWORKS_API_BASE (https://api.linnworks.net). Store the default warehouse location GUID as LINNWORKS_LOCATION_GUID (retrieve via GET /api/Inventory/GetStockLocations). SKU values in Shopify line_items must map exactly to Linnworks SKU strings; this alignment is a pre-go-live prerequisite documented in the implementation notes.
Rate limits
Linnworks Open API enforces a rate limit of 150 requests per minute per session. At 110 orders/day with an average of 2 SKUs per order, peak usage is approximately 5 to 8 requests per minute during busy periods. Throttling is not required at current volume, but the orchestration layer should implement a 1-second minimum delay between sequential stock-check calls to avoid burst spikes.
Constraints
SessionToken expiry must be handled gracefully: if a 401 is returned, re-authenticate and retry the failed call once before raising an error. SKU mismatches return a zero-quantity result, not an error; the orchestration layer must treat a zero-quantity response as an out-of-stock flag, not a system fault.
// Stock check request (POST /api/Stock/GetStockItemsFull)
request.body.searchType -> 'SKU'
request.body.searchValue -> line_item.sku   // from Shopify order
// Stock check response - key fields
response[0].StockItemId          -> string  // internal Linnworks ID
response[0].Locations[0].Available -> number // available quantity
// Pick list creation (POST /api/Orders/CreateOrders)
order.ReferenceNum  -> shopify_order_id
order.ItemSKU       -> line_item.sku
order.Quantity      -> line_item.quantity
Slack

Receives exception alerts from Agent 1 when one or more SKUs on an order are out of stock. The fulfilment manager reviews the alert and acts manually.

Auth method
OAuth 2.0 using a Slack app Bot Token (xoxb-...). Install the Slack app to the workspace and grant it the required scopes. Store the Bot Token as SLACK_BOT_TOKEN.
Required scopes
chat:write, chat:write.public, incoming-webhook (if using the webhook method as fallback)
Webhook / trigger setup
Use the Slack Web API method chat.postMessage. Target the designated exception channel by storing the channel ID (not the name) as SLACK_EXCEPTION_CHANNEL_ID. Optionally configure an Incoming Webhook URL as SLACK_WEBHOOK_URL for a simpler fallback. Messages should include order ID, affected SKUs, quantities requested, and a direct link to the Shopify order.
Required configuration
SLACK_EXCEPTION_CHANNEL_ID must be stored in the credential store. The Slack app must be invited to the target channel. Message formatting should use Block Kit JSON for readability; store the Block Kit template as a configuration variable, not hardcoded in the workflow step.
Rate limits
Slack Web API Tier 3 limit: 50 requests per minute for chat.postMessage. At current exception rates (estimated under 5 to 10 out-of-stock flags per day) this limit is far from being reached. No throttling needed.
Constraints
Slack message delivery is best-effort; the orchestration layer must log the alert payload locally before attempting the Slack call so the exception is not silently lost if the Slack API is unavailable. Do not store PII (customer name, address) in the Slack message; include only order ID, SKUs, and a Shopify admin link.
// Outbound message payload (chat.postMessage)
channel     -> SLACK_EXCEPTION_CHANNEL_ID
text        -> 'Out-of-stock exception: Order #' + order.id
blocks[].fields[].text -> sku, quantity_ordered, quantity_available
blocks[].actions[].url -> 'https://' + SHOPIFY_SHOP_DOMAIN + '/admin/orders/' + order.id
ShipStation

Receives confirmed order data from Agent 2, applies carrier selection rules, generates the shipping label, and returns the tracking number and label PDF URL.

Auth method
HTTP Basic Authentication over HTTPS. Credentials are API Key (username) and API Secret (password) encoded as Base64 in the Authorization header. Store as SHIPSTATION_API_KEY and SHIPSTATION_API_SECRET.
Required scopes
ShipStation uses key-level permissions set in the account. The API key must have: Orders (read/write), Shipments (read/write), Carriers (read), and Stores (read). Configure these in the ShipStation account under Settings > API Settings.
Webhook / trigger setup
ShipStation is called outbound by Agent 2; it does not push webhooks into the automation for this flow. ShipStation does support webhook subscriptions (e.g. SHIP_NOTIFY) but these are not required for the current build.
Required configuration
Store the ShipStation store ID linked to the Shopify channel as SHIPSTATION_STORE_ID (GET /stores). Store each carrier service code as a configuration map: SHIPSTATION_CARRIER_RULES (JSON object mapping weight bands and destination zones to carrier_code and service_code strings). Do not hardcode carrier or service codes in workflow steps. Store the default warehouse origin address ID as SHIPSTATION_WAREHOUSE_ID.
Rate limits
ShipStation REST API: 40 requests per 60-second window (rate limit header X-Rate-Limit-Remaining returned on every response). At 110 orders/day the peak rate is approximately 2 to 4 label creation calls per minute during batch peaks. No throttling is required at current volume, but the orchestration layer must inspect X-Rate-Limit-Remaining and pause if it drops below 5.
Constraints
Label creation is billable per label; duplicate calls on the same order ID will create and charge for additional labels. The orchestration layer must check for an existing shipment on the ShipStation order before calling createLabel. Always retrieve the label PDF URL from the response for logging; do not re-request it separately. Carrier service availability depends on account configuration; the carrier rules map must be reviewed when carrier contracts change.
// Create order + label request (POST /shipments/createlabel)
request.orderId         -> shopify_order_id   // used as externalOrderId
request.carrierCode     -> from SHIPSTATION_CARRIER_RULES lookup
request.serviceCode     -> from SHIPSTATION_CARRIER_RULES lookup
request.weight.value    -> order.total_weight_grams
request.shipTo.name     -> order.shipping_address.name
request.shipTo.street1  -> order.shipping_address.address1
request.shipTo.city     -> order.shipping_address.city
request.shipTo.state    -> order.shipping_address.province_code
request.shipTo.postalCode -> order.shipping_address.zip
request.shipTo.country  -> order.shipping_address.country_code
// Label creation response - key fields
response.trackingNumber  -> string  // passed to Shopify and Xero
response.labelUrl        -> string  // PDF label for warehouse printing
response.shipmentCost    -> number  // USD, passed to Xero
response.carrierCode     -> string  // confirmed carrier
Xero

Receives the shipment cost from Agent 2 and posts it as a line item against the corresponding sales invoice or order in Xero.

Auth method
OAuth 2.0 using the Xero connected app with PKCE flow. The access token expires after 30 minutes; the orchestration layer must use the refresh token (valid 60 days) to obtain a new access token automatically. Store as XERO_CLIENT_ID, XERO_CLIENT_SECRET, XERO_ACCESS_TOKEN, XERO_REFRESH_TOKEN, and XERO_TENANT_ID.
Required scopes
openid, profile, email, accounting.transactions, accounting.contacts.read
Webhook / trigger setup
Xero is called outbound by the orchestration layer after label creation. No inbound webhook from Xero is used in this flow.
Required configuration
Store the shipping cost account code as XERO_SHIPPING_ACCOUNT_CODE (the nominal ledger code for freight/postage in the Xero chart of accounts, e.g. '420'). Store the tax rate type as XERO_TAX_TYPE (e.g. 'NONE' or 'TAX001' depending on jurisdiction). The Xero contact for the carrier must be pre-created and its ContactID stored as XERO_CARRIER_CONTACT_ID. Do not hardcode account codes or contact IDs.
Rate limits
Xero API: 60 calls per minute per organisation; daily limit of 5,000 calls. At one Xero write per order (110/day) usage is minimal. No throttling required at current volume. The orchestration layer must handle 429 responses with a 30-second backoff before retrying.
Constraints
Xero access tokens expire after 30 minutes; token refresh logic is mandatory and must be tested explicitly. Duplicate invoice line items can distort accounts; use the Shopify order ID as a reference field on each Xero line item and check for its existence before posting. All monetary amounts must be passed as numbers with two decimal places.
// POST /api.xro/2.0/Invoices (add line item to existing invoice)
Invoice.InvoiceNumber  -> shopify_order_id   // used to locate the invoice
LineItem.Description   -> 'Shipping: ' + response.carrierCode + ' ' + response.trackingNumber
LineItem.Quantity      -> 1
LineItem.UnitAmount    -> shipstation_response.shipmentCost  // number, 2dp
LineItem.AccountCode   -> XERO_SHIPPING_ACCOUNT_CODE
LineItem.TaxType       -> XERO_TAX_TYPE
Klaviyo

Receives a trigger event from the orchestration layer when Shopify fires the fulfillments/create webhook. Sends the branded tracking email to the customer using a pre-built Klaviyo flow.

Auth method
Klaviyo Private API Key passed in the Authorization header as 'Klaviyo-API-Key <key>'. Store as KLAVIYO_PRIVATE_API_KEY. The public API key (site ID) is used only for client-side tracking and is not required here.
Required scopes
Klaviyo private API keys are not scoped by resource in the same way as OAuth; the key has full account access. Create a dedicated key named 'FullSpec-Fulfilment-Automation' and restrict it to Events (write) and Profiles (read/write) using Klaviyo's key permission settings (available from Klaviyo API Keys settings page under 'Scopes' when creating a new key).
Webhook / trigger setup
The orchestration layer calls the Klaviyo Events API (POST /api/events/) to create a custom event named 'Order Shipped'. A Klaviyo flow must be configured by the FullSpec team to trigger on this event. The flow's email template must include the following placeholder variables: {{ event.tracking_number }}, {{ event.tracking_url }}, {{ event.carrier_name }}, {{ event.estimated_delivery }}. The Klaviyo flow filter must be set to 'metric name equals Order Shipped'.
Required configuration
Store the Klaviyo metric name as KLAVIYO_METRIC_NAME ('Order Shipped'). Store the Klaviyo flow ID as KLAVIYO_FLOW_ID for reference and monitoring. The email template ID should be stored as KLAVIYO_TEMPLATE_ID. The customer email address used to identify the Klaviyo profile is sourced from the Shopify order (order.email); no separate profile lookup is required if the profile already exists.
Rate limits
Klaviyo Events API: 75 requests per second burst; sustained limit varies by plan but is sufficient for 110 events/day. No throttling required at current volume. The orchestration layer should implement a 200ms minimum interval between consecutive event calls to stay within burst limits during any edge-case batch replay.
Constraints
If the customer profile does not exist in Klaviyo, the event call will create it automatically using the email address; this is expected behaviour. The Klaviyo flow must not be set to 'Draft' status; confirm it is 'Live' before go-live. Unsubscribed profiles will not receive the email; this is correct and must not be treated as a delivery failure.
// POST /api/events/ (Klaviyo Events API v2024-02-15)
data.type                       -> 'event'
data.attributes.metric.name     -> KLAVIYO_METRIC_NAME  // 'Order Shipped'
data.attributes.profile.email   -> order.email
data.attributes.properties.tracking_number   -> shipstation_response.trackingNumber
data.attributes.properties.tracking_url      -> shopify_fulfillment.tracking_url
data.attributes.properties.carrier_name      -> shipstation_response.carrierCode
data.attributes.properties.estimated_delivery -> shipstation_response.estimatedDeliveryDate
data.attributes.properties.order_id          -> shopify_order_id
Integration and API SpecPage 2 of 4
FS-DOC-05Technical

03Field mappings between tools

The following tables define the exact field mappings at each agent handoff point. Field names are shown in monospace as they appear in the respective API payloads or credential store. All mappings are implemented in the orchestration layer; no manual data entry occurs at any handoff.

Agent 1 handoff: Shopify paid order to Linnworks stock check

Source tool
Source field
Destination tool
Destination field
Shopify
order.id
Linnworks
ReferenceNum
Shopify
order.line_items[n].sku
Linnworks
searchValue (per SKU loop)
Shopify
order.line_items[n].quantity
Linnworks
Quantity (compared to Locations[0].Available)
Shopify
order.line_items[n].variant_id
Linnworks
stored for reconciliation log only
Shopify
order.shipping_address.name
Linnworks
CustomerName (pick list header)
Shopify
order.created_at
Linnworks
ReceivedDate

Agent 1 handoff: Linnworks stock check result to Slack exception alert (out-of-stock path only)

Source tool
Source field
Destination tool
Destination field
Shopify
order.id
Slack
blocks[].fields[].text (Order ID)
Linnworks
response[n].StockItemId
Slack
blocks[].fields[].text (SKU reference)
Linnworks
searchValue (requested SKU)
Slack
blocks[].fields[].text (SKU)
Linnworks
Locations[0].Available
Slack
blocks[].fields[].text (available qty)
Shopify
order.line_items[n].quantity
Slack
blocks[].fields[].text (ordered qty)
Shopify
order.admin_graphql_api_id (derived URL)
Slack
blocks[].actions[].url (Shopify admin link)

Agent 2 handoff: Shopify order plus Linnworks confirmation to ShipStation label creation

Source tool
Source field
Destination tool
Destination field
Shopify
order.id
ShipStation
externalOrderId
Shopify
order.shipping_address.name
ShipStation
shipTo.name
Shopify
order.shipping_address.address1
ShipStation
shipTo.street1
Shopify
order.shipping_address.address2
ShipStation
shipTo.street2
Shopify
order.shipping_address.city
ShipStation
shipTo.city
Shopify
order.shipping_address.province_code
ShipStation
shipTo.state
Shopify
order.shipping_address.zip
ShipStation
shipTo.postalCode
Shopify
order.shipping_address.country_code
ShipStation
shipTo.country
Shopify
order.total_weight (grams)
ShipStation
weight.value (with units: grams)
Config
SHIPSTATION_CARRIER_RULES lookup
ShipStation
carrierCode
Config
SHIPSTATION_CARRIER_RULES lookup
ShipStation
serviceCode
Config
SHIPSTATION_STORE_ID
ShipStation
advancedOptions.storeId
Config
SHIPSTATION_WAREHOUSE_ID
ShipStation
advancedOptions.warehouseId

Agent 2 handoff: ShipStation label response to Shopify fulfilment write-back

Source tool
Source field
Destination tool
Destination field
ShipStation
response.trackingNumber
Shopify
fulfillment.tracking_number
ShipStation
response.carrierCode
Shopify
fulfillment.tracking_company
ShipStation
response.trackingUrl (if returned)
Shopify
fulfillment.tracking_url
Config
SHOPIFY_LOCATION_ID
Shopify
fulfillment.location_id
Shopify
order.line_items[n].id (all)
Shopify
fulfillment.line_items[n].id

Agent 2 handoff: ShipStation label response to Xero cost posting

Source tool
Source field
Destination tool
Destination field
Shopify
order.id
Xero
Invoice.InvoiceNumber (lookup key)
ShipStation
response.shipmentCost
Xero
LineItem.UnitAmount
ShipStation
response.carrierCode
Xero
LineItem.Description (partial)
ShipStation
response.trackingNumber
Xero
LineItem.Description (partial)
Config
XERO_SHIPPING_ACCOUNT_CODE
Xero
LineItem.AccountCode
Config
XERO_TAX_TYPE
Xero
LineItem.TaxType

Agent 3 handoff: Shopify fulfilment event to Klaviyo tracking email trigger

Source tool
Source field
Destination tool
Destination field
Shopify
order.email
Klaviyo
data.attributes.profile.email
Shopify
order.id
Klaviyo
data.attributes.properties.order_id
Shopify
fulfillment.tracking_number
Klaviyo
data.attributes.properties.tracking_number
Shopify
fulfillment.tracking_url
Klaviyo
data.attributes.properties.tracking_url
Shopify
fulfillment.tracking_company
Klaviyo
data.attributes.properties.carrier_name
ShipStation
response.estimatedDeliveryDate
Klaviyo
data.attributes.properties.estimated_delivery
Config
KLAVIYO_METRIC_NAME
Klaviyo
data.attributes.metric.name
Integration and API SpecPage 3 of 4
FS-DOC-05Technical

04Build stack and orchestration

Orchestration layout
Three discrete workflow scenarios on the automation platform, one per agent. Scenarios share a single credential store; no credentials are duplicated across scenarios. Each scenario is independently schedulable and can be paused or restarted without affecting the others.
Agent 1 trigger mechanism
Webhook (push). Shopify sends a POST to the automation platform's inbound webhook URL on the orders/paid event. HMAC-SHA256 signature validation runs as the first step in the scenario before any processing. No polling; latency is sub-second.
Agent 2 trigger mechanism
Internal call from Agent 1 (in-process handoff within the same run, or via a platform-native inter-scenario trigger). Fires only when Agent 1 confirms all SKUs are in stock. Not externally triggered.
Agent 3 trigger mechanism
Webhook (push). Shopify sends a POST on the fulfillments/create event. Signature validation applies identically to the Agent 1 webhook. This is a separate Shopify webhook subscription from the orders/paid subscription.
Credential store
All secrets are stored in the automation platform's native encrypted credential store (environment variables or secrets vault depending on platform). No credentials appear in scenario logic, module configuration fields, or logs. Rotation of any credential requires updating only the credential store entry; scenario logic does not change.
Idempotency
The Shopify order ID is used as the idempotency key across all three agents. Agent 2 checks ShipStation for an existing shipment on the order ID before creating a label. Agent 3 checks Klaviyo for a prior 'Order Shipped' event on the same order ID before firing the email event.
Logging
Each scenario writes a structured execution log entry on completion (success or failure) including: order ID, agent name, timestamp, outcome, and any error payload. Logs are retained for a minimum of 30 days in the platform's execution history.
Credential store reference, showing key names and placeholder value formats. Actual values are set during onboarding and never exposed in scenario configuration.
// CREDENTIAL STORE CONTENTS
// All values stored as encrypted environment variables in the automation platform

// Shopify
SHOPIFY_API_TOKEN          = 'shpat_xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
SHOPIFY_WEBHOOK_SECRET     = 'whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
SHOPIFY_SHOP_DOMAIN        = 'yourcompany.myshopify.com'
SHOPIFY_API_VERSION        = '2024-01'
SHOPIFY_LOCATION_ID        = '12345678901234'

// Linnworks
LINNWORKS_APP_ID           = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
LINNWORKS_APP_SECRET       = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
LINNWORKS_APP_TOKEN        = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
LINNWORKS_API_BASE         = 'https://api.linnworks.net'
LINNWORKS_LOCATION_GUID    = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'

// Slack
SLACK_BOT_TOKEN            = 'xoxb-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx'
SLACK_EXCEPTION_CHANNEL_ID = 'C0XXXXXXXXXXX'
SLACK_WEBHOOK_URL          = 'https://hooks.slack.com/services/T.../B.../xxxx'

// ShipStation
SHIPSTATION_API_KEY        = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
SHIPSTATION_API_SECRET     = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
SHIPSTATION_STORE_ID       = '123456'
SHIPSTATION_WAREHOUSE_ID   = '654321'
SHIPSTATION_CARRIER_RULES  = '{"0-500g-UK":"royalmail/rm24","501-2000g-UK":"royalmail/rm48","0-2000g-US":"ups/ups_ground"}'

// Xero
XERO_CLIENT_ID             = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
XERO_CLIENT_SECRET         = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
XERO_ACCESS_TOKEN          = '<rotated by platform, stored on refresh>'
XERO_REFRESH_TOKEN         = '<rotated by platform, stored on refresh>'
XERO_TENANT_ID             = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
XERO_SHIPPING_ACCOUNT_CODE = '420'
XERO_TAX_TYPE              = 'NONE'

// Klaviyo
KLAVIYO_PRIVATE_API_KEY    = 'pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
KLAVIYO_METRIC_NAME        = 'Order Shipped'
KLAVIYO_FLOW_ID            = 'XXXXXXXXXXX'
KLAVIYO_TEMPLATE_ID        = 'XXXXXXXXXXX'

05Error handling and retry logic

Unhandled exceptions must never fail silently. Every integration point must log the error payload, including the order ID and timestamp, before any retry or fallback path is taken. If a fallback is not possible, a Slack alert must be posted to the exception channel with enough context for the fulfilment team to act manually.
Integration
Scenario
Required behaviour
Shopify inbound webhook (Agent 1)
HMAC signature validation fails
Reject the request immediately with HTTP 200 (to prevent Shopify retries). Log the rejection with the raw X-Shopify-Hmac-Sha256 header value. Do not process the payload. Alert the FullSpec monitoring channel if more than 3 invalid signatures are received within 5 minutes.
Shopify inbound webhook (Agent 1)
Duplicate webhook delivery for the same order.id
Detect using the order ID idempotency check at the start of the scenario. Skip processing and return HTTP 200. Log the duplicate event for audit purposes. No retry needed.
Linnworks stock check (Agent 1)
Session token expired (401 response)
Re-authenticate immediately using LINNWORKS_APP_ID, LINNWORKS_APP_SECRET, and LINNWORKS_APP_TOKEN. Retry the failed stock-check call once with the new session token. If re-authentication fails, halt the scenario, log the error, and post a Slack alert with the order ID.
Linnworks stock check (Agent 1)
SKU not found in Linnworks (zero results)
Treat as an out-of-stock exception. Do not attempt label creation. Route to the Slack exception alert path with a note that the SKU was unrecognised. Log the unmatched SKU for the SKU alignment review process.
Linnworks stock check (Agent 1)
Linnworks API timeout or 5xx error
Retry up to 3 times with exponential backoff: 10 seconds, 30 seconds, 90 seconds. If all retries fail, post a Slack exception alert with the order ID and error detail. Do not route the order to label creation. Log the full error payload.
Slack exception alert (Agent 1)
Slack API unavailable or returns non-200
Retry once after 15 seconds. If the retry fails, write the exception details to the platform execution log and send a fallback notification to the FullSpec monitoring email (support@gofullspec.com) if a monitoring integration is configured. The exception must not be silently dropped.
ShipStation label creation (Agent 2)
Order already has an existing shipment (duplicate label risk)
Query GET /shipments?externalOrderId={order_id} before calling createLabel. If a shipment exists, retrieve the existing tracking number and proceed to the Shopify write-back step without creating a new label. Log the skip event.
ShipStation label creation (Agent 2)
Rate limit hit (429 response, X-Rate-Limit-Remaining = 0)
Pause scenario execution for the duration specified in the Retry-After response header (default 60 seconds if not present). Retry the call once after the pause. If the second attempt also returns 429, halt and post a Slack alert. Do not retry a third time without human confirmation.
ShipStation label creation (Agent 2)
Carrier service unavailable or invalid service code
Log the invalid service code and the order details. Do not create a label. Post a Slack exception alert with the order ID, destination country, and attempted carrier code so the fulfilment manager can create the label manually in ShipStation. Update SHIPSTATION_CARRIER_RULES if the rule set is incorrect.
Xero cost posting (Agent 2)
Access token expired (401 response)
Trigger the OAuth token refresh flow using XERO_REFRESH_TOKEN. Store the new access and refresh tokens in the credential store immediately. Retry the Xero API call once with the new access token. If the refresh itself fails, log the error and post a Slack alert; the shipping cost entry must be made manually in Xero.
Xero cost posting (Agent 2)
Invoice not found for the Shopify order ID
Log the missing invoice reference and the shipment cost. Post a Slack alert to the accounts channel (SLACK_EXCEPTION_CHANNEL_ID) with the order ID, shipment cost, and carrier. Do not create a new invoice automatically. The accounts team (Priya Nair) must match and post the cost manually.
Klaviyo event trigger (Agent 3)
Klaviyo API returns 429 (rate limit)
Wait 200 milliseconds and retry once. If the second attempt fails, retry after 5 seconds. If all three attempts fail, log the order ID and tracking number to the platform execution log so the email can be manually triggered from the Klaviyo flow debug panel. Post a Slack alert if more than 5 consecutive Klaviyo failures occur.
Klaviyo event trigger (Agent 3)
Customer profile is unsubscribed from marketing
Klaviyo will suppress the email delivery automatically; this is correct behaviour. The orchestration layer should not treat a suppressed send as an error. Log the Klaviyo API response status for audit. No retry or fallback email is required.
Shopify fulfilment write-back (Agent 2)
Shopify returns 422 (validation error on fulfillment create)
Log the full 422 response body including the errors array. Post a Slack alert with the order ID and the validation error message. The fulfilment coordinator must manually mark the order as fulfilled in Shopify and paste the tracking number. ShipStation label has already been created at this point; do not attempt to void it.
Integration and API SpecPage 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
Developer Handover Pack
Technical · Developer
View
Test and QA Plan
Quality · Developer
View