Workflows & Integration Patterns

Step-by-step guides for common API integration scenarios.

Create Product with Variants

Create a product with multiple variants (e.g., a t-shirt in different sizes and colors). This workflow demonstrates the two-step process: create the product, then add variants.

1

Create the Product

First, create the base product with its core attributes:

const product = await fetch('https://your-store.com/api/v1/products/', {
  method: 'POST',
  headers: {
    'Authorization': 'Token YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Classic T-Shirt',
    description: 'A comfortable cotton t-shirt.',
    status: 'draft',
    category_id: 42,
    options: [
      { name: 'Size', values: ['S', 'M', 'L', 'XL'] },
      { name: 'Color', values: ['Black', 'White', 'Navy'] }
    ]
  })
}).then(r => r.json());

console.log('Product created:', product.id);
import requests

headers = {'Authorization': 'Token YOUR_API_KEY'}

product = requests.post(
    'https://your-store.com/api/v1/products/',
    headers=headers,
    json={
        'name': 'Classic T-Shirt',
        'description': 'A comfortable cotton t-shirt.',
        'status': 'draft',
        'category_id': 42,
        'options': [
            {'name': 'Size', 'values': ['S', 'M', 'L', 'XL']},
            {'name': 'Color', 'values': ['Black', 'White', 'Navy']}
        ]
    }
).json()

print(f"Product created: {product['id']}")
curl -X POST "https://your-store.com/api/v1/products/" \
  -H "Authorization: Token YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Classic T-Shirt",
    "description": "A comfortable cotton t-shirt.",
    "status": "draft",
    "category_id": 42,
    "options": [
      {"name": "Size", "values": ["S", "M", "L", "XL"]},
      {"name": "Color", "values": ["Black", "White", "Navy"]}
    ]
  }'
2

Add Variants with Pricing and SKUs

Create variants for each size/color combination:

const variants = [
  { options: { Size: 'S', Color: 'Black' }, sku: 'TSHIRT-BLK-S', price: '29.99' },
  { options: { Size: 'M', Color: 'Black' }, sku: 'TSHIRT-BLK-M', price: '29.99' },
  { options: { Size: 'L', Color: 'Black' }, sku: 'TSHIRT-BLK-L', price: '29.99' },
  { options: { Size: 'S', Color: 'White' }, sku: 'TSHIRT-WHT-S', price: '29.99' },
  // ... more variants
];

for (const variant of variants) {
  await fetch(`https://your-store.com/api/v1/products/${product.id}/variants/`, {
    method: 'POST',
    headers: {
      'Authorization': 'Token YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(variant)
  });
}

console.log(`Created ${variants.length} variants`);
variants = [
    {'options': {'Size': 'S', 'Color': 'Black'}, 'sku': 'TSHIRT-BLK-S', 'price': '29.99'},
    {'options': {'Size': 'M', 'Color': 'Black'}, 'sku': 'TSHIRT-BLK-M', 'price': '29.99'},
    {'options': {'Size': 'L', 'Color': 'Black'}, 'sku': 'TSHIRT-BLK-L', 'price': '29.99'},
    {'options': {'Size': 'S', 'Color': 'White'}, 'sku': 'TSHIRT-WHT-S', 'price': '29.99'},
    # ... more variants
]

for variant in variants:
    requests.post(
        f"https://your-store.com/api/v1/products/{product['id']}/variants/",
        headers=headers,
        json=variant
    )

print(f"Created {len(variants)} variants")
3

Set Inventory and Publish

Set stock levels and activate the product:

// Set inventory for a variant at a specific warehouse
await fetch(`https://your-store.com/api/v1/inventory/`, {
  method: 'POST',
  headers: {
    'Authorization': 'Token YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    variant_id: variantId,
    warehouse_id: 1,
    quantity: 100
  })
});

// Publish the product
await fetch(`https://your-store.com/api/v1/products/${product.id}/`, {
  method: 'PATCH',
  headers: {
    'Authorization': 'Token YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ status: 'active' })
});
# Set inventory for a variant
requests.post(
    'https://your-store.com/api/v1/inventory/',
    headers=headers,
    json={
        'variant_id': variant_id,
        'warehouse_id': 1,
        'quantity': 100
    }
)

# Publish the product
requests.patch(
    f"https://your-store.com/api/v1/products/{product['id']}/",
    headers=headers,
    json={'status': 'active'}
)

Customer Checkout Flow

Implement a complete headless checkout experience for your custom storefront.

1

Create Cart and Add Items

// Create a new cart (or retrieve existing by session)
const cart = await fetch('https://your-store.com/api/v1/cart/', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' }
}).then(r => r.json());

// Add items to cart
await fetch(`https://your-store.com/api/v1/cart/${cart.id}/items/`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    variant_id: 'var_abc123',
    quantity: 2
  })
});

// Apply a voucher code
await fetch(`https://your-store.com/api/v1/cart/${cart.id}/vouchers/`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ code: 'SAVE10' })
});
import requests

base_url = 'https://your-store.com/api/v1'

# Create cart
cart = requests.post(f'{base_url}/cart/').json()

# Add items
requests.post(
    f"{base_url}/cart/{cart['id']}/items/",
    json={'variant_id': 'var_abc123', 'quantity': 2}
)

# Apply voucher
requests.post(
    f"{base_url}/cart/{cart['id']}/vouchers/",
    json={'code': 'SAVE10'}
)
2

Create Checkout Session

const checkout = await fetch('https://your-store.com/api/v1/checkout/', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    cart_id: cart.id,
    email: '[email protected]',
    shipping_address: {
      first_name: 'Jane',
      last_name: 'Smith',
      address_line_1: '456 Oak Ave',
      city: 'Los Angeles',
      state: 'CA',
      postal_code: '90001',
      country: 'US',
      phone: '+1-555-0123'
    },
    billing_same_as_shipping: true
  })
}).then(r => r.json());

// Response includes available shipping methods
console.log('Shipping options:', checkout.shipping_methods);
checkout = requests.post(
    f'{base_url}/checkout/',
    json={
        'cart_id': cart['id'],
        'email': '[email protected]',
        'shipping_address': {
            'first_name': 'Jane',
            'last_name': 'Smith',
            'address_line_1': '456 Oak Ave',
            'city': 'Los Angeles',
            'state': 'CA',
            'postal_code': '90001',
            'country': 'US',
            'phone': '+1-555-0123'
        },
        'billing_same_as_shipping': True
    }
).json()

print(f"Shipping options: {checkout['shipping_methods']}")
3

Select Shipping and Payment Method

// Select shipping method
await fetch(`https://your-store.com/api/v1/checkout/${checkout.id}/`, {
  method: 'PATCH',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    shipping_method_id: checkout.shipping_methods[0].id
  })
});

// Get available payment methods
const paymentMethods = await fetch(
  `https://your-store.com/api/v1/checkout/${checkout.id}/payment-methods/`
).then(r => r.json());

// Create payment intent (e.g., with Stripe)
const payment = await fetch(`https://your-store.com/api/v1/checkout/${checkout.id}/pay/`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    payment_method_id: paymentMethods.results[0].id
  })
}).then(r => r.json());

// Redirect to payment.redirect_url or handle client-side
# Select shipping
requests.patch(
    f"{base_url}/checkout/{checkout['id']}/",
    json={'shipping_method_id': checkout['shipping_methods'][0]['id']}
)

# Get payment methods
payment_methods = requests.get(
    f"{base_url}/checkout/{checkout['id']}/payment-methods/"
).json()

# Create payment
payment = requests.post(
    f"{base_url}/checkout/{checkout['id']}/pay/",
    json={'payment_method_id': payment_methods['results'][0]['id']}
).json()

# Redirect to payment['redirect_url']

Order Fulfillment

Process orders from your warehouse management system or fulfillment service.

1

Fetch Orders Ready for Fulfillment

// Get orders in "processing" status (paid, ready to ship)
const orders = await fetch(
  'https://your-store.com/api/v1/orders/?status=processing&expand=items',
  { headers: { 'Authorization': 'Token YOUR_API_KEY' }}
).then(r => r.json());

console.log(`${orders.count} orders ready for fulfillment`);

for (const order of orders.results) {
  console.log(`Order ${order.order_number}:`);
  for (const item of order.items) {
    console.log(`  - ${item.quantity}x ${item.variant.sku}`);
  }
}
orders = requests.get(
    'https://your-store.com/api/v1/orders/',
    headers=headers,
    params={'status': 'processing', 'expand': 'items'}
).json()

print(f"{orders['count']} orders ready for fulfillment")

for order in orders['results']:
    print(f"Order {order['order_number']}:")
    for item in order['items']:
        print(f"  - {item['quantity']}x {item['variant']['sku']}")
2

Create Shipment with Tracking

const shipment = await fetch(
  `https://your-store.com/api/v1/orders/${orderId}/shipments/`,
  {
    method: 'POST',
    headers: {
      'Authorization': 'Token YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      carrier: 'fedex',
      tracking_number: '794932643366',
      items: [
        { order_item_id: 101, quantity: 1 },
        { order_item_id: 102, quantity: 2 }
      ],
      notify_customer: true  // Sends shipping confirmation email
    })
  }
).then(r => r.json());

console.log(`Shipment created: ${shipment.id}`);
shipment = requests.post(
    f'https://your-store.com/api/v1/orders/{order_id}/shipments/',
    headers=headers,
    json={
        'carrier': 'fedex',
        'tracking_number': '794932643366',
        'items': [
            {'order_item_id': 101, 'quantity': 1},
            {'order_item_id': 102, 'quantity': 2}
        ],
        'notify_customer': True
    }
).json()

print(f"Shipment created: {shipment['id']}")

Partial Fulfillment: You can create multiple shipments for a single order. Each shipment specifies which items and quantities it contains. The order status updates to "shipped" when all items are fulfilled.

Refund Processing

Issue full or partial refunds for orders.

// Full refund
const fullRefund = await fetch(
  `https://your-store.com/api/v1/orders/${orderId}/refunds/`,
  {
    method: 'POST',
    headers: {
      'Authorization': 'Token YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      reason: 'Customer request',
      notify_customer: true
    })
  }
).then(r => r.json());

// Partial refund (specific amount)
const partialRefund = await fetch(
  `https://your-store.com/api/v1/orders/${orderId}/refunds/`,
  {
    method: 'POST',
    headers: {
      'Authorization': 'Token YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      amount: '25.00',
      reason: 'Damaged item - partial compensation',
      items: [
        { order_item_id: 101, quantity: 1 }  // Optional: associate with line items
      ]
    })
  }
).then(r => r.json());
# Full refund
full_refund = requests.post(
    f'https://your-store.com/api/v1/orders/{order_id}/refunds/',
    headers=headers,
    json={
        'reason': 'Customer request',
        'notify_customer': True
    }
).json()

# Partial refund
partial_refund = requests.post(
    f'https://your-store.com/api/v1/orders/{order_id}/refunds/',
    headers=headers,
    json={
        'amount': '25.00',
        'reason': 'Damaged item - partial compensation',
        'items': [
            {'order_item_id': 101, 'quantity': 1}
        ]
    }
).json()

Inventory Sync (ERP Integration)

Keep your Spwig inventory synchronized with external systems like ERP or warehouse management software.

// Bulk inventory update from ERP
const inventoryUpdates = [
  { sku: 'TSHIRT-BLK-S', warehouse_id: 1, quantity: 150 },
  { sku: 'TSHIRT-BLK-M', warehouse_id: 1, quantity: 200 },
  { sku: 'TSHIRT-BLK-L', warehouse_id: 1, quantity: 175 },
  // ... more updates
];

// Process in batches
for (const update of inventoryUpdates) {
  // First, find the variant by SKU
  const variant = await fetch(
    `https://your-store.com/api/v1/variants/?sku=${update.sku}`,
    { headers: { 'Authorization': 'Token YOUR_API_KEY' }}
  ).then(r => r.json());

  if (variant.results.length > 0) {
    // Update inventory
    await fetch('https://your-store.com/api/v1/inventory/', {
      method: 'PUT',
      headers: {
        'Authorization': 'Token YOUR_API_KEY',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        variant_id: variant.results[0].id,
        warehouse_id: update.warehouse_id,
        quantity: update.quantity
      })
    });
  }
}

console.log('Inventory sync complete');
inventory_updates = [
    {'sku': 'TSHIRT-BLK-S', 'warehouse_id': 1, 'quantity': 150},
    {'sku': 'TSHIRT-BLK-M', 'warehouse_id': 1, 'quantity': 200},
    {'sku': 'TSHIRT-BLK-L', 'warehouse_id': 1, 'quantity': 175},
    # ... more updates
]

for update in inventory_updates:
    # Find variant by SKU
    variant = requests.get(
        'https://your-store.com/api/v1/variants/',
        headers=headers,
        params={'sku': update['sku']}
    ).json()

    if variant['results']:
        # Update inventory
        requests.put(
            'https://your-store.com/api/v1/inventory/',
            headers=headers,
            json={
                'variant_id': variant['results'][0]['id'],
                'warehouse_id': update['warehouse_id'],
                'quantity': update['quantity']
            }
        )

print('Inventory sync complete')

Rate Limiting: For large inventory syncs, use bulk endpoints or implement delays between requests. See Rate Limits for details.

Webhook Integration Patterns

Best practices for reliable webhook processing in production environments.

Verify Webhook Signatures

Always verify the X-Spwig-Signature header to ensure webhooks are authentic:

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

app.post('/webhooks/spwig', express.raw({type: 'application/json'}), (req, res) => {
  const signature = req.headers['x-spwig-signature'];
  const webhookSecret = process.env.SPWIG_WEBHOOK_SECRET;

  if (!verifyWebhookSignature(req.body, signature, webhookSecret)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body);

  // Process event...
  handleWebhookEvent(event);

  res.status(200).send('OK');
});
import hmac
import hashlib
from flask import Flask, request

app = Flask(__name__)

def verify_signature(payload, signature, secret):
    expected = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

@app.route('/webhooks/spwig', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Spwig-Signature')
    secret = os.environ['SPWIG_WEBHOOK_SECRET']

    if not verify_signature(request.data, signature, secret):
        return 'Invalid signature', 401

    event = request.json

    # Process event...
    handle_webhook_event(event)

    return 'OK', 200

Idempotent Processing

Webhooks may be delivered multiple times. Use the event ID to ensure idempotent processing:

async function handleWebhookEvent(event) {
  // Check if we've already processed this event
  const processed = await redis.get(`webhook:${event.id}`);
  if (processed) {
    console.log(`Event ${event.id} already processed, skipping`);
    return;
  }

  // Process the event
  switch (event.type) {
    case 'order.created':
      await syncOrderToERP(event.data);
      break;
    case 'order.paid':
      await triggerFulfillment(event.data);
      break;
    // ... handle other events
  }

  // Mark as processed (with 7-day expiry)
  await redis.set(`webhook:${event.id}`, '1', 'EX', 604800);
}
import redis

r = redis.Redis()

def handle_webhook_event(event):
    event_key = f"webhook:{event['id']}"

    # Check if already processed
    if r.get(event_key):
        print(f"Event {event['id']} already processed, skipping")
        return

    # Process the event
    if event['type'] == 'order.created':
        sync_order_to_erp(event['data'])
    elif event['type'] == 'order.paid':
        trigger_fulfillment(event['data'])
    # ... handle other events

    # Mark as processed (7-day expiry)
    r.setex(event_key, 604800, '1')

Next Steps: See the Webhooks documentation for a complete list of event types, or explore the API Reference.