Skip to content
scaffolding-marketplace-integrations logo

Marketplace Integration Helper

scaffolding-marketplace-integrations

Generates boilerplate for e-commerce API integrations. Use when the user asks about WooCommerce, Shopify, Bol.com, or Etsy APIs, webhooks, product sync, or order handling.

SKILL.md

Full skill instructions

Marketplace Integration Helper

When to use this skill

  • User asks to integrate with WooCommerce or Shopify
  • User needs webhook handlers for orders or products
  • User mentions Bol.com, Etsy, or marketplace APIs
  • User wants rate-limited API clients
  • User asks about product synchronization

Workflow

  • Identify target marketplace(s)
  • Generate API client with auth
  • Add rate limiting and retry logic
  • Create webhook handlers
  • Add TypeScript types
  • Implement error recovery

Instructions

Step 1: Identify Marketplace

PlatformAuth TypeRate LimitDocs
ShopifyOAuth / Access Token2 req/​sec (burst 40)Admin API
WooCommerceOAuth 1.0 / API Keys25 req/​secREST API v3
Bol.comOAuth 2.0 Client Credentials25 req/​10secRetailer API
EtsyOAuth 2.0 PKCE10 req/​secOpen API v3

Step 2: Base API Client Structure

// lib/​marketplace/​base-client.ts
interface RateLimitConfig {
  maxRequests: number;
  windowMs: number;
}

interface RetryConfig {
  maxRetries: number;
  baseDelayMs: number;
  maxDelayMs: number;
}

export abstract class BaseMarketplaceClient {
  protected baseUrl: string;
  protected rateLimitConfig: RateLimitConfig;
  protected retryConfig: RetryConfig;
  private requestQueue: Array<() => Promise<unknown>> = [];
  private processing = false;

  constructor(
    baseUrl: string,
    rateLimit: RateLimitConfig,
    retry: RetryConfig = {
      maxRetries: 3,
      baseDelayMs: 1000,
      maxDelayMs: 10000,
    },
  ) {
    this.baseUrl = baseUrl;
    this.rateLimitConfig = rateLimit;
    this.retryConfig = retry;
  }

  protected abstract getAuthHeaders(): Record<string, string>;

  protected async request<T>(
    method: string,
    path: string,
    body?: unknown,
  ): Promise<T> {
    return this.enqueue(() => this.executeWithRetry<T>(method, path, body));
  }

  private async executeWithRetry<T>(
    method: string,
    path: string,
    body?: unknown,
    attempt = 0,
  ): Promise<T> {
    try {
      const response = await fetch(`${this.baseUrl}${path}`, {
        method,
        headers: {
          "Content-Type": "application/​json",
          ...this.getAuthHeaders(),
        },
        body: body ? JSON.stringify(body) : undefined,
      });

      if (response.status === 429) {
        const retryAfter = parseInt(response.headers.get("Retry-After") || "5");
        await this.delay(retryAfter * 1000);
        return this.executeWithRetry(method, path, body, attempt);
      }

      if (!response.ok) {
        throw new MarketplaceError(response.status, await response.text());
      }

      return response.json();
    } catch (error) {
      if (attempt < this.retryConfig.maxRetries && this.isRetryable(error)) {
        const delay = Math.min(
          this.retryConfig.baseDelayMs * Math.pow(2, attempt),
          this.retryConfig.maxDelayMs,
        );
        await this.delay(delay);
        return this.executeWithRetry(method, path, body, attempt + 1);
      }
      throw error;
    }
  }

  private isRetryable(error: unknown): boolean {
    if (error instanceof MarketplaceError) {
      return [408, 429, 500, 502, 503, 504].includes(error.status);
    }
    return error instanceof TypeError; // Network errors
  }

  private enqueue<T>(fn: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      this.requestQueue.push(async () => {
        try {
          resolve(await fn());
        } catch (e) {
          reject(e);
        }
      });
      this.processQueue();
    });
  }

  private async processQueue(): Promise<void> {
    if (this.processing) return;
    this.processing = true;

    while (this.requestQueue.length > 0) {
      const batch = this.requestQueue.splice(
        0,
        this.rateLimitConfig.maxRequests,
      );
      await Promise.all(batch.map((fn) => fn()));
      if (this.requestQueue.length > 0) {
        await this.delay(this.rateLimitConfig.windowMs);
      }
    }

    this.processing = false;
  }

  private delay(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}

export class MarketplaceError extends Error {
  constructor(
    public status: number,
    public body: string,
  ) {
    super(`Marketplace API error ${status}: ${body}`);
  }
}

Step 3: Platform-Specific Clients

Shopify Client:

// lib/​marketplace/​shopify-client.ts
import { BaseMarketplaceClient } from "./​base-client";

interface ShopifyProduct {
  id: number;
  title: string;
  body_html: string;
  vendor: string;
  product_type: string;
  variants: ShopifyVariant[];
  images: ShopifyImage[];
  status: "active" | "archived" | "draft";
}

interface ShopifyVariant {
  id: number;
  product_id: number;
  title: string;
  price: string;
  sku: string;
  inventory_quantity: number;
}

interface ShopifyImage {
  id: number;
  src: string;
  alt: string | null;
}

interface ShopifyOrder {
  id: number;
  order_number: number;
  email: string;
  financial_status: string;
  fulfillment_status: string | null;
  line_items: ShopifyLineItem[];
  total_price: string;
  currency: string;
}

interface ShopifyLineItem {
  id: number;
  product_id: number;
  variant_id: number;
  quantity: number;
  price: string;
}

export class ShopifyClient extends BaseMarketplaceClient {
  private accessToken: string;

  constructor(shop: string, accessToken: string) {
    super(`https://${shop}.myshopify.com/​admin/​api/​2024-01`, {
      maxRequests: 2,
      windowMs: 1000,
    });
    this.accessToken = accessToken;
  }

  protected getAuthHeaders(): Record<string, string> {
    return { "X-Shopify-Access-Token": this.accessToken };
  }

  // Products
  async getProducts(limit = 50): Promise<ShopifyProduct[]> {
    const data = await this.request<{ products: ShopifyProduct[] }>(
      "GET",
      `/​products.json?limit=${limit}`,
    );
    return data.products;
  }

  async getProduct(id: number): Promise<ShopifyProduct> {
    const data = await this.request<{ product: ShopifyProduct }>(
      "GET",
      `/​products/​${id}.json`,
    );
    return data.product;
  }

  async createProduct(
    product: Partial<ShopifyProduct>,
  ): Promise<ShopifyProduct> {
    const data = await this.request<{ product: ShopifyProduct }>(
      "POST",
      "/​products.json",
      { product },
    );
    return data.product;
  }

  async updateProduct(
    id: number,
    product: Partial<ShopifyProduct>,
  ): Promise<ShopifyProduct> {
    const data = await this.request<{ product: ShopifyProduct }>(
      "PUT",
      `/​products/​${id}.json`,
      { product },
    );
    return data.product;
  }

  // Inventory
  async updateInventory(
    inventoryItemId: number,
    locationId: number,
    quantity: number,
  ): Promise<void> {
    await this.request("POST", "/​inventory_levels/​set.json", {
      inventory_item_id: inventoryItemId,
      location_id: locationId,
      available: quantity,
    });
  }

  // Orders
  async getOrders(status = "any", limit = 50): Promise<ShopifyOrder[]> {
    const data = await this.request<{ orders: ShopifyOrder[] }>(
      "GET",
      `/​orders.json?status=${status}&limit=${limit}`,
    );
    return data.orders;
  }

  async fulfillOrder(orderId: number, trackingNumber?: string): Promise<void> {
    await this.request("POST", `/​orders/​${orderId}/​fulfillments.json`, {
      fulfillment: {
        tracking_number: trackingNumber,
        notify_customer: true,
      },
    });
  }
}

WooCommerce Client:

// lib/​marketplace/​woocommerce-client.ts
import { BaseMarketplaceClient } from "./​base-client";
import crypto from "crypto";

interface WooProduct {
  id: number;
  name: string;
  slug: string;
  type: "simple" | "variable" | "grouped";
  status: "publish" | "draft" | "pending";
  sku: string;
  price: string;
  regular_price: string;
  stock_quantity: number | null;
  images: { id: number; src: string; alt: string }[];
}

interface WooOrder {
  id: number;
  status: string;
  currency: string;
  total: string;
  billing: WooAddress;
  shipping: WooAddress;
  line_items: WooLineItem[];
}

interface WooAddress {
  first_name: string;
  last_name: string;
  address_1: string;
  city: string;
  postcode: string;
  country: string;
}

interface WooLineItem {
  id: number;
  product_id: number;
  quantity: number;
  total: string;
}

export class WooCommerceClient extends BaseMarketplaceClient {
  private consumerKey: string;
  private consumerSecret: string;

  constructor(siteUrl: string, consumerKey: string, consumerSecret: string) {
    super(`${siteUrl}/​wp-json/​wc/​v3`, { maxRequests: 25, windowMs: 1000 });
    this.consumerKey = consumerKey;
    this.consumerSecret = consumerSecret;
  }

  protected getAuthHeaders(): Record<string, string> {
    const auth = Buffer.from(
      `${this.consumerKey}:${this.consumerSecret}`,
    ).toString("base64");
    return { Authorization: `Basic ${auth}` };
  }

  // Products
  async getProducts(page = 1, perPage = 100): Promise<WooProduct[]> {
    return this.request("GET", `/​products?page=${page}&per_page=${perPage}`);
  }

  async getProduct(id: number): Promise<WooProduct> {
    return this.request("GET", `/​products/​${id}`);
  }

  async createProduct(product: Partial<WooProduct>): Promise<WooProduct> {
    return this.request("POST", "/​products", product);
  }

  async updateProduct(
    id: number,
    product: Partial<WooProduct>,
  ): Promise<WooProduct> {
    return this.request("PUT", `/​products/​${id}`, product);
  }

  async updateStock(productId: number, quantity: number): Promise<WooProduct> {
    return this.updateProduct(productId, { stock_quantity: quantity });
  }

  // Orders
  async getOrders(status?: string, page = 1): Promise<WooOrder[]> {
    const query = status ? `&status=${status}` : "";
    return this.request("GET", `/​orders?page=${page}${query}`);
  }

  async updateOrderStatus(orderId: number, status: string): Promise<WooOrder> {
    return this.request("PUT", `/​orders/​${orderId}`, { status });
  }
}

See examples/​bol-etsy-clients.md for Bol.com and Etsy implementations.

Step 4: Webhook Handlers

Webhook verification and routing:

// lib/​marketplace/​webhooks.ts
import crypto from "crypto";

interface WebhookHandler<T = unknown> {
  topic: string;
  handler: (payload: T) => Promise<void>;
}

// Shopify webhook verification
export function verifyShopifyWebhook(
  body: string,
  hmacHeader: string,
  secret: string,
): boolean {
  const hash = crypto
    .createHmac("sha256", secret)
    .update(body, "utf8")
    .digest("base64");
  return crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(hmacHeader));
}

// WooCommerce webhook verification
export function verifyWooCommerceWebhook(
  body: string,
  signature: string,
  secret: string,
): boolean {
  const hash = crypto
    .createHmac("sha256", secret)
    .update(body, "utf8")
    .digest("base64");
  return crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(signature));
}

// Generic webhook router
export class WebhookRouter {
  private handlers: Map<string, WebhookHandler["handler"]> = new Map();

  register<T>(topic: string, handler: (payload: T) => Promise<void>): void {
    this.handlers.set(topic, handler as WebhookHandler["handler"]);
  }

  async route(topic: string, payload: unknown): Promise<void> {
    const handler = this.handlers.get(topic);
    if (!handler) {
      console.warn(`No handler registered for topic: ${topic}`);
      return;
    }
    await handler(payload);
  }
}

Next.js API route example:

// app/​api/​webhooks/​shopify/​route.ts
import { NextRequest, NextResponse } from "next/​server";
import {
  verifyShopifyWebhook,
  WebhookRouter,
} from "@/​lib/​marketplace/​webhooks";
import {
  handleOrderCreated,
  handleProductUpdated,
} from "@/​lib/​marketplace/​handlers";

const router = new WebhookRouter();
router.register("orders/​create", handleOrderCreated);
router.register("products/​update", handleProductUpdated);

export async function POST(request: NextRequest) {
  const body = await request.text();
  const hmac = request.headers.get("X-Shopify-Hmac-Sha256") || "";
  const topic = request.headers.get("X-Shopify-Topic") || "";

  if (!verifyShopifyWebhook(body, hmac, process.env.SHOPIFY_WEBHOOK_SECRET!)) {
    return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
  }

  try {
    await router.route(topic, JSON.parse(body));
    return NextResponse.json({ success: true });
  } catch (error) {
    console.error("Webhook processing error:", error);
    return NextResponse.json({ error: "Processing failed" }, { status: 500 });
  }
}

Webhook handler implementations:

// lib/​marketplace/​handlers.ts
import { db } from "@/​lib/​db";

interface ShopifyOrderPayload {
  id: number;
  order_number: number;
  email: string;
  line_items: Array<{
    product_id: number;
    variant_id: number;
    quantity: number;
  }>;
}

export async function handleOrderCreated(
  payload: ShopifyOrderPayload,
): Promise<void> {
  // Idempotency check
  const existing = await db.order.findUnique({
    where: { externalId: `shopify_${payload.id}` },
  });
  if (existing) return;

  // Create local order record
  await db.order.create({
    data: {
      externalId: `shopify_${payload.id}`,
      platform: "shopify",
      orderNumber: String(payload.order_number),
      customerEmail: payload.email,
      status: "pending",
      lineItems: {
        create: payload.line_items.map((item) => ({
          externalProductId: String(item.product_id),
          externalVariantId: String(item.variant_id),
          quantity: item.quantity,
        })),
      },
    },
  });

  // Sync inventory across platforms
  for (const item of payload.line_items) {
    await syncInventoryAcrossPlatforms(item.product_id, -item.quantity);
  }
}

export async function handleProductUpdated(payload: {
  id: number;
  title: string;
}): Promise<void> {
  await db.product.updateMany({
    where: { externalId: `shopify_${payload.id}` },
    data: { title: payload.title, updatedAt: new Date() },
  });
}

Step 5: Product Sync Service

// lib/​marketplace/​sync-service.ts
import { ShopifyClient } from "./​shopify-client";
import { WooCommerceClient } from "./​woocommerce-client";

interface NormalizedProduct {
  sku: string;
  title: string;
  description: string;
  price: number;
  quantity: number;
  images: string[];
}

export class ProductSyncService {
  constructor(
    private shopify: ShopifyClient,
    private woocommerce: WooCommerceClient,
  ) {}

  async syncProductToAll(product: NormalizedProduct): Promise<void> {
    const results = await Promise.allSettled([
      this.syncToShopify(product),
      this.syncToWooCommerce(product),
    ]);

    for (const result of results) {
      if (result.status === "rejected") {
        console.error("Sync failed:", result.reason);
      }
    }
  }

  private async syncToShopify(product: NormalizedProduct): Promise<void> {
    const existing = await this.findShopifyProductBySku(product.sku);

    if (existing) {
      await this.shopify.updateProduct(existing.id, {
        title: product.title,
        body_html: product.description,
        variants: [{ ...existing.variants[0], price: String(product.price) }],
      });
    } else {
      await this.shopify.createProduct({
        title: product.title,
        body_html: product.description,
        variants: [{ sku: product.sku, price: String(product.price) }] as any,
        images: product.images.map((src) => ({ src })) as any,
      });
    }
  }

  private async syncToWooCommerce(product: NormalizedProduct): Promise<void> {
    // Similar pattern for WooCommerce
  }

  private async findShopifyProductBySku(sku: string) {
    const products = await this.shopify.getProducts(250);
    return products.find((p) => p.variants.some((v) => v.sku === sku));
  }
}

Environment Setup

# .env.local
SHOPIFY_SHOP=your-store
SHOPIFY_ACCESS_TOKEN=shpat_xxxxx
SHOPIFY_WEBHOOK_SECRET=xxxxx

WOOCOMMERCE_URL=https://your-store.com
WOOCOMMERCE_KEY=ck_xxxxx
WOOCOMMERCE_SECRET=cs_xxxxx

BOL_CLIENT_ID=xxxxx
BOL_CLIENT_SECRET=xxxxx

ETSY_API_KEY=xxxxx
ETSY_SHARED_SECRET=xxxxx

Validation

Before completing:

  • API client handles rate limits correctly
  • Webhook signatures verified before processing
  • Idempotency checks prevent duplicate processing
  • Error recovery with exponential backoff works
  • TypeScript types match API responses

Error Handling

  • Rate limit exceeded: Queue requests and respect Retry-After header.
  • Authentication failure: Check token expiry; refresh OAuth tokens if needed.
  • Webhook signature mismatch: Reject immediately; log for investigation.
  • Partial sync failure: Use Promise.allSettled; continue with working platforms.
  • Network timeout: Retry with exponential backoff up to max retries.

Resources

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View