Node.js + Express
Esta receta muestra el flujo completo en una aplicación Express: autenticar, crear un cliente, emitir una factura y manejar el webhook de resultado.
Emitia-Version: 2026-01-01
Instalación
Section titled “Instalación”npm install express# Variables de entorno recomendadas:# EMITIA_API_KEY=sk_test_TU_CLAVE# EMITIA_WEBHOOK_SECRET=whsec_...Código completo
Section titled “Código completo”import crypto from 'crypto';import express from 'express';
const API_BASE = 'https://api.emitia.co/v1';const EMITIA_VERSION = '2026-01-01';
// Helper: request autenticadoasync function emitia( method: string, path: string, body?: object, idempotencyKey?: string,): Promise<unknown> { const headers: Record<string, string> = { Authorization: `Bearer ${process.env.EMITIA_API_KEY}`, 'Emitia-Version': EMITIA_VERSION, }; if (body) headers['Content-Type'] = 'application/json'; if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;
const res = await fetch(`${API_BASE}${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined, });
const data = await res.json(); if (!res.ok) throw Object.assign(new Error('Emitia API error'), { status: res.status, data }); return data;}
// 1. Verificar autenticación (funciona hoy)async function verifyAuth() { const account = await emitia('GET', '/account'); console.log('Conectado como:', (account as { name: string }).name); return account;}
// 2. Crear cliente (disponible con api-resources)async function createCustomer(nit: string, legalName: string) { return emitia( 'POST', '/customers', { organization_type: '1', identification: { type: '31', number: nit, check_digit: '1' }, legal_name: legalName, email: 'facturacion@empresa.co', address: 'Cra 1 # 1-1', city_code: '11001', department_code: '11', country_code: 'CO', }, crypto.randomUUID(), // Idempotency-Key );}
// 3. Crear factura (disponible con api-resources + test-mode-sandbox)async function createInvoice(customerId: string, orderId: string) { return emitia( 'POST', '/invoices', { customer_id: customerId, currency: 'COP', operation_type: '10', payment_means: { code: '10', type: '1' }, lines: [ { description: 'Servicio de consultoría', quantity: '1.00', unit_code: '94', unit_price: '100000.00', line_extension_amount: '100000.00', taxes: [{ code: '01', rate: '19.00', base: '100000.00', value: '19000.00' }], }, ], metadata: { order_id: orderId }, }, crypto.randomUUID(), // Genera UNA SOLA VEZ y guarda si necesitas reintentar );}
// 4. Verificar firma del webhookfunction verifyWebhook(rawBody: string, signature: string, secret: string): boolean { const parts = Object.fromEntries(signature.split(',').map((s) => s.split('='))); const t = parseInt(parts['t'] ?? '', 10); const v1 = parts['v1']; if (!t || !v1) return false; if (Math.abs(Date.now() / 1000 - t) > 300) return false; // 5 min tolerancia const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex'); return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(v1, 'hex'));}
// --- Servidor Express ---const app = express();
app.post('/hooks/emitia', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['emitia-signature'] as string ?? ''; const secret = process.env.EMITIA_WEBHOOK_SECRET ?? '';
if (!verifyWebhook(req.body.toString(), signature, secret)) { return res.status(401).json({ error: 'Firma inválida' }); }
const event = JSON.parse(req.body.toString()) as { type: string; data: { object: { id: string; cufe?: string } } };
switch (event.type) { case 'invoice.accepted_by_dian': console.log('Factura aceptada:', event.data.object.id, 'CUFE:', event.data.object.cufe); // Actualizar estado en tu DB break; case 'invoice.rejected_by_dian': console.error('Factura rechazada:', event.data.object.id); // Notificar al usuario break; }
res.status(200).json({ received: true });});
app.listen(3001, () => console.log('Webhook listener en puerto 3001'));
// Ejemplo de usoverifyAuth() .then(() => createCustomer('900111222', 'Acme SAS')) .then((customer) => createInvoice((customer as { id: string }).id, 'order-42')) .then((invoice) => console.log('Factura creada:', invoice)) .catch(console.error);Qué sigue
Section titled “Qué sigue”- Registra tu webhook endpoint en
POST /v1/webhook-endpoints - Configura las variables de entorno en producción
- Lee Conceptos: Idempotencia para entender el retry pattern