Back to Docs

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.

Create ticket with order context
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

FieldTypeDescription
orderIdstringInternal order ID for API lookups
orderNumberstringCustomer-facing order number
orderStatusstringpending, paid, shipped, delivered, refunded
trackingNumberstringCarrier tracking number
trackingUrlstringDirect link to tracking page
itemsarrayLine 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:

Enrich ticket with Shopify order
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:

Contact form handler
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:

WooCommerce order lookup
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.

Order lookup endpoint
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.

React order lookup component
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:

Returns request handler
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.

Multi-store architecture
// 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