E-commerce Integration
Connect your Shopify, WooCommerce, or custom store. Pull order data into tickets automatically.
Overview
E-commerce support is fundamentally about orders. When a customer asks "Where's my order?", your agents need order details instantly. Dispatch Tickets lets you attach order context to every ticket.
Order Context
See order details alongside every ticket
Customer Lookup
Find orders by email or order number
Real-time Sync
Order status updates via webhooks
Multi-Store
One brand per store, unified dashboard
Order Context in Tickets
Use customFields to attach order data to tickets. This data appears in the dashboard and is searchable.
const ticket = await dispatch.tickets.create('br_abc123', {
title: 'Where is my order?',
body: 'I placed an order 5 days ago and it still has not arrived.',
customerEmail: '[email protected]',
customFields: {
// Order information
orderId: 'ORD-12345',
orderNumber: '#1042',
orderDate: '2024-01-10T10:30:00Z',
orderTotal: 149.99,
orderCurrency: 'USD',
orderStatus: 'shipped',
// Shipping information
shippingMethod: 'Standard Shipping',
trackingNumber: '1Z999AA10123456784',
trackingUrl: 'https://track.example.com/1Z999AA10123456784',
estimatedDelivery: '2024-01-18',
// Items (as JSON)
items: [
{ name: 'Blue Widget', sku: 'BW-001', quantity: 2, price: 49.99 },
{ name: 'Red Gadget', sku: 'RG-002', quantity: 1, price: 49.99 }
],
// Customer context
customerLifetimeValue: 1249.50,
customerOrderCount: 8,
customerSince: '2022-03-15'
}
});Recommended Fields
| Field | Type | Description |
|---|---|---|
| orderId | string | Internal order ID for API lookups |
| orderNumber | string | Customer-facing order number |
| orderStatus | string | pending, paid, shipped, delivered, refunded |
| trackingNumber | string | Carrier tracking number |
| trackingUrl | string | Direct link to tracking page |
| items | array | Line items with name, sku, quantity, price |
Shopify Integration
Connect Shopify to automatically enrich tickets with order data when customers email you.
Option 1: Webhook-Based (Recommended)
Listen for Dispatch webhooks and enrich tickets with Shopify data:
import { Shopify } from '@shopify/shopify-api';
// When a ticket is created, look up the customer's orders
app.post('/webhooks/dispatch', async (req, res) => {
const event = req.body;
if (event.type === 'ticket.created') {
const { ticket } = event.data;
// Search Shopify for orders by customer email
const orders = await shopify.order.list({
email: ticket.customer.email,
status: 'any',
limit: 5
});
if (orders.length > 0) {
const latestOrder = orders[0];
// Update ticket with order context
await dispatch.tickets.update(BRAND_ID, ticket.id, {
customFields: {
...ticket.customFields,
orderId: latestOrder.id,
orderNumber: latestOrder.name, // e.g., "#1042"
orderDate: latestOrder.created_at,
orderTotal: latestOrder.total_price,
orderStatus: latestOrder.fulfillment_status || 'unfulfilled',
trackingNumber: latestOrder.fulfillments?.[0]?.tracking_number,
trackingUrl: latestOrder.fulfillments?.[0]?.tracking_url,
items: latestOrder.line_items.map(item => ({
name: item.name,
sku: item.sku,
quantity: item.quantity,
price: item.price
}))
}
});
}
}
res.status(200).send('OK');
});Option 2: Contact Form with Order Field
Add an "Order Number" field to your contact form. Then look up the order when creating the ticket:
app.post('/api/contact', async (req, res) => {
const { email, orderNumber, subject, message } = req.body;
let orderContext = {};
// If order number provided, fetch from Shopify
if (orderNumber) {
const orders = await shopify.order.list({
name: orderNumber, // Search by order number like "#1042"
limit: 1
});
if (orders.length > 0) {
const order = orders[0];
orderContext = {
orderId: order.id,
orderNumber: order.name,
orderStatus: order.fulfillment_status || 'unfulfilled',
orderTotal: order.total_price,
trackingNumber: order.fulfillments?.[0]?.tracking_number,
};
}
}
// Create ticket with order context
const ticket = await dispatch.tickets.create(BRAND_ID, {
title: subject,
body: message,
customerEmail: email,
customFields: orderContext
});
res.json({ ticketNumber: ticket.ticketNumber });
});WooCommerce Integration
Similar pattern for WooCommerce using the REST API:
import WooCommerceRestApi from '@woocommerce/woocommerce-rest-api';
const woo = new WooCommerceRestApi({
url: 'https://your-store.com',
consumerKey: process.env.WOO_CONSUMER_KEY,
consumerSecret: process.env.WOO_CONSUMER_SECRET,
version: 'wc/v3'
});
async function enrichTicketWithWooOrder(ticket) {
// Search orders by customer email
const { data: orders } = await woo.get('orders', {
search: ticket.customer.email,
per_page: 5
});
if (orders.length > 0) {
const order = orders[0];
await dispatch.tickets.update(BRAND_ID, ticket.id, {
customFields: {
orderId: order.id,
orderNumber: `#${order.number}`,
orderDate: order.date_created,
orderTotal: order.total,
orderStatus: order.status,
trackingNumber: order.meta_data.find(m => m.key === '_tracking_number')?.value,
items: order.line_items.map(item => ({
name: item.name,
sku: item.sku,
quantity: item.quantity,
price: item.price
}))
}
});
}
}Customer Order Lookup
Let customers find their orders before creating a ticket. This reduces "Where's my order?" tickets by showing tracking info upfront.
app.post('/api/order-lookup', async (req, res) => {
const { email, orderNumber } = req.body;
// Find order matching both email and order number
const orders = await shopify.order.list({
email,
name: orderNumber,
limit: 1
});
if (orders.length === 0) {
return res.status(404).json({ error: 'Order not found' });
}
const order = orders[0];
// Return safe subset of order data
res.json({
orderNumber: order.name,
status: order.fulfillment_status || 'processing',
placedAt: order.created_at,
total: order.total_price,
items: order.line_items.map(item => ({
name: item.name,
quantity: item.quantity
})),
tracking: order.fulfillments?.[0] ? {
carrier: order.fulfillments[0].tracking_company,
number: order.fulfillments[0].tracking_number,
url: order.fulfillments[0].tracking_url
} : null
});
});Use in Contact Form
Show order status before the customer submits a ticket. Many "Where's my order?" questions resolve themselves when the customer sees tracking info.
function OrderLookup({ onOrderFound }) {
const [email, setEmail] = useState('');
const [orderNumber, setOrderNumber] = useState('');
const [order, setOrder] = useState(null);
const [loading, setLoading] = useState(false);
const lookupOrder = async () => {
setLoading(true);
try {
const res = await fetch('/api/order-lookup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, orderNumber })
});
if (res.ok) {
const data = await res.json();
setOrder(data);
onOrderFound(data);
}
} finally {
setLoading(false);
}
};
return (
<div>
<input
type="email"
placeholder="Email"
value={email}
onChange={e => setEmail(e.target.value)}
/>
<input
type="text"
placeholder="Order number (e.g., #1042)"
value={orderNumber}
onChange={e => setOrderNumber(e.target.value)}
/>
<button onClick={lookupOrder} disabled={loading}>
{loading ? 'Looking up...' : 'Find Order'}
</button>
{order && (
<div className="order-status">
<h3>Order {order.orderNumber}</h3>
<p>Status: {order.status}</p>
{order.tracking && (
<a href={order.tracking.url} target="_blank">
Track Package →
</a>
)}
</div>
)}
</div>
);
}Returns Portal
Create a self-service returns portal that creates tickets for returns and exchanges:
app.post('/api/returns', async (req, res) => {
const { email, orderNumber, items, reason, action } = req.body;
// Verify the order exists and belongs to this email
const orders = await shopify.order.list({
email,
name: orderNumber,
limit: 1
});
if (orders.length === 0) {
return res.status(404).json({ error: 'Order not found' });
}
const order = orders[0];
// Create return ticket
const ticket = await dispatch.tickets.create(BRAND_ID, {
title: `${action === 'exchange' ? 'Exchange' : 'Return'} Request - ${orderNumber}`,
body: `
Customer requests ${action} for order ${orderNumber}.
Items:
${items.map(item => `- ${item.name} (Qty: ${item.quantity})`).join('\n')}
Reason: ${reason}
`.trim(),
customerEmail: email,
customFields: {
type: 'return',
action, // 'return' or 'exchange'
orderId: order.id,
orderNumber: order.name,
returnItems: items,
returnReason: reason,
orderDate: order.created_at,
orderTotal: order.total_price
}
});
res.json({
ticketNumber: ticket.ticketNumber,
message: 'Return request submitted. We will email you with next steps.'
});
});Multi-Store Setup
If you operate multiple stores, create a brand for each one. This keeps tickets, customers, and settings isolated.
// Store configuration
const stores = {
'urban-apparel': {
brandId: 'br_urban123',
shopifyDomain: 'urban-apparel.myshopify.com',
shopifyToken: process.env.SHOPIFY_TOKEN_URBAN
},
'home-essentials': {
brandId: 'br_home456',
shopifyDomain: 'home-essentials.myshopify.com',
shopifyToken: process.env.SHOPIFY_TOKEN_HOME
},
'fitness-first': {
brandId: 'br_fitness789',
shopifyDomain: 'fitness-first.myshopify.com',
shopifyToken: process.env.SHOPIFY_TOKEN_FITNESS
}
};
// Route based on store
app.post('/api/:store/contact', async (req, res) => {
const store = stores[req.params.store];
if (!store) {
return res.status(404).json({ error: 'Store not found' });
}
const { email, orderNumber, subject, message } = req.body;
// Use store-specific Shopify credentials
const shopify = new Shopify({
domain: store.shopifyDomain,
accessToken: store.shopifyToken
});
// Look up order if provided
let orderContext = {};
if (orderNumber) {
const orders = await shopify.order.list({ name: orderNumber, limit: 1 });
if (orders.length > 0) {
orderContext = mapOrderToContext(orders[0]);
}
}
// Create ticket in store-specific brand
const ticket = await dispatch.tickets.create(store.brandId, {
title: subject,
body: message,
customerEmail: email,
customFields: orderContext
});
res.json({ ticketNumber: ticket.ticketNumber });
});Benefits of multi-brand setup:
- Separate email identities ([email protected], [email protected])
- Isolated customer data per store
- Per-store reporting and metrics
- Team members can be assigned to specific stores
- One unified dashboard to see everything