Skip to Content
IntegrationsERP Example

ERP Integration Example

Complete example showing how to integrate an external ERP system.

Setup

ERP Client

src/lib/server/integrations/erp/client.ts
export class ErpClient { private baseUrl = process.env.ERP_BASE_URL!; private apiKey = process.env.ERP_API_KEY!; async getInventory(): Promise<ErpInventoryItem[]> { const res = await fetch(`${this.baseUrl}/inventory`, { headers: { Authorization: `Bearer ${this.apiKey}` } }); return res.json(); } async createOrder(order: ErpOrder): Promise<ErpOrderResponse> { const res = await fetch(`${this.baseUrl}/orders`, { method: "POST", headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json" }, body: JSON.stringify(order) }); return res.json(); } } export const erpClient = new ErpClient();

Order Push (Inline with Retry)

Push orders to ERP inline after payment using withRetry:

src/lib/server/integrations/erp/order-push.ts
import { withRetry } from "$lib/server/integrations"; import { erpClient } from "./client"; export async function pushOrderToErp(order: OrderWithRelations) { await withRetry( () => erpClient.createOrder({ reference: order.code, customerEmail: order.customer?.email, items: order.lines.map((line) => ({ sku: line.variant.sku, quantity: line.quantity, unitPrice: line.unitPrice })), total: order.total }), { maxAttempts: 5, delayMs: 2000, backoff: true } ); }

Inventory Sync (Workflow)

Use a Vercel Workflow for scheduled inventory synchronization:

workflows/erp-sync.ts
import { sleep } from "workflow"; export async function erpInventorySync() { "use workflow"; while (true) { await syncInventory(); await sleep("1h"); } } async function syncInventory() { "use step"; const { runSync } = await import("$lib/server/integrations"); const { inventorySync } = await import("$lib/server/integrations/erp/inventory-sync.js"); const results = await runSync(inventorySync); return results; }
src/lib/server/integrations/erp/inventory-sync.ts
import { type SyncJob } from "$lib/server/integrations"; import { erpClient } from "./client"; export const inventorySync: SyncJob<ErpInventoryItem, typeof productVariants.$inferSelect> = { name: "erp-inventory", fetchExternal: () => erpClient.getInventory(), getExternalId: (item) => item.sku, findLocal: async (sku) => { const [variant] = await db .select() .from(productVariants) .where(eq(productVariants.sku, sku)); return variant ?? null; }, create: async (item) => { console.log(`Unknown SKU from ERP: ${item.sku}`); }, update: async (item, local) => { await db .update(productVariants) .set({ stock: item.quantity }) .where(eq(productVariants.id, local.id)); } };

Webhook Handler

Standard SvelteKit endpoint with signature verification:

src/routes/api/webhooks/erp/+server.ts
import { json } from "@sveltejs/kit"; import { verifyHmacSha256, syncSingleItem } from "$lib/server/integrations"; import { env } from "$env/dynamic/private"; import { inventorySync } from "$lib/server/integrations/erp/inventory-sync"; export async function POST({ request }) { const body = await request.text(); const signature = request.headers.get("x-erp-signature"); if (!signature || !verifyHmacSha256(body, signature, env.ERP_WEBHOOK_SECRET!)) { return json({ error: "Invalid signature" }, { status: 401 }); } const payload = JSON.parse(body); switch (payload.event) { case "inventory.updated": await syncSingleItem(inventorySync, payload.data); break; case "order.shipped": const order = await orderService.getByCode(payload.reference); if (order) { await orderService.transitionState(order.id, "shipped"); } break; } return json({ received: true }); }

Environment Variables

ERP_BASE_URL="https://erp.example.com/api" ERP_API_KEY="your-api-key" ERP_WEBHOOK_SECRET="your-webhook-secret"
Last updated on