Skip to content

Node.js / TypeScript

Complete integration examples using Node.js with TypeScript.

Terminal window
npm install node-fetch
# or use the built-in fetch in Node.js 18+

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 definitions
interface 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;
}
const client = new EUDRClient("eudr_live_abc123...");
// Fetch suppliers from your database
const mySuppliers = await db.query("SELECT * FROM suppliers");
// Transform and sync
const 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`);
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);
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",
},
]);
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: '...' }]
// }
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);
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;
}
}
}