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;
}
}
}