Node.js / TypeScript
Node.js / TypeScript Examples
Section titled “Node.js / TypeScript Examples”Complete integration examples using Node.js with TypeScript.
npm install node-fetch# or use the built-in fetch in Node.js 18+API Client Class
Section titled “API Client Class”A reusable client for the N’entropy EUDR Integration API:
const BASE_URL = "https://backend.joinnentropy.com/api/v1/integration";
interface ApiResponse<T> { success: boolean; data: T; error?: string;}
class EUDRClient { private apiKey: string;
constructor(apiKey: string) { this.apiKey = apiKey; }
private async request<T>( method: string, path: string, body?: unknown, ): Promise<T> { const res = await fetch(`${BASE_URL}${path}`, { method, headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json", }, body: body ? JSON.stringify(body) : undefined, });
if (!res.ok) { const error = await res.json(); throw new Error(`API Error ${res.status}: ${error.error?.message}`); }
const json: ApiResponse<T> = await res.json(); return json.data; }
// Suppliers async upsertSuppliers(suppliers: SupplierInput[]) { return this.request("POST", "/suppliers", { suppliers }); }
async getSuppliers(params?: { page?: number; limit?: number }) { const query = new URLSearchParams(); if (params?.page) query.set("page", String(params.page)); if (params?.limit) query.set("limit", String(params.limit)); return this.request("GET", `/suppliers?${query}`); }
// Products async upsertProducts(products: ProductInput[]) { return this.request("POST", "/products", { products }); }
async getProducts(params?: { page?: number; limit?: number }) { const query = new URLSearchParams(); if (params?.page) query.set("page", String(params.page)); if (params?.limit) query.set("limit", String(params.limit)); return this.request("GET", `/products?${query}`); }
// Batches async upsertBatches(batches: BatchInput[]) { return this.request("POST", "/batches", { batches }); }
// Sync status async getSyncStatus(entityType?: string) { const query = entityType ? `?entityType=${entityType}` : ""; return this.request("GET", `/sync-status${query}`); }
// DDS status async getDDSStatus(batchExternalId: string) { return this.request("GET", `/dds/${batchExternalId}`); }}
// Type definitionsinterface SupplierInput { externalId: string; name: string; country?: string; address?: string; city?: string; email?: string; phone?: string; taxId?: string;}
interface ProductInput { externalId: string; name: string; hsCode?: string; description?: string; supplierExternalId?: string;}
interface BatchInput { externalId: string; productExternalId: string; quantity?: number; unit?: string; batchDate?: string;}Usage Examples
Section titled “Usage Examples”Sync Suppliers from Your System
Section titled “Sync Suppliers from Your System”const client = new EUDRClient("eudr_live_abc123...");
// Fetch suppliers from your databaseconst mySuppliers = await db.query("SELECT * FROM suppliers");
// Transform and syncconst result = await client.upsertSuppliers( mySuppliers.map((s) => ({ externalId: s.id, name: s.company_name, country: s.country_code, address: s.street_address, city: s.city, email: s.contact_email, phone: s.phone, taxId: s.vat_number, })),);
console.log(`Synced: ${result.created} created, ${result.updated} updated`);Sync Products with Supplier Association
Section titled “Sync Products with Supplier Association”const client = new EUDRClient("eudr_live_abc123...");
const result = await client.upsertProducts([ { externalId: "PROD-001", name: "Brazilian Eucalyptus Timber", hsCode: "4407.11", description: "Sustainably sourced eucalyptus planks", supplierExternalId: "SUP-100", // Links to previously synced supplier }, { externalId: "PROD-002", name: "Indonesian Palm Oil", hsCode: "1511.10", supplierExternalId: "SUP-200", },]);
console.log(result);Create Batches
Section titled “Create Batches”const client = new EUDRClient("eudr_live_abc123...");
const result = await client.upsertBatches([ { externalId: "BATCH-2024-001", productExternalId: "PROD-001", quantity: 5000, unit: "kg", batchDate: "2024-06-15", },]);Check DDS Status
Section titled “Check DDS Status”const client = new EUDRClient("eudr_live_abc123...");
const ddsStatus = await client.getDDSStatus("BATCH-2024-001");
console.log("DDS Status:", ddsStatus);// {// batch: { externalId: 'BATCH-2024-001', ... },// product: { name: 'Brazilian Eucalyptus Timber', ... },// supplier: { name: 'Amazon Timber Co.', ... },// ddsSubmissions: [{ status: 'submitted', submittedAt: '...' }]// }Webhook Verification
Section titled “Webhook Verification”import { createHmac, timingSafeEqual } from "crypto";import express from "express";
const app = express();app.use(express.raw({ type: "application/json" }));
function verifyWebhook( payload: Buffer, signature: string, secret: string,): boolean { const expected = createHmac("sha256", secret).update(payload).digest("hex");
const sig = signature.replace("sha256=", ""); return timingSafeEqual(Buffer.from(sig), Buffer.from(expected));}
app.post("/webhooks/eudr", (req, res) => { const signature = req.headers["x-webhook-signature"] as string; const secret = process.env.WEBHOOK_SECRET!;
if (!verifyWebhook(req.body, signature, secret)) { return res.status(401).json({ error: "Invalid signature" }); }
const event = JSON.parse(req.body.toString());
switch (event.event) { case "supplier.created": console.log("New supplier synced:", event.data); break; case "sync.completed": console.log("Sync finished:", event.data); break; case "dds.status_changed": console.log("DDS status update:", event.data); break; }
res.json({ received: true });});
app.listen(3000);Error Handling
Section titled “Error Handling”async function syncWithRetry( client: EUDRClient, suppliers: SupplierInput[], maxRetries = 3,) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await client.upsertSuppliers(suppliers); } catch (error: any) { if (error.message.includes("429")) { // Rate limited — wait and retry const waitMs = Math.pow(2, attempt) * 1000; console.log(`Rate limited, retrying in ${waitMs}ms...`); await new Promise((r) => setTimeout(r, waitMs)); continue; } if (attempt === maxRetries) throw error; } }}