Back to Returns & Reverse Logistics

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

Returns and Reverse Logistics Automation

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

This document is the primary technical reference for the FullSpec team building the Returns and Reverse Logistics automation. It covers the current-state process map, all three agent specifications with IO contracts, the end-to-end data flow, and the recommended build stack. The FullSpec team is responsible for all build, integration, configuration, and testing work described here. The process owner and their team are responsible for supplying confirmed API credentials, documented return policy rules, and access to all connected tools before the build starts.

01Process snapshot

Step
Name
Description
1
Receive Return Request
Staff monitor email, Shopify portal, and live chat to catch incoming return requests, then copy-paste details into a shared Google Sheet. Time cost: 8 min.
2
Check Order Eligibility
Rep opens Shopify, looks up the original order, and checks the purchase date against the return policy window. Non-qualifying orders are declined by reply email. Time cost: 6 min.
3
Log Return in Tracking Sheet
Approved return is entered into a shared Google Sheet with the order number, reason code, and customer contact details. Time cost: 5 min.
4
Generate Return Shipping Label
Staff log into ShipStation, create a return shipment manually, select the carrier, and email the prepaid label to the customer. Each label takes several minutes to generate. Time cost: 10 min.
5
Notify Customer of Label
A reply email is drafted and sent to the customer with the label attached and packing instructions. No automated follow-up if the label is unused. Time cost: 5 min.
6
Monitor Inbound Shipment
Staff check ShipStation periodically to see whether the return shipment has been scanned in transit. No alert fires when goods arrive at the warehouse. Time cost: 7 min.
7
Inspect Returned Goods
Warehouse staff physically inspect the returned item and decide whether it is resalable, damaged, or must go back to the supplier. Outcome communicated via a Slack message. Time cost: 8 min (human step, not replaced).
8
Update Inventory in Shopify
Based on the inspection outcome, a staff member manually adjusts stock levels in Shopify. Damaged or supplier-return stock is often left unadjusted for days. Time cost: 6 min.
9
Approve Refund Amount
Customer service rep reviews the inspection result and decides whether a full, partial, or no refund applies, referencing a policy document in Google Drive. Time cost: 5 min.
10
Issue Refund in Shopify
Rep processes the refund directly in Shopify. Often batched with other refunds at end of day, delaying the customer's credit by hours. Time cost: 4 min.
11
Create Credit Note in Xero
Finance manually creates a credit note in Xero to match the Shopify refund. Discrepancies between the two systems surface at month-end reconciliation. Time cost: 7 min.
12
Send Refund Confirmation to Customer
A confirmation email is sent to the customer once the refund has been processed. Sometimes forgotten, leading to support follow-ups. Time cost: 3 min.
Time cost summary: Total manual time per return cycle is 74 minutes. At 60 returns per month (~15 per week), this equates to approximately 7 manual staff hours per week and roughly 350 hours per year. The automation replaces steps 1 through 6 and steps 8 through 12 (10 of 12 steps). Step 7 (physical warehouse inspection) and exception handling for non-qualifying returns remain with a human by design. Steps 4, 6, and 11 are the three confirmed bottlenecks, accounting for the majority of reconciliation lag and customer escalations.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Returns Intake Agent

Captures every new return request from the Shopify returns portal, evaluates it against the configured eligibility rules (return window, product type, sale/bundle exclusions), logs the approved return as a new row in Google Sheets, and creates a prepaid return label in ShipStation. Handles the first four manual steps in full for all qualifying returns without any staff involvement. Non-qualifying requests are written to the sheet with status 'Exception' and routed for manual review.

Trigger
Shopify webhook: orders/returns/create (or returns portal submission event). Fires on every new return request submitted via the Shopify returns portal.
Tools
Shopify (read order, read returns, write return status), Google Sheets (append row), ShipStation (create return shipment, retrieve label URL)
Replaces steps
Steps 1, 2, 3, 4 — Receive Return Request, Check Order Eligibility, Log Return in Tracking Sheet, Generate Return Shipping Label
Estimated build
16 hours — Moderate complexity
// Input
shopify.return.id          // Shopify return ID
shopify.order.id           // Parent order ID
shopify.order.created_at   // Original purchase date (for window check)
shopify.order.email        // Customer email address
shopify.return.reason      // Reason code from portal
shopify.line_items[]       // Returned line items with SKU and quantity
shopify.customer.address   // Shipping address for label creation

// Eligibility check logic
return_window_days: 30     // Configurable policy rule
excluded_types: ['sale', 'bundle', 'digital']  // Configurable exclusion list

// Output (qualifying return)
sheets.row.order_id        // Written to Google Sheet column A
sheets.row.customer_email  // Written to Google Sheet column B
sheets.row.reason_code     // Written to Google Sheet column C
sheets.row.status          // 'Label Sent' or 'Exception'
sheets.row.timestamp       // ISO 8601 timestamp
shipstation.return_shipment_id  // ShipStation shipment record ID
shipstation.label_url      // Prepaid return label download URL

// Output (non-qualifying return)
sheets.row.status          // 'Exception — Manual Review'
sheets.row.exception_reason // e.g. 'Outside return window' or 'Excluded item type'
  • Shopify plan must be on Shopify Advanced or higher, or the Shopify Returns API must be confirmed accessible on the current plan before build starts. The webhook topic is orders/returns/create and requires the read_orders and write_returns OAuth scopes.
  • ShipStation API key and secret must be confirmed at the account level (not sub-user level) to allow shipment creation. The default carrier and service level must be agreed and hard-coded into the ShipStation call as a fallback; dynamic carrier selection is out of scope for this build.
  • Google Sheets: the returns tracking sheet must be pre-created with named column headers before the agent is connected. The sheet ID must be stored in the credential store. Appends must be idempotent — use shopify.return.id as the deduplication key before writing.
  • Eligibility rules must be fully documented and reviewed by the customer service team before the build starts. Implement rules as a configurable lookup table rather than hard-coded conditionals so they can be updated without rebuilding the flow.
  • If ShipStation label creation fails, the agent must write status 'Label Error' to the sheet and send an email alert to the process owner. Do not silently skip the label step.
  • Sale items, bundles, and partial returns each require a separate rule branch. Confirm the expected outcome for each edge case with the process owner in week one.
Dispatch and Monitoring Agent

Sends the return label to the customer via a templated Gmail message as soon as the Returns Intake Agent produces a label URL. Then monitors the ShipStation tracking feed for the inbound shipment. When ShipStation marks the shipment as delivered, this agent posts a structured Slack alert to the designated warehouse channel, including the order number, return ID, and a link to the inspection outcome form. This agent eliminates the manual label email and the periodic ShipStation polling that currently consumes 12 minutes per return.

Trigger
Internal: label URL available in the workflow context from Returns Intake Agent. Secondary trigger: ShipStation webhook (shipment_delivered event) for the return shipment ID.
Tools
Gmail (send label email via OAuth), ShipStation (subscribe to tracking webhook), Slack (post message to warehouse channel via Bot token)
Replaces steps
Steps 5, 6 — Notify Customer of Label, Monitor Inbound Shipment
Estimated build
10 hours — Moderate complexity
// Input (label dispatch)
shipstation.label_url       // From Returns Intake Agent output
shopify.order.id            // For reference in email body
shopify.customer.email      // Recipient address
shopify.return.reason       // Included in email body for context

// Output (label dispatch)
gmail.message_id            // Sent message ID for audit log
sheets.row.label_sent_at    // Timestamp written back to tracking sheet

// Input (delivery monitoring)
shipstation.tracking_event.status   // 'Delivered' triggers warehouse alert
shipstation.tracking_event.timestamp // Delivery confirmed datetime
shipstation.return_shipment_id       // Matched to open return record

// Output (warehouse alert)
slack.message.channel       // #warehouse-returns (or configured channel name)
slack.message.order_id      // Shopify order ID in message text
slack.message.return_id     // ShipStation return shipment ID
slack.message.inspection_form_url   // Link to Google Form or Typeform for inspection outcome
sheets.row.status           // Updated to 'Awaiting Inspection'
  • Gmail OAuth scope required: gmail.send. Use a dedicated service account or a shared mailbox, not a personal staff account, to avoid disruption if staff leave. Confirm the sending address with the process owner before build.
  • The label email template must be agreed before build. Store the template text in the credential/config store, not in the workflow definition, so it can be updated without a redeploy.
  • ShipStation supports outbound webhooks for shipment status events. Register the webhook against the specific return shipment ID created by the Returns Intake Agent. Do not use a global webhook listener for all shipments or you will receive noise from outbound orders.
  • The Slack alert must post to a named channel (e.g. #warehouse-returns) confirmed as actively monitored by warehouse staff. The Slack Bot token requires the chat:write scope. Confirm the channel ID (not just the display name) before build to prevent routing failures.
  • The inspection outcome form URL included in the Slack message must be a stable, pre-configured link. The form must collect at minimum: order ID, inspection result (Resalable, Damaged, Supplier Return), and refund recommendation (Full, Partial, None). Form responses must post to a known endpoint (Google Sheets or a webhook) that the Refund and Reconciliation Agent can listen to.
  • If the ShipStation delivery webhook does not fire within 14 days of label creation, write status 'Monitoring Timeout' to the sheet and send an alert to the process owner. This handles lost or abandoned parcels.
Refund and Reconciliation Agent

Triggered when a warehouse operative submits the inspection outcome form linked from the Slack alert. Reads the inspection result and applies the correct refund type in Shopify, adjusts the inventory level for the returned SKU based on the outcome (resalable restocks, damaged or supplier-return does not), creates a matching credit note in Xero, and sends the customer a confirmation email via Gmail. All status fields in the Google Sheet are updated to reflect completion. This agent replaces five manual steps and eliminates the primary source of month-end reconciliation errors between Shopify and Xero.

Trigger
Inspection outcome form submission (Google Form webhook or Typeform webhook) containing the order ID, inspection result, and refund recommendation.
Tools
Shopify (write refunds, write inventory), Xero (create credit notes via Accounting API), Gmail (send confirmation email), Google Sheets (update return row status)
Replaces steps
Steps 8, 9, 10, 11, 12 — Update Inventory in Shopify, Approve Refund Amount, Issue Refund in Shopify, Create Credit Note in Xero, Send Refund Confirmation to Customer
Estimated build
18 hours — Complex
// Input
form.order_id               // Shopify order ID from inspection form
form.return_id              // ShipStation return shipment ID
form.inspection_result      // 'Resalable' | 'Damaged' | 'SupplierReturn'
form.refund_recommendation  // 'Full' | 'Partial' | 'None'
form.partial_amount_usd     // Only present when recommendation is 'Partial'
form.operative_name         // For audit log

// Shopify refund logic
// Full: refund 100% of line item total + shipping if applicable
// Partial: refund form.partial_amount_usd against the order
// None: no refund issued; sheet updated to 'Write-Off'

// Inventory adjustment logic
// Resalable: increment inventory_quantity for SKU at default location
// Damaged or SupplierReturn: do not adjust inventory; flag for warehouse review

// Output (Shopify)
shopify.refund.id           // Created refund record ID
shopify.refund.amount_usd   // Actual refund amount processed
shopify.inventory.updated   // Boolean — true if stock adjusted

// Output (Xero)
xero.credit_note.id         // Created credit note ID
xero.credit_note.amount_usd // Must match shopify.refund.amount_usd exactly
xero.credit_note.reference  // Shopify order ID as Xero reference field
xero.credit_note.contact    // Matched by customer email to existing Xero contact

// Output (Gmail)
gmail.confirmation.message_id  // Sent confirmation email message ID

// Output (Google Sheets)
sheets.row.status           // 'Complete' | 'Write-Off' | 'Refund Error'
sheets.row.refund_amount    // Final refund amount
sheets.row.xero_credit_note_id // For reconciliation audit trail
sheets.row.completed_at     // ISO 8601 timestamp

// On approval (partial or exception path)
// If form.refund_recommendation is 'None', no Shopify refund is created.
// Sheet status is set to 'Write-Off' and an internal Slack message
// is posted to notify the customer service rep to close the case.
  • Shopify refund API requires the write_orders scope. Confirm that the connected Shopify admin account has this scope and that refund permissions are not restricted at the staff account level.
  • Xero API connection requires the OAuth 2.0 flow with the accounting.transactions scope (write). If the Xero organisation uses multi-currency or tracking categories, the credit note payload must include the correct CurrencyCode and TrackingCategories fields. Confirm both with the Finance Officer before the Xero step is built.
  • Xero contact matching: the agent attempts to match the Shopify customer email to an existing Xero contact. If no match is found, create a new Xero contact using the customer name and email from the Shopify order before creating the credit note. Do not leave the ContactID field blank.
  • Partial refund amounts must be validated against the original order total before submission to Shopify. If form.partial_amount_usd exceeds the refundable amount for the order, write status 'Refund Error' to the sheet and alert the process owner. Do not attempt an over-refund.
  • Inventory adjustment must use the correct location ID. Confirm the ShipStation returns destination maps to a single Shopify location. If the business operates multiple warehouse locations, additional location mapping is required before this step is reliable.
  • The Xero credit note and the Shopify refund must carry the same reference value (Shopify order ID) to enable automated reconciliation. This is non-negotiable for the Finance Officer's month-end process.
  • All three output write operations (Shopify refund, Xero credit note, Google Sheet update) must be wrapped in error handling. If any step fails, write the failure reason to the sheet and send an alert to support@gofullspec.com and the process owner. Do not partially complete a refund cycle without logging it.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow with field names and agent handoff points
// =========================================================
// TRIGGER: Shopify returns portal submission
// =========================================================
SHOPIFY_WEBHOOK :: orders/returns/create
  -> return.id              // e.g. 'ret_9182736450'
  -> order.id               // e.g. 'ord_1234567890'
  -> order.created_at       // ISO 8601 original purchase date
  -> order.email            // 'customer@example.com'
  -> return.reason          // e.g. 'defective' | 'wrong_item' | 'no_longer_needed'
  -> line_items[].sku       // e.g. 'SKU-ABC-001'
  -> line_items[].quantity  // e.g. 1
  -> customer.address       // Full shipping address object

// =========================================================
// AGENT 1: Returns Intake Agent
// =========================================================

// Step 1: Eligibility check against policy rules
SHOPIFY_READ :: GET /admin/api/2024-01/orders/{order.id}.json
  <- order.created_at       // Compare to NOW() minus return_window_days (30)
  <- line_items[].product_type  // Check against excluded_types[]

IF eligibility == false:
  SHEETS_APPEND :: status = 'Exception — Manual Review'
                   exception_reason = '<reason string>'
  HALT — exception path, human review required

// Step 2: Log qualifying return in Google Sheets
SHEETS_APPEND :: {
  col_A: order.id,
  col_B: order.email,
  col_C: return.reason,
  col_D: status = 'Processing',
  col_E: timestamp = NOW()
}
  -> sheets.row_index       // Row number for later updates

// Step 3: Create return shipment in ShipStation
SHIPSTATION_POST :: POST /shipments/createreturn
  payload: {
    orderNumber: order.id,
    customerEmail: order.email,
    shipTo: customer.address,
    carrierCode: 'usps',       // Configurable default carrier
    serviceCode: 'usps_first_class_mail'  // Configurable default service
  }
  <- shipstation.return_shipment_id
  <- shipstation.label_url

SHEETS_UPDATE :: row_index -> col_F: shipstation.return_shipment_id
                              col_G: shipstation.label_url
                              col_D: status = 'Label Created'

// =========================================================
// HANDOFF: Returns Intake Agent -> Dispatch and Monitoring Agent
// Context passed: order.id, order.email, return.reason,
//   shipstation.return_shipment_id, shipstation.label_url,
//   sheets.row_index
// =========================================================

// AGENT 2: Dispatch and Monitoring Agent

// Step 4: Send label email to customer via Gmail
GMAIL_SEND :: {
  to: order.email,
  subject: 'Your return label for order {order.id}',
  body: template('label_email', {label_url, order_id, reason})
}
  <- gmail.message_id

SHEETS_UPDATE :: row_index -> col_H: gmail.message_id
                              col_I: label_sent_at = NOW()
                              col_D: status = 'Label Sent'

// Step 5: Monitor ShipStation for delivery event
SHIPSTATION_WEBHOOK :: shipment_delivered
  filter: shipment_id == shipstation.return_shipment_id
  <- tracking_event.status      // 'Delivered'
  <- tracking_event.timestamp   // Delivery confirmed datetime

// Step 6: Post warehouse alert in Slack
SLACK_POST :: channel = '#warehouse-returns'
  payload: {
    text: 'Return delivered for order {order.id}. Please inspect and submit outcome.',
    inspection_form_url: 'https://forms.example.com/inspection?order_id={order.id}',
    return_id: shipstation.return_shipment_id
  }

SHEETS_UPDATE :: row_index -> col_D: status = 'Awaiting Inspection'
                              col_J: delivered_at = tracking_event.timestamp

// =========================================================
// HUMAN STEP: Warehouse operative inspects goods and submits form
// Form fields: order_id, return_id, inspection_result,
//   refund_recommendation, partial_amount_usd (conditional),
//   operative_name
// =========================================================

// HANDOFF: Dispatch and Monitoring Agent -> Refund and Reconciliation Agent
// Trigger: inspection form submission webhook fires
// Context passed: form.order_id, form.return_id,
//   form.inspection_result, form.refund_recommendation,
//   form.partial_amount_usd, sheets.row_index
// =========================================================

// AGENT 3: Refund and Reconciliation Agent

// Step 7: Issue refund in Shopify
IF refund_recommendation == 'Full':
  SHOPIFY_POST :: POST /admin/api/2024-01/orders/{order.id}/refunds.json
    payload: { refund_line_items: line_items[], shipping: true }
    <- shopify.refund.id
    <- shopify.refund.amount_usd

IF refund_recommendation == 'Partial':
  SHOPIFY_POST :: POST /admin/api/2024-01/orders/{order.id}/refunds.json
    payload: { amount: form.partial_amount_usd }
    <- shopify.refund.id
    <- shopify.refund.amount_usd

IF refund_recommendation == 'None':
  SHEETS_UPDATE :: col_D: status = 'Write-Off'
  SLACK_POST :: #cs-team alert: 'No refund issued for order {order.id}. Close case.'
  HALT

// Step 8: Adjust inventory in Shopify
IF inspection_result == 'Resalable':
  SHOPIFY_POST :: POST /admin/api/2024-01/inventory_levels/adjust.json
    payload: { inventory_item_id, location_id, available_adjustment: +quantity }
    <- shopify.inventory.updated = true

IF inspection_result IN ['Damaged', 'SupplierReturn']:
  shopify.inventory.updated = false  // No adjustment; flagged in sheet

// Step 9: Create credit note in Xero
XERO_LOOKUP :: GET /api.xro/2.0/Contacts?where=EmailAddress='{order.email}'
  <- xero.contact.ContactID   // Use existing or create new contact

XERO_POST :: POST /api.xro/2.0/CreditNotes
  payload: {
    Type: 'ACCREC',
    Contact: { ContactID: xero.contact.ContactID },
    Date: TODAY(),
    Reference: order.id,
    LineItems: [{ Description: 'Return refund — {order.id}',
                  Quantity: 1,
                  UnitAmount: shopify.refund.amount_usd,
                  AccountCode: '200' }]
  }
  <- xero.credit_note.id
  <- xero.credit_note.amount_usd   // Must == shopify.refund.amount_usd

// Step 10: Send refund confirmation email to customer
GMAIL_SEND :: {
  to: order.email,
  subject: 'Your refund for order {order.id} has been processed',
  body: template('refund_confirmation', {refund_amount, order_id})
}
  <- gmail.confirmation.message_id

// Step 11: Final Google Sheets update
SHEETS_UPDATE :: row_index -> {
  col_D: status = 'Complete',
  col_K: shopify.refund.id,
  col_L: shopify.refund.amount_usd,
  col_M: xero.credit_note.id,
  col_N: gmail.confirmation.message_id,
  col_O: completed_at = NOW()
}

// =========================================================
// END OF AUTOMATED FLOW
// Remaining human steps: physical inspection (Step 7 in process)
// and manual exception review for non-qualifying returns
// =========================================================
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation platform configured with one workflow per agent (three workflows total): Returns Intake, Dispatch and Monitoring, and Refund and Reconciliation. All API credentials are stored in a shared credential store within the platform, not hard-coded in individual workflow nodes. Workflows are version-controlled and named consistently (e.g. ria-returns-intake, dma-dispatch-monitoring, rra-refund-reconciliation) to match this document.
Webhook configuration
Three inbound webhook endpoints are required. (1) Shopify orders/returns/create webhook registered in the Shopify Partner admin, pointing to the Returns Intake Agent workflow. (2) ShipStation shipment_delivered webhook registered per return shipment ID at creation time, pointing to the Dispatch and Monitoring Agent. (3) Inspection outcome form submission webhook (Google Forms App Script or Typeform Connect endpoint), pointing to the Refund and Reconciliation Agent. All webhook endpoints must be secured with a shared secret or HMAC signature header and validated on receipt.
Templating approach
All customer-facing email bodies (label dispatch and refund confirmation) are stored as named templates in the platform credential/config store. Templates use simple variable substitution (e.g. {{order_id}}, {{label_url}}, {{refund_amount}}). Template content is not embedded in workflow node definitions so it can be updated by the process owner without a workflow change or redeployment.
Error logging
A dedicated error log table is maintained in a Supabase database (table name: returns_automation_errors). Every caught exception writes a row with: workflow_name, node_name, order_id, error_message, error_timestamp, and retry_attempted. An alert is sent to support@gofullspec.com and the process owner email when any error row is inserted. The Google Sheets tracking log is updated with status 'Refund Error' or 'Label Error' as appropriate so the process owner has visibility without accessing Supabase directly.
Testing approach
All three agents are built and tested in sandbox/staging environments first. Shopify test orders are used for intake and refund steps. ShipStation sandbox mode is used for label creation. Xero demo company is used for credit note creation. Gmail send is tested against an internal test mailbox before live customer addresses are used. The Slack alert is tested against a private #ria-test channel before switching to #warehouse-returns. End-to-end testing uses at least three qualifying returns (full refund, partial refund, resalable and damaged outcomes) and two non-qualifying returns (out-of-window, excluded item type) before go-live.
Estimated total build time
Returns Intake Agent: 16 hours. Dispatch and Monitoring Agent: 10 hours. Refund and Reconciliation Agent: 18 hours. End-to-end integration testing and QA: 4 hours. Total: 48 hours, consistent with the confirmed build effort. Delivery is structured across weeks 2 through 5, following the week-one discovery and credential confirmation stage.
Before the build starts, the FullSpec team requires: (1) documented return policy rules including edge cases for sale items, bundles, and partial returns; (2) confirmed API credentials for Shopify (admin-level, write_orders and write_returns scopes), ShipStation (account-level key), Xero (OAuth 2.0 with accounting.transactions write scope), Gmail (OAuth 2.0 with gmail.send scope), and Slack (Bot token with chat:write scope); (3) the Google Sheet ID for the returns tracking log; (4) confirmed Shopify location ID for inventory adjustments; and (5) confirmation of whether the Xero organisation uses multi-currency or tracking categories. Delays in any of these items will push the build timeline out. Contact the FullSpec team at support@gofullspec.com with any questions.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Returns & Reverse Logistics.

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