SeplorX e-commerce channel integrations — adding channels, stock sync, order sync, webhook handling, and product publishing. Use when integrating a new sales channel, working with order-driven stock management, or extending channel capabilities. Covers ChannelDefinition registry, ChannelHandler interface, OAuth flow, webhook setup, product sync, channel_products cache, product publishing pipeline, and stock reservations.
| Apps | Channels | |
|---|---|---|
| Examples | Delhivery, FedEx, Razorpay | WooCommerce, Shopify, Amazon |
| Purpose | Shipping/payment providers | Order source integrations |
| Instances | 1 per user | Many per user (multi-store) |
| Auth | API keys | OAuth or API key wizard |
| DB table | app_installations | channels |
Add to src/lib/channels/registry.ts:
{
id: "shopify", // unique slug — matches channelType varchar in DB
name: "Shopify",
description: "Connect your Shopify storefront to manage orders.",
icon: "/channels/shopify.svg", // null = placeholder icon
authType: "oauth", // "oauth" | "apikey"
popular: true,
available: true, // false = "Coming soon" overlay in UI
}
Place the SVG at public/channels/{id}.svg. No DB migration needed — channelType is a varchar.
Also add "shopify" to the ChannelType union in src/lib/channels/types.ts.
Create src/lib/channels/{id}/index.ts implementing ChannelHandler:
import type { ChannelHandler } from "@/lib/channels/types";
export const shopifyHandler: ChannelHandler = {
id: "shopify",
// Wizard step 4 config fields (for apikey auth channels)
configFields: [
{ key: "storeUrl", label: "Store URL", type: "url", required: true },
{ key: "accessToken", label: "Access Token", type: "password", required: true },
],
// OAuth channels: topics to subscribe to
webhookTopics: ["orders/create", "orders/cancelled"],
// Validate config before connecting
validateConfig(config) {
if (!config.storeUrl?.startsWith("https://")) return "Store URL must start with https://";
return null;
},
// Build the OAuth redirect URL (OAuth channels)
buildConnectUrl(channelId, config, appUrl) {
return `https://${config.shop}/admin/oauth/authorize?client_id=...&redirect_uri=${appUrl}/api/channels/shopify/callback&state=${channelId}`;
},
// Parse the OAuth callback body to extract credentials
parseCallback(body) {
const params = new URLSearchParams(body);
return {
channelId: Number(params.get("state")),
credentials: { accessToken: params.get("code")! },
};
},
// Push stock update to channel (when SeplorX stock changes)
async pushStock(storeUrl, credentials, externalProductId, quantity, parentId, sku, productType, rawData) {
await fetch(`${storeUrl}/admin/api/2024-01/variants/${externalProductId}.json`, {
method: "PUT",
headers: { "X-Shopify-Access-Token": credentials.accessToken },
body: JSON.stringify({ variant: { inventory_quantity: quantity } }),
});
},
// Register webhooks after channel is connected
async registerWebhooks(storeUrl, credentials, baseUrl) {
// Call channel API to register webhooks, return secret
return { secret: "generated-secret" };
},
// Process incoming webhook payload → return stock changes
processWebhook(body, signature, topic, secret) {
const order = JSON.parse(body);
return order.line_items.map((item: any) => ({
externalProductId: String(item.variant_id),
quantityChange: -item.quantity,
}));
},
// Optional: fetch products for AI mapping + product browser
async fetchProducts(storeUrl, credentials, search) {
// Call Shopify products API, return ExternalProduct[]
return [];
},
// Optional: fetch orders from the channel and save them to SeplorX
async fetchAndSaveOrders(userId, channelId) {
// Call channel API, insert into sales_orders and sales_order_items
return { fetched: 0, saved: 0 };
},
};
Register the handler in src/lib/channels/handlers.ts:
import { shopifyHandler } from "./shopify";
// add to the handlers map: