scaffolding-marketplace-integrations

Marketplace Integration Helper

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "scaffolding-marketplace-integrations" with this command: npx skills add wesleysmits/agent-skills/wesleysmits-agent-skills-scaffolding-marketplace-integrations

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

Platform Auth Type Rate Limit Docs

Shopify OAuth / Access Token 2 req/sec (burst 40) Admin API

WooCommerce OAuth 1.0 / API Keys 25 req/sec REST API v3

Bol.com OAuth 2.0 Client Credentials 25 req/10sec Retailer API

Etsy OAuth 2.0 PKCE 10 req/sec Open 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 &#x3C; this.retryConfig.maxRetries &#x26;&#x26; 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}&#x26;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}&#x26;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 ? &#x26;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

  • Shopify Admin API

  • WooCommerce REST API

  • Bol.com Retailer API

  • Etsy Open API v3

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

writing-product-descriptions

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

writing-press-releases

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

creating-podcast-show-notes

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

generating-social-media-captions

No summary provided by upstream source.

Repository SourceNeeds Review