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.
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"]}
]
}'
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")
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.
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'}
)
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']}")
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.
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']}")
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.