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