FS-DOC-05Technical
Integration and API Spec
Returns and Reverse Logistics
[YourCompany.com] · Fulfilment Department · Prepared by FullSpec · [Today's Date]
This document is the authoritative technical reference for every integration point in the Returns and Reverse Logistics automation. It covers tool inventory, OAuth scopes, webhook configuration, field mappings, credential store layout, orchestration design, and error handling behaviour. FullSpec uses this specification to build and wire every connection. No integration should be created, modified, or promoted to production without consulting the definitions here first.
01Tool inventory
Tool
Role in automation
Auth method
Min plan required
Used by agents
Shopify
Return request trigger, eligibility lookup, refund issuance, inventory adjustment
OAuth 2.0 (Custom App / Admin API token)
Basic Shopify or above (Admin API access)
Agent 1, Agent 3
Google Sheets
Central returns tracking log: append rows, write back label URL and status
OAuth 2.0 (Google service account or user OAuth)
Free (Google Workspace or personal)
Agent 1, Agent 3
ShipStation
Return label creation, shipment tracking, delivery webhook
API key + API secret (HTTP Basic over HTTPS)
Starter plan or above (webhook support included)
Agent 1, Agent 2
Gmail
Send label email to customer, send refund confirmation email
OAuth 2.0 (Google user OAuth, send-on-behalf or service account with domain delegation)
Free (Google Workspace or personal Gmail)
Agent 2, Agent 3
Slack
Post warehouse inspection alert with inspection form link
OAuth 2.0 (Slack Bot Token, chat:write scope)
Free tier or above (incoming webhooks supported)
Agent 2
Xero
Create credit note matching Shopify refund amount and customer reference
OAuth 2.0 (PKCE flow, 60-minute access token with refresh)
Starter plan or above (Accounts Payable / Receivable API access)
Agent 3
Automation platform (orchestration layer)
Hosts all three agent workflows, manages credential store, routes events between tools, handles retries
Platform-native credential management
Subscription as scoped by FullSpec during build
All agents
Before you connect anything: all integrations must be validated in a sandbox or staging environment using non-production credentials before any production credential is entered into the automation platform. Shopify has a Partner sandbox. Xero provides a demo company. ShipStation supports test API keys. Gmail and Slack connections should be tested against a dedicated test account and a private test channel respectively. FullSpec will never connect a live production credential until sandbox tests pass in full.
Integration and API SpecPage 1 of 4
FS-DOC-05Technical
02Per-tool integration detail
Shopify
Shopify is the entry point for the entire workflow and also the execution surface for refunds and inventory adjustments. The automation subscribes to the returns/create webhook for intake and calls the Admin REST API (or GraphQL Admin API) for order lookups, refund creation, and inventory level updates.
Auth method
OAuth 2.0 via Custom App installed in the Shopify Admin. Generates a permanent Admin API access token. Store in the credential store as SHOPIFY_API_TOKEN. Never hardcode in workflow nodes.
Required scopes
read_orders, write_orders, read_returns, write_returns, read_products, write_products, read_inventory, write_inventory, read_customers
Webhook / trigger setup
Subscribe to the topic returns/create via POST /admin/api/2024-04/webhooks.json. Payload delivery is HTTPS POST to the automation platform inbound URL. Validate every inbound request using the X-Shopify-Hmac-SHA256 header against SHOPIFY_WEBHOOK_SECRET (HMAC-SHA256, base64-encoded). Reject any request where validation fails with HTTP 401.
Required configuration
SHOPIFY_STORE_DOMAIN stored in credential store (e.g. yourcompany.myshopify.com). SHOPIFY_API_VERSION pinned to 2024-04. Return policy window (days) stored as a workflow-level config variable, not hardcoded. Location ID for inventory adjustment stored as SHOPIFY_LOCATION_ID in credential store.
Rate limits
REST Admin API: 2 requests/second (leaky bucket, burst to 40). GraphQL: 1000 cost points/second. At 60 returns/month (approximately 2 per day peak), sustained API usage is well below the bucket limit. Throttling logic is recommended as a precaution: implement a 500ms delay between sequential API calls within a single run and retry on 429 with exponential backoff (see Section 05).
Hard constraints
Refund amounts must not exceed the original order total. The API will reject over-refund attempts with HTTP 422. Inventory adjustments require a valid location_id; if the business operates multiple warehouses, each location must be mapped before go-live. Shopify does not allow deletion of a return record via API; cancellations must be handled by updating return status only.
// Inbound webhook payload (returns/create)
{ "return": { "id": <return_id>, "order_id": <order_id>, "status": "open",
"return_line_items": [ { "id": <line_id>, "quantity": 1,
"fulfillment_line_item": { "line_item": { "sku": "SKU-001",
"product_id": <product_id> } } } ] } }
// Key fields extracted
return_id, order_id, customer_id, line_item_sku, quantity, reason_code
// Outbound: refund creation
POST /admin/api/2024-04/orders/{order_id}/refunds.json
{ "refund": { "currency": "USD", "notify": false,
"refund_line_items": [ { "line_item_id": <id>, "quantity": 1,
"restock_type": "return" } ],
"transactions": [ { "parent_id": <transaction_id>,
"amount": "<refund_amount>", "kind": "refund", "gateway": "<gateway>" } ] } }Google Sheets
Google Sheets holds the central returns tracking log. The automation appends a new row when a return is first received and writes back updated values (label URL, send timestamp, inspection outcome, refund amount, credit note ID) as the return progresses through each stage.
Auth method
OAuth 2.0 using a Google service account with the Sheets API enabled in Google Cloud Console. Download the service account JSON key and store as GOOGLE_SERVICE_ACCOUNT_JSON in the credential store. Share the target spreadsheet with the service account email (editor access).
Required scopes
https://www.googleapis.com/auth/spreadsheets
Webhook / trigger setup
Not applicable. Google Sheets is written to (not listened to) by the automation. No trigger is set on the sheet itself.
Required configuration
SHEETS_SPREADSHEET_ID stored in credential store. SHEETS_TAB_NAME set to 'Returns Log' (exact sheet tab name). Column header row must match the field mapping in Section 03 exactly. Spreadsheet must not use merged cells in the data range. Store the spreadsheet ID from the URL, not the sheet title.
Rate limits
Google Sheets API v4: 300 requests/minute per project, 60 requests/minute per user. At 60 returns/month (peak approximately 5 per day), the automation will generate at most 25 to 30 API calls per day across all write-back events. No throttling required at current volume, but implement retry on 429 with 5-second base delay.
Hard constraints
Rows must be appended using spreadsheets.values.append (not insert), preserving existing data. Do not use batchUpdate to insert rows as this can corrupt formula ranges. The service account must have editor (not viewer) access. If the sheet contains protected ranges, ensure the service account is included in the exception list.
// Append new return row
POST /v4/spreadsheets/{SHEETS_SPREADSHEET_ID}/values/Returns Log!A:O:append
{ "range": "Returns Log!A:O", "majorDimension": "ROWS",
"values": [ [ <return_id>, <order_id>, <customer_email>, <customer_name>,
<sku>, <reason_code>, <eligibility_status>, <timestamp_received>,
"", "", "", "", "", "", "" ] ] }
// Write-back: label URL and send timestamp (columns I, J)
PUT /v4/spreadsheets/{SHEETS_SPREADSHEET_ID}/values/Returns Log!I{row}:J{row}
{ "values": [ [ <label_url>, <label_sent_timestamp> ] ] }ShipStation
ShipStation handles return label creation and acts as the tracking event source. The automation calls the ShipStation REST API to create return shipments and retrieve label documents, then receives a webhook event when the shipment status changes to 'delivered'.
Auth method
HTTP Basic Authentication over HTTPS. Credentials are API Key and API Secret generated in the ShipStation account under Account Settings, API Settings. Store as SHIPSTATION_API_KEY and SHIPSTATION_API_SECRET in the credential store. Base64-encode as 'API_KEY:API_SECRET' for the Authorization header.
Required scopes
ShipStation uses key/secret pairs with no granular scope model. The key must be generated by a user with Manage Orders and Manage Shipments permissions. Restrict the API key to read/write on Orders and Shipments only via the ShipStation user role if role-based access is available on the account plan.
Webhook / trigger setup
Register a webhook in ShipStation under Account Settings, Webhooks for the event type SHIP_NOTIFY (shipment status update). Set the webhook URL to the automation platform inbound endpoint for Agent 2. ShipStation does not sign webhook payloads with HMAC; validate delivery by checking that the resource_url domain is api.shipstation.com before fetching shipment detail. Poll as a fallback if the webhook misses a delivery event (poll interval: 15 minutes, filter by shipmentStatus = 'delivered' and updatedAfter = last poll timestamp).
Required configuration
SHIPSTATION_CARRIER_CODE stored in credential store (e.g. 'stamps_com' or 'fedex'). SHIPSTATION_SERVICE_CODE stored in credential store (e.g. 'usps_first_class_mail'). SHIPSTATION_WAREHOUSE_ID stored in credential store. SHIPSTATION_RETURN_CONFIRMATION set to 'delivery' for delivery confirmation. Do not hardcode carrier or service codes in workflow nodes.
Rate limits
ShipStation API: 40 API calls per minute (sliding window). A rate limit header X-RateLimit-Remaining is returned on every response. At 60 returns/month, the automation makes at most 3 to 4 API calls per return (create shipment, get label, get tracking). Peak usage is well within limits. Implement a check on X-RateLimit-Remaining and pause for 60 seconds if the value drops below 5.
Hard constraints
Label retrieval returns a PDF binary or a URL depending on the endpoint used. Use GET /shipments/createlabel and store the label PDF URL, not the binary, to the Google Sheet. Return shipments must reference a valid orderId from Shopify. The ShipStation API does not support async label creation; the label URL is returned synchronously in the create response.
// Create return shipment
POST /orders/createshipment
{ "orderId": <shipstation_order_id>, "carrierCode": <SHIPSTATION_CARRIER_CODE>,
"serviceCode": <SHIPSTATION_SERVICE_CODE>, "confirmation": "delivery",
"shipDate": "<YYYY-MM-DD>", "returnLabel": true }
// Response
{ "shipmentId": <id>, "trackingNumber": "<tracking_no>",
"labelData": null, "labelUrl": "<label_pdf_url>" }
// Delivery webhook payload (SHIP_NOTIFY)
{ "resource_url": "https://ssapi.shipstation.com/shipments?shipmentId=<id>",
"resource_type": "SHIP_NOTIFY" }Gmail
Gmail sends two customer-facing emails: the return label dispatch email (Agent 2) and the refund confirmation email (Agent 3). Both use a stored HTML template with named placeholders substituted at runtime. Sending is done via the Gmail API using the authenticated user's address or a shared mailbox alias.
Auth method
OAuth 2.0 using a Google user OAuth grant (or a service account with domain-wide delegation for Google Workspace). Store the refresh token as GMAIL_REFRESH_TOKEN and the client credentials as GMAIL_CLIENT_ID and GMAIL_CLIENT_SECRET in the credential store. Access tokens are short-lived (1 hour); the orchestration layer must handle token refresh before each send.
Required scopes
https://www.googleapis.com/auth/gmail.send
Webhook / trigger setup
Not applicable. Gmail is used as an outbound send tool only. No inbound trigger or Gmail watch is configured for this automation.
Required configuration
GMAIL_FROM_ADDRESS stored in credential store (e.g. returns@yourcompany.com). GMAIL_LABEL_TEMPLATE_ID stored in credential store (Google Drive file ID of the label email HTML template). GMAIL_CONFIRMATION_TEMPLATE_ID stored in credential store (file ID of the confirmation email HTML template). Template placeholders: {{customer_name}}, {{order_id}}, {{label_url}}, {{refund_amount}}, {{return_reason}}. Do not hardcode template content in workflow nodes.
Rate limits
Gmail API: 250 quota units per user per second; sending a message costs 100 units. This permits approximately 2 sends per second. At 60 returns/month (2 sends per return = 120 sends/month), usage is negligible. No throttling required. Implement retry on 429 or 503 with 10-second base delay.
Hard constraints
The authenticated Gmail account must have send permission for the GMAIL_FROM_ADDRESS alias. If returns@yourcompany.com is a Google Group or shared alias, the sending account must be a member with send-as rights. Attachments are not used; the label is sent as a URL link in the email body, not as a binary attachment, to avoid Gmail attachment size limits.
// Send label email (Agent 2)
POST /gmail/v1/users/me/messages/send
{ "raw": "<base64url-encoded RFC 2822 message>",
// Message headers: To: <customer_email>, From: <GMAIL_FROM_ADDRESS>,
// Subject: Your return label for order #{{order_id}}
// Body: HTML template with {{customer_name}}, {{label_url}} substituted }
// Send confirmation email (Agent 3)
// Subject: Your refund for order #{{order_id}} has been processed
// Body: HTML template with {{customer_name}}, {{order_id}},
// {{refund_amount}}, {{return_reason}} substitutedSlack
Slack receives a single outbound alert from Agent 2 when a return shipment is marked delivered. The message is posted to the warehouse inspection channel and includes the order number, return reason, and a direct link to the inspection outcome form. The warehouse operative's form submission is the trigger for Agent 3.
Auth method
OAuth 2.0 Bot Token. Install the automation as a Slack App in the workspace. Store the bot token as SLACK_BOT_TOKEN in the credential store (format: xoxb-...). The bot must be invited to the target channel before it can post.
Required scopes
chat:write, chat:write.public (if posting to public channels without joining)
Webhook / trigger setup
Outbound only. The automation calls the Slack Web API chat.postMessage method. No inbound Slack event subscription is required for this automation. The inspection form linked in the Slack message is a separate web form (hosted externally or via a form tool) whose submission triggers Agent 3 via a webhook into the orchestration layer.
Required configuration
SLACK_BOT_TOKEN stored in credential store. SLACK_WAREHOUSE_CHANNEL_ID stored in credential store (use the channel ID, not the channel name, to avoid breakage if the channel is renamed). SLACK_INSPECTION_FORM_URL stored in credential store (the URL of the inspection form with order_id passed as a query parameter). Do not hardcode channel IDs or form URLs in workflow nodes.
Rate limits
Slack Web API: tier 3 methods (chat.postMessage) allow 1 request/second. At 60 alerts/month (approximately 2 per day), usage is far below the limit. No throttling required. Implement retry on 429 using the Retry-After header value.
Hard constraints
Messages must not contain unformatted URLs as plain text in production; use Slack Block Kit with a 'Button' action element linking to the inspection form for reliable rendering. The bot token must not be a user token (xoxp-); only bot tokens are permitted in the credential store. If the warehouse channel is private, the bot must be explicitly invited.
// chat.postMessage payload
POST https://slack.com/api/chat.postMessage
{ "channel": <SLACK_WAREHOUSE_CHANNEL_ID>,
"blocks": [
{ "type": "section", "text": { "type": "mrkdwn",
"text": "*Return arrived* — Order #{{order_id}}\nReason: {{return_reason}}" } },
{ "type": "actions", "elements": [
{ "type": "button", "text": { "type": "plain_text",
"text": "Submit inspection outcome" },
"url": "{{SLACK_INSPECTION_FORM_URL}}?order_id={{order_id}}" } ] }
] }Xero
Xero receives a single API call from Agent 3 to create a credit note matching the Shopify refund. The credit note uses the customer's Xero contact record (matched by email), the refund line amount, and the configured revenue account code. No manual finance entry is required after this step.
Auth method
OAuth 2.0 with PKCE (Authorization Code flow). Access tokens expire after 60 minutes and must be refreshed using the refresh token. Store XERO_CLIENT_ID, XERO_CLIENT_SECRET, XERO_REFRESH_TOKEN, and XERO_TENANT_ID in the credential store. The orchestration layer must refresh the access token before each Xero API call and update XERO_REFRESH_TOKEN with the new value returned.
Required scopes
openid, profile, email, accounting.transactions, accounting.contacts.read, offline_access
Webhook / trigger setup
Not applicable. Xero is called outbound only to create the credit note. No Xero webhook subscription is used in this automation.
Required configuration
XERO_TENANT_ID stored in credential store (the organisation's Xero tenant identifier, visible after OAuth consent). XERO_ACCOUNT_CODE stored in credential store (the revenue account code used for returns credit notes, e.g. '200'). XERO_TAX_TYPE stored in credential store (e.g. 'NONE' or 'OUTPUT2' depending on the account's tax configuration). If the Xero account uses tracking categories, XERO_TRACKING_CATEGORY_ID and XERO_TRACKING_OPTION_ID must also be stored. Do not hardcode account codes or tax types.
Rate limits
Xero API: 60 calls/minute per tenant, 5000 calls/day per tenant. At 60 credit notes/month, the automation uses 60 Xero API calls/month for credit note creation plus a small number for contact lookups. Usage is negligible. No throttling required. Implement retry on 429 with 60-second delay.
Hard constraints
The Xero contact must exist before the credit note can be created; the automation must look up the contact by email first and handle the case where no contact is found (create a minimal contact record or route to a human exception). Credit notes in Xero cannot be deleted via API once approved; the automation must only approve the credit note after confirming the Shopify refund amount is finalised. Multi-currency organisations require CurrencyCode to be set on the credit note; confirm whether the Xero organisation uses multi-currency before go-live.
// Contact lookup by email
GET /api.xro/2.0/Contacts?where=EmailAddress=="{{customer_email}}"
// Response: extract Contacts[0].ContactID
// Create credit note
POST /api.xro/2.0/CreditNotes
{ "Type": "ACCRECCREDIT", "Contact": { "ContactID": "<contact_id>" },
"Date": "<YYYY-MM-DD>", "LineAmountTypes": "Exclusive",
"LineItems": [ { "Description": "Return credit — Order #{{order_id}}",
"Quantity": 1, "UnitAmount": <refund_amount>,
"AccountCode": <XERO_ACCOUNT_CODE>, "TaxType": <XERO_TAX_TYPE> } ],
"Reference": "Shopify Order #{{order_id}}",
"Status": "AUTHORISED" }Integration and API SpecPage 2 of 4
FS-DOC-05Technical
03Field mappings between tools
The tables below define every field handoff between tools at each agent boundary. Use exact field names as shown (monospace). Any deviation in field naming between tools must be resolved by explicit mapping in the orchestration layer, not by changing the source tool's field names.
Agent 1 handoff: Shopify returns/create webhook to Google Sheets (Returns Log) and ShipStation (create shipment)
Source tool
Source field
Destination tool
Destination field
Shopify
`return.id`
Google Sheets
`return_id` (col A)
Shopify
`return.order_id`
Google Sheets
`order_id` (col B)
Shopify
`order.email`
Google Sheets
`customer_email` (col C)
Shopify
`order.billing_address.first_name` + `last_name`
Google Sheets
`customer_name` (col D)
Shopify
`return.return_line_items[0].fulfillment_line_item.line_item.sku`
Google Sheets
`sku` (col E)
Shopify
`return.return_line_items[0].reason`
Google Sheets
`reason_code` (col F)
Automation layer (computed)
`eligibility_status` (pass/fail)
Google Sheets
`eligibility_status` (col G)
Automation layer (computed)
`ISO 8601 timestamp`
Google Sheets
`timestamp_received` (col H)
Shopify
`order.id`
ShipStation
`orderId`
Shopify
`order.shipping_address.*`
ShipStation
`shipTo.*`
Shopify
`order.email`
ShipStation
`customerEmail`
Agent 1 to Agent 2 handoff: ShipStation (label created) to Gmail (label email) and Google Sheets (write-back)
Source tool
Source field
Destination tool
Destination field
ShipStation
`labelUrl`
Gmail
`{{label_url}}` in email template body
ShipStation
`trackingNumber`
Google Sheets
`tracking_number` (col I)
ShipStation
`labelUrl`
Google Sheets
`label_url` (col J)
Automation layer (computed)
`ISO 8601 timestamp`
Google Sheets
`label_sent_timestamp` (col K)
Shopify (from Agent 1 context)
`order.email`
Gmail
`To:` header
Shopify (from Agent 1 context)
`order_id`
Gmail
`{{order_id}}` in email subject and body
Shopify (from Agent 1 context)
`customer_name`
Gmail
`{{customer_name}}` in email body
Agent 2 to Agent 3 handoff: ShipStation (delivery webhook) to Slack (warehouse alert), then inspection form submission to Shopify, Xero, Gmail, and Google Sheets
Source tool
Source field
Destination tool
Destination field
ShipStation (delivery webhook)
`resource_url` (resolve to `shipmentId`)
Slack
`{{order_id}}` in message block (looked up via Google Sheets by `tracking_number`)
Google Sheets (lookup)
`reason_code` (col F)
Slack
`{{return_reason}}` in message block
Inspection form (webhook payload)
`order_id`
Shopify
`order_id` for refund and inventory calls
Inspection form (webhook payload)
`inspection_outcome` (resalable/damaged/write_off)
Shopify
`restock_type` ('return' if resalable, 'no_restock' if damaged/write_off)
Inspection form (webhook payload)
`refund_amount`
Shopify
`transactions[].amount`
Shopify (refund response)
`refund.id`
Google Sheets
`shopify_refund_id` (col L)
Shopify (refund response)
`refund.transactions[0].amount`
Xero
`LineItems[0].UnitAmount`
Shopify (order context)
`order.email`
Xero
contact lookup `EmailAddress`
Shopify (order context)
`order_id`
Xero
`Reference` field on credit note
Xero (credit note response)
`CreditNotes[0].CreditNoteID`
Google Sheets
`xero_credit_note_id` (col M)
Automation layer (computed)
`ISO 8601 timestamp`
Google Sheets
`resolved_timestamp` (col N)
Inspection form (webhook payload)
`inspection_outcome`
Google Sheets
`inspection_outcome` (col O)
Integration and API SpecPage 3 of 4
FS-DOC-05Technical
04Build stack and orchestration
Orchestration layout
Three discrete workflows in the automation platform, one per agent. Agent 1 (Returns Intake Agent), Agent 2 (Dispatch and Monitoring Agent), and Agent 3 (Refund and Reconciliation Agent). Workflows are not chained directly; each is triggered by its own event so that a failure in one agent does not cascade to another. Shared state is carried through Google Sheets (as a persistent log) and through workflow execution context (order_id, return_id) passed via the orchestration layer's internal variables.
Shared credential store
All secrets and configuration values are stored in the automation platform's encrypted credential store. No secret or ID is hardcoded in any workflow node. Every credential reference in a workflow node uses the variable name defined in the code block below. Credential rotation must be performed via the credential store only; workflow nodes do not need to be re-edited after a rotation.
Agent 1 trigger mechanism
Webhook (push). Shopify sends a returns/create POST to the automation platform's inbound webhook URL for Agent 1. The platform validates the HMAC-SHA256 signature using SHOPIFY_WEBHOOK_SECRET before processing. If signature validation fails, the request is rejected and an error is logged. No polling fallback is used for intake.
Agent 2 trigger mechanism
Webhook (push) from ShipStation for delivery events, with a poll fallback. ShipStation fires SHIP_NOTIFY to the Agent 2 inbound URL. Because ShipStation does not sign webhook payloads, the platform validates the delivery by fetching the resource_url from api.shipstation.com and confirming the shipmentStatus equals 'delivered'. The poll fallback queries the ShipStation shipments endpoint every 15 minutes, filtered by updatedAfter the last poll timestamp, and fires the same delivery-handling logic if a delivered status is found that has not already been processed (de-duplicated by shipmentId stored in workflow state).
Agent 3 trigger mechanism
Webhook (push) from the inspection outcome form. When the warehouse operative submits the inspection form, the form tool posts a JSON payload to the Agent 3 inbound webhook URL. The payload includes order_id, inspection_outcome, and refund_amount. Signature validation uses a shared secret (INSPECTION_FORM_WEBHOOK_SECRET) passed as a header by the form tool. If signature is missing or invalid, the request is rejected and a Slack alert is sent to the operations channel.
Concurrency and idempotency
Each workflow must be idempotent: if the same trigger fires twice (duplicate webhook delivery), a second execution must detect the duplicate by checking the relevant ID (return_id, shipmentId, or order_id) against the Google Sheet status column before proceeding. If a row already exists with a non-empty label_url (Agent 1) or shopify_refund_id (Agent 3), the duplicate run must exit cleanly without writing a new row or issuing a second refund.
The credential store is the single source of truth for all secrets. If a credential is rotated (e.g. a new Xero refresh token after re-authorisation), update the credential store entry only. Do not paste secrets into workflow node configuration fields directly.
Credential store variable names: reference these exact keys in all workflow nodes
// Credential store contents (all values encrypted at rest)
// Shopify
SHOPIFY_API_TOKEN = <permanent Admin API access token>
SHOPIFY_STORE_DOMAIN = yourcompany.myshopify.com
SHOPIFY_API_VERSION = 2024-04
SHOPIFY_WEBHOOK_SECRET = <HMAC secret from Shopify webhook settings>
SHOPIFY_LOCATION_ID = <warehouse location ID from Shopify admin>
SHOPIFY_RETURN_WINDOW_DAYS = 30
// Google Sheets
GOOGLE_SERVICE_ACCOUNT_JSON = <service account key JSON, base64-encoded>
SHEETS_SPREADSHEET_ID = <spreadsheet ID from URL>
SHEETS_TAB_NAME = Returns Log
// ShipStation
SHIPSTATION_API_KEY = <API key from ShipStation account settings>
SHIPSTATION_API_SECRET = <API secret from ShipStation account settings>
SHIPSTATION_CARRIER_CODE = <e.g. stamps_com>
SHIPSTATION_SERVICE_CODE = <e.g. usps_first_class_mail>
SHIPSTATION_WAREHOUSE_ID = <warehouse ID from ShipStation>
// Gmail
GMAIL_CLIENT_ID = <OAuth 2.0 client ID from Google Cloud Console>
GMAIL_CLIENT_SECRET = <OAuth 2.0 client secret>
GMAIL_REFRESH_TOKEN = <long-lived refresh token from OAuth consent>
GMAIL_FROM_ADDRESS = returns@yourcompany.com
GMAIL_LABEL_TEMPLATE_ID = <Google Drive file ID of label email HTML template>
GMAIL_CONFIRMATION_TEMPLATE_ID = <Google Drive file ID of confirmation template>
// Slack
SLACK_BOT_TOKEN = xoxb-<workspace bot token>
SLACK_WAREHOUSE_CHANNEL_ID = <channel ID, not channel name>
SLACK_INSPECTION_FORM_URL = https://forms.yourcompany.com/returns-inspection
// Xero
XERO_CLIENT_ID = <Xero app client ID>
XERO_CLIENT_SECRET = <Xero app client secret>
XERO_REFRESH_TOKEN = <OAuth refresh token, updated on each token refresh>
XERO_TENANT_ID = <organisation tenant ID from Xero>
XERO_ACCOUNT_CODE = 200
XERO_TAX_TYPE = NONE
XERO_TRACKING_CATEGORY_ID = <if applicable, else leave blank>
XERO_TRACKING_OPTION_ID = <if applicable, else leave blank>
// Inspection form
INSPECTION_FORM_WEBHOOK_SECRET = <shared secret set in form tool webhook config>
05Error handling and retry logic
Every integration point has a defined failure behaviour. Unhandled exceptions must never fail silently. If a retry sequence is exhausted and the issue is not resolved automatically, the workflow must write an error status to the Google Sheet (col N set to 'ERROR: <reason>') and send an alert to the operations Slack channel with the order_id, the failing step, and the HTTP status or error message received.
Integration
Scenario
Required behaviour
Shopify webhook
Webhook received with invalid HMAC signature
Reject immediately with HTTP 401. Log the event with the raw payload hash. Do not process. Alert the operations channel with the source IP and timestamp.
Shopify API
429 rate limit on order lookup or refund call
Pause for 1 second (leaky bucket reset), then retry up to 5 times with 1-second incremental backoff. If still failing after 5 retries, write ERROR to the Google Sheet row and alert operations channel.
Shopify API
422 refund amount exceeds order total
Do not retry. Log the mismatch (refund_amount vs order_total) to Google Sheets. Route to human exception: alert operations channel with order_id and the two amounts for manual review.
ShipStation API
Label creation fails (4xx or 5xx response)
Retry up to 3 times with 10-second exponential backoff (10s, 20s, 40s). If all retries fail, set Google Sheet status to 'LABEL_FAILED', send alert to operations channel. Customer label email is not sent until label is confirmed. Manual fallback: operations team creates the label in ShipStation directly.
ShipStation webhook
Delivery webhook not received within 72 hours of label send
Poll fallback activates (15-minute interval). If delivery status is found via poll, proceed normally. If no delivery status after 10 days, update Google Sheet to 'DELIVERY_TIMEOUT' and alert operations channel for manual follow-up.
ShipStation webhook
Duplicate delivery webhook fires for same shipmentId
Check shipmentId against workflow state before processing. If already processed, exit cleanly with no action and log 'DUPLICATE_SKIPPED' at debug level.
Gmail send
429 or 503 on label or confirmation email send
Retry up to 3 times with 10-second base delay. If all retries fail, write 'EMAIL_FAILED' to Google Sheet (col K or confirmation status col). Alert operations channel. Manual fallback: operations team sends the email directly from the Gmail account.
Gmail OAuth
Access token expired (401 on send attempt)
Automatically refresh using GMAIL_REFRESH_TOKEN before retrying the send. If refresh also fails (refresh token expired or revoked), halt the workflow, alert operations channel, and require re-authorisation via the credential store before resuming.
Slack
chat.postMessage fails with 429 (rate limit)
Wait for the number of seconds specified in the Retry-After response header, then retry once. If the second attempt fails, write 'SLACK_ALERT_FAILED' to Google Sheet and alert the operations channel via email as a fallback (using the Gmail integration).
Xero API
Contact not found for customer email
Do not create the credit note. Write 'XERO_CONTACT_MISSING' to Google Sheet (col M). Alert operations channel and finance team with order_id and customer email. Manual fallback: finance officer creates the Xero contact and re-triggers the credit note creation step via a manual workflow re-run.
Xero OAuth
Refresh token expired or revoked (401 on API call)
Halt the Xero step immediately. Write 'XERO_AUTH_FAILED' to Google Sheet. Alert operations channel and finance team. Re-authorisation must be completed in the credential store (new OAuth consent required in Xero). The Shopify refund is already issued at this point; the credit note gap must be resolved manually by finance within the same business day.
Inspection form webhook
Payload received with missing or invalid signature
Reject with HTTP 401. Log the event. Alert operations channel. Do not trigger refund or credit note steps. Warehouse operative must re-submit the form.
Google Sheets API
Row append or write-back fails (4xx or 5xx)
Retry up to 3 times with 5-second base delay. If all retries fail, the workflow continues with the primary action (e.g. label creation or refund) but writes the error to an internal workflow log. Alert operations channel with the full row data so the record can be manually added to the sheet. Primary automation actions must not be blocked by a Sheets write failure.
Unhandled exceptions must never fail silently. Any error state not covered by the rows above must default to: write an error entry to the Google Sheet row (col N), send an alert to the operations Slack channel with the workflow name, the order_id, the step name, and the raw error message, and halt further processing of that run. Contact support@gofullspec.com if a new error scenario requires a defined handling rule to be added to this specification.
Integration and API SpecPage 4 of 4