A client came to me with a problem most small Shopify store owners know well: they were spending 3-4 hours a day just answering the same support emails. Order status. Return requests. Refund questions. Same thing, over and over.
We built a lightweight Node.js integration that connects Claude directly to the Shopify Admin API. There is no third-party automation tools included and no bloated support platforms. Just Claude reading order data and responding like a smart support agent.
Here's exactly how we did it.
What the system does
When a customer sends a support message, the system:
- Parses the message to figure out what they want (order status or return/refund)
- Pulls the relevant order data from Shopify
- Feeds that data + the customer's message to Claude
- Sends Claude's response back to the customer
If the request is something Claude can't handle confidently, it flags it for a human.
The stack
- Node.js (Express)
- Shopify Admin REST API
- Anthropic Claude API (claude-sonnet-4-20250514)
- A simple webhook endpoint your email/chat tool can hit
Step 1: Set up your Shopify API access
Go to your Shopify admin > Settings > Apps and sales channels > Develop apps. Create a private app and give it these scopes:
> read_orders
> read_customers
Save your Admin API access token. You will need it shortly.
Step 2: Set up your Node.js project
mkdir shopify-claude-support
cd shopify-claude-support
npm init -y
npm install express @anthropic-ai/sdk axios dotenv
Create a .env file:
SHOPIFY_STORE=your-store.myshopify.com
SHOPIFY_ACCESS_TOKEN=your_shopify_token
ANTHROPIC_API_KEY=your_claude_api_key
PORT=3000
Step 3: Pull order data from Shopify
This function takes a customer email and fetches their most recent order, including line items and fulfillment status.
`// shopify.js
const axios = require('axios');
require('dotenv').config();async function getOrderByEmail(email) {
const url =https://${process.env.SHOPIFY_STORE}/admin/api/2024-01/orders.json;const response = await axios.get(url, {
headers: {
'X-Shopify-Access-Token': process.env.SHOPIFY_ACCESS_TOKEN,
'Content-Type': 'application/json'
},
params: {
email: email,
status: 'any',
limit: 5
}
});const orders = response.data.orders;
if (!orders || orders.length === 0) return null;// Return the most recent order
return orders[0];
}module.exports = { getOrderByEmail };
`
Step 4: Build the Claude response function
This is where the actual intelligence lives. We pass Claude the order details and the customer's message, and tell it exactly what it can and can't do.
// claude.js
const Anthropic = require('@anthropic-ai/sdk');
require('dotenv').config();const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
async function generateSupportResponse(customerMessage, orderData) {
const orderContext = orderData
?${i.name} (qty: ${i.quantity})
Order ID: ${orderData.name}
Status: ${orderData.financial_status} / ${orderData.fulfillment_status || 'unfulfilled'}
Items: ${orderData.line_items.map(i =>).join(', ')}
Total: ${orderData.currency} ${orderData.total_price}
Created: ${new Date(orderData.created_at).toDateString()}
Tracking: ${orderData.fulfillments?.[0]?.tracking_number || 'Not available yet'}
: 'No order found for this customer email.';const systemPrompt = `You are a helpful customer support agent for an online store.
You have access to the customer's order information. Use it to answer their question clearly and accurately.Rules:
- For order status questions: give a direct answer based on the data provided.
- For return or refund requests: tell them the return window is 30 days from delivery, and instruct them to reply with their reason. Do not process returns yourself.
- If you don't have enough information to answer confidently, say so and tell the customer a human agent will follow up within 24 hours.
- Keep responses short and friendly. No corporate fluff.
- Never make up order details that aren't in the data.`;
const userMessage =
Customer message: ${customerMessage}\n\nOrder data:\n${orderContext};const response = await client.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 500,
system: systemPrompt,
messages: [{ role: 'user', content: userMessage }]
});return response.content[0].text;
}module.exports = { generateSupportResponse };
A few things worth noting in the system prompt:
- We explicitly tell Claude what it can and cannot do. It collects the intent and hands off.
- The "never make up order details" line matters. Without it, Claude will sometimes fill gaps confidently with hallucinated data.
- The 24-hour human escalation clause keeps customers from being left hanging.
Step 5: Wire it together with a webhook endpoint
// index.js
const express = require('express');
const { getOrderByEmail } = require('./shopify');
const { generateSupportResponse } = require('./claude');
require('dotenv').config();const app = express();
app.use(express.json());app.post('/support', async (req, res) => {
const { customer_email, message } = req.body;if (!customer_email || !message) {
return res.status(400).json({ error: 'customer_email and message are required' });
}try {
const orderData = await getOrderByEmail(customer_email);
const reply = await generateSupportResponse(message, orderData);res.json({
reply,
order_found: !!orderData,
order_id: orderData?.name || null
});
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Something went wrong. Please try again.' });
}
});app.listen(process.env.PORT, () => {
console.log(Support server running on port ${process.env.PORT});
});
Testing it
Start the server:
node index.js
Send a test request:
curl -X POST http://localhost:3000/support \
-H "Content-Type: application/json" \
-d '{
"customer_email": "customer@example.com",
"message": "Hey, where is my order? I ordered 5 days ago."
}'
If the customer has an order in Shopify, Claude will pull the data and respond with the actual status and tracking info. If not, it will tell them a human will follow up.
What Claude handles well vs. what it doesn't
*Handles well:
*
- Order status questions ("Where is my package?")
- Basic return eligibility ("Can I return this?")
- Reformatting messy questions into clean answers
- Staying calm with frustrated customers
*Where you still need a human:
*
- Complex refund disputes
- Lost packages requiring carrier investigation
- Cases where the customer's email doesn't match the order
- Anything involving payment changes
For those cases, the system flags the conversation and drops it into a queue for manual review. We keep a simple escalation_needed boolean in the response for this purpose.
What this saved the client
Before: **~3.5 hours/day on support emails.
**After: ~40 minutes, mostly on escalated cases.
Claude handles roughly 70-75% of incoming tickets fully on its own. The rest either need a human or the customer email didn't match an order in the system.
Total build time was about two days including testing. Cost of running it is almost nothing; a few dollars a month in API calls at the volume this client gets.
What's next
A few things we're adding to this AI integration:
- Order lookup by order number (not just email) since customers often don't use the email they ordered with
- A basic classification step before hitting Claude, so obvious spam doesn't burn tokens
- Logging escalated tickets to a simple Notion database for the client to review
If you build something similar or have questions about the setup, drop a comment. Happy to help troubleshoot.
Top comments (0)