Skip to content
SimploSimplo Docs

Webhooks

El contrato de webhooks de la API de Simplo — eventos del ciclo de vida DTE, registro de endpoints y verificación de firma Standard Webhooks.

This content is not available in your language yet.

Los webhooks empujan los cambios del ciclo de vida de un DTE a tu endpoint en vez de que tú hagas polling.

Evento Se dispara cuando
dte.created Se emitió un DTE
dte.accepted El SII aceptó el DTE
dte.rejected El SII rechazó el DTE
dte.repaired El SII aceptó el DTE con observaciones (con_reparos)
caf.low_stock Un CAF se está quedando sin folios
certificado.expiring El certificado digital está próximo a vencer
Método Path Descripción
GET /api/v1/billing/webhooks Listar los webhooks registrados.
POST /api/v1/billing/webhooks Registrar una URL HTTPS para recibir eventos.
DELETE /api/v1/billing/webhooks/{id} Eliminar un webhook.
const webhook = await simplo.webhooks.create({
url: 'https://tu-app.example/webhooks/simplo', // debe ser HTTPS
events: ['dte.accepted', 'dte.rejected', 'caf.low_stock'],
});
// El secreto de firma se devuelve SOLO acá. Guárdalo ahora.
await secrets.store('SIMPLO_WEBHOOK_SECRET', webhook.secret!);

simplo.webhooks.list() muestra los endpoints registrados (secretos enmascarados); simplo.webhooks.delete(id) elimina uno.

Cualquiera puede hacer POST a tu endpoint. Verifica cada entrega antes de confiar en ella. La entrega usa el esquema de firma Standard Webhooks — este es el contrato que las entregas llevarán cuando se habiliten, y el que implementa webhooks.verify:

Header Contenido
webhook-id ID único del mensaje (estable entre reentregas)
webhook-timestamp Timestamp Unix (segundos)
webhook-signature v1,<HMAC-SHA256 en base64> (lista separada por espacios)

La firma cubre `${id}.${timestamp}.${rawBody}`. El SDK la verifica con comparación en tiempo constante y una tolerancia de ±5 minutos en el timestamp:

import Simplo, { WebhookVerificationError } from '@simplohq/sdk';
import express from 'express';
const simplo = new Simplo();
const app = express();
// IMPORTANTE: el body CRUDO, no el JSON parseado.
app.post('/webhooks/simplo', express.raw({ type: 'application/json' }), async (req, res) => {
try {
const event = await simplo.webhooks.verify(
req.body.toString('utf8'),
req.headers,
process.env.SIMPLO_WEBHOOK_SECRET!,
);
switch (event.type) {
case 'dte.accepted':
await marcarFacturaAceptada(event.data);
break;
case 'dte.rejected':
await alertarFacturacion(event.data);
break;
}
res.sendStatus(200);
} catch (error) {
if (error instanceof WebhookVerificationError) {
res.sendStatus(401); // entrega no confiable — rechaza
return;
}
throw error;
}
});

También disponible como función standalone (sin cliente):

import { verifyWebhook } from '@simplohq/sdk';
const event = await verifyWebhook(rawBody, headers, secret, { toleranceSeconds: 300 });
  1. Verifica contra el body crudo. Re-serializar JSON parseado cambia el orden de bytes/espacios y la firma no calzará.
  2. Responde 2xx rápido. Haz el trabajo lento después de responder (encólalo). Las respuestas no-2xx y los timeouts se reentregan.
  3. Las entregas pueden llegar más de una vez. Usa webhook-id (o tu propio estado por event.data.id) para deduplicar.
  4. El orden no está garantizado. Trata cada evento como “algo cambió; re-verifica el estado” cuando el orden importe — o lee el estado autoritativo con dtes.retrieve.

Calcula una firma válida en tus tests en vez de tocar la red:

import { createHmac } from 'node:crypto';
const secret = 'test-secret';
const id = 'msg_1';
const timestamp = Math.floor(Date.now() / 1000);
const payload = JSON.stringify({ type: 'dte.accepted', data: { id: 'dte_1' } });
const signature = createHmac('sha256', secret)
.update(`${id}.${timestamp}.${payload}`)
.digest('base64');
const headers = {
'webhook-id': id,
'webhook-timestamp': String(timestamp),
'webhook-signature': `v1,${signature}`,
};