Hilo Customer Support - Design Document
Purpose: Problem in, action out - with context as the connective tissue.
Section 1: System Overview
What CS does - problem in, action out
A problem comes in. CS reasons over device, user, and policy context, and emits a single action - either internal (operational) or external (customer-facing).
Section 2: Current State - Tools Ecosystem
Today's CS stack. Freshdesk is the ticketing hub (Lucy AI + ticketing). Customer contact arrives via email or web form. The agent pulls context tab-by-tab from five info systems below, and dispatches actions through those same systems plus Jira-CM. No unified context view yet.
Customer contact lands in Freshdesk. The agent pulls context from five info systems (NetSuite, WooCommerce, Back Office Tool, Amplitude, Confluence) - tab by tab, no unified view - then dispatches an internal action (close ticket, escalate, agent note) or an external action (customer reply, refund, replacement, remote device action, account/subscription, Jira engineering ticket).
Section 3: Current State - Request Path
Freshdesk + manual lookups - what happens today, end-to-end, when a customer writes in. Identity threads through email only; context is gathered tab-by-tab. The diagram shows the round-trip for a typical "my cuff isn't syncing" ticket that becomes an RMA + refund.
Why this hurts:
- 5+ systems, no shared identity. Agent re-keys email/serial across Freshdesk, NetSuite, WooCommerce, Back Office Tool, Amplitude, and Confluence. Nothing auto-links.
- Context lives in the agent's head. Device telemetry, order history, prior RMAs - all pulled by hand, tab by tab, every time.
- Customer is the bottleneck. Many tickets stall waiting on the customer to find the serial number on the back of the device.
- Copy-paste is the integration layer. RMA #, addresses, refund amounts - all manually pasted between NetSuite, WooCommerce, and Freshdesk replies.
- Multi-day wall-clock for a "simple" case. 5–15 min of agent time per touch, plus return shipping, then a separate touch to refund or ship a replacement.
Section 4: Interim State - Onboarding Intercom + Shopify
The bridge between today and the ideal state. Intercom replaces Freshdesk as the CS hub, and Shopify is now connected - new customer order data flows in automatically. The four legacy tools (NetSuite, WooCommerce, Back Office Tool, Amplitude) still require manual tab-switching until the data lake is live. Confluence stays as the KB fallback.
Intercom is now the CS hub. Shopify customer and order data syncs in automatically (new customers). The four legacy tools below still require manual tab-switching for pre-Shopify customers - this is the gap the data lake closes in the ideal state.
What improved vs. Section 3:
- Intercom replaces Freshdesk. Customer messages arrive in Intercom. Replies go back via Intercom chat, not email.
- Shopify customers have pre-loaded context. For anyone who ordered via Shopify, the agent already has order, warranty, and subscription data in the panel. No NetSuite / WooCommerce lookup needed for them.
- Legacy customers still require manual tabs. Pre-Shopify customers still need NetSuite + WooCommerce lookups. Back Office Tool and Amplitude are still manual for all customers.
- RMA and refund actions still manual. No webhook-based fan-out yet - agent still copy-pastes RMA # and processes refunds through NetSuite / WooCommerce directly.
Section 5: Ideal State
Same shape as Section 4. The middle layer has two components: Data Retrieval continuously syncs context from every source into Intercom contact attributes so the agent never opens another tab; Action Handler receives Canvas Kit submits and dispatches them to the right backend API (NetSuite, WooCommerce, Jira-CM, Back Office Tool) in the background.
What changed vs. Section 4 (interim):
- Identity is implicit. Contact form attaches to the Shopify customer record at submission. No email threading, no manual lookups.
- Context arrives pre-loaded. Agent never opens Shopify, NetSuite, or the Hilo admin. The Canvas Kit panel already has device telemetry, order, warranty, and RMA history.
- Copilot answers policy inline. Agent asks in plain English, Copilot cites the Article. No SOP hunting in Confluence except for true edge cases.
- One submit = parallel fan-out. The Canvas Kit submit webhook creates the RMA, issues the refund / replacement, and sends the customer reply in one shot.
Section 6: Before / After - RMA + Refund
Same scenario throughout the doc: customer reports their cuff isn't syncing, agent determines RMA + refund is appropriate. Sixteen steps and ~30 minutes today. Eight steps and ~5 minutes after.
Section 7: Integration Spec - Intercom - NetSuite (RMA via Action Handler)
One integration written out end-to-end: trigger, payload, processing chain, NetSuite API call, error handling, and rollback. This is the Action Handler path from Section 5 - the agent clicks Submit in Canvas Kit, and this is what happens in the background.
Trigger
Agent selects case_action: "rma" in the Canvas Kit panel and clicks Submit. Intercom fires a synchronous POST /canvas/submit to the Action Handler. Response must arrive within 10 s or Intercom shows an error to the agent.
Input payload (from Intercom)
{
"context": {
"contact_id": "65abc123",
"conversation_id": "conv_789xyz",
"workspace_id": "qpmg9n5u"
},
"input_values": {
"case_action": "rma",
"priority": "high",
"agent_note": "Device stopped syncing after firmware update v2.1.4"
}
}
Processing chain
- Verify HMAC-SHA1 signature on
x-hub-signatureheader. Reject 401 if mismatch. GET /contacts/{contact_id}from Intercom API - pullemail,hilo_serial_number,hilo_warranty_statusfrom contact attributes.- Check warranty eligibility. If ineligible → return Canvas error with reason, stop processing.
- POST Return Authorization to NetSuite REST API (payload below).
- Receive
tranId(RMA number) from NetSuite response. PUT /contacts/{contact_id}- writehilo_rma_status: "Pending",hilo_rma_number,hilo_rma_netsuite_id,hilo_case_state: "RMA In Progress"back to Intercom.POST /conversations/{conversation_id}/reply- send customer message: "Your return has been initiated. RMA #: [tranId]. Please ship your device back within 14 days."- Return updated Canvas to Intercom - panel shows RMA number, status, and confirmation text.
NetSuite REST payload
POST https://{accountId}.suitetalk.api.netsuite.com/services/rest/record/v1/returnauthorization
Authorization: Bearer {oauth2_token}
{
"entity": { "id": "{netsuite_customer_id}" },
"memo": "RMA via Hilo Support - {agent_note}",
"tranDate": "{today_ISO}",
"itemList": {
"item": [{
"item": { "id": "{product_item_id}" },
"quantity": 1,
"description": "Hilo Band - Serial: {serial_number}",
"serialNumbers": "{serial_number}"
}]
},
"custbody_hilo_conversation_id": "{conversation_id}",
"custbody_hilo_serial": "{serial_number}",
"custbody_hilo_priority": "{priority}",
"custbody_hilo_agent_note": "{agent_note}"
}
// Response
{ "id": "12345", "tranId": "RMA-2024-00234", "status": "A" }
Error handling
| HTTP / Condition | Cause | Response |
|---|---|---|
401 / 403 |
Expired OAuth 2.0 token | Refresh client-credentials token, retry once. If still failing → Canvas: "Action failed - token error, contact engineering." |
404 |
Customer not found in NetSuite | Canvas: "Customer not found in NetSuite - verify email or use manual process." Do not continue. |
409 |
Duplicate RMA for this serial | Canvas: "An RMA already exists for this device: [existing RMA #]." Show existing number. |
429 |
NetSuite rate limit | Queue with exponential backoff (1 s → 2 s → 4 s). Respond to Canvas immediately with "Processing…". Update Canvas attributes on completion. |
500 / timeout >10 s |
NetSuite down | Respond to Intercom Canvas immediately (do not let Intercom time out). Push to dead-letter queue. Alert on-call via Slack. Canvas: "Action queued - RMA will be created within 30 min." |
Rollback
- At step 6,
hilo_rma_netsuite_idis written to the Intercom contact. This is the key for all rollback operations. - Canvas Kit shows a "Cancel RMA" button whenever
hilo_rma_status = "Pending". - Cancel flow:
DELETE /record/v1/returnauthorization/{netsuite_id}→ reset contact attributes (rma_status: "Cancelled",rma_number: "-",case_state: "Open") → log cancellation with timestamp and agent ID. - If the refund was already queued when the cancel fires, issue a compensating
DELETEon the NetSuite refund record using the same pattern.