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.
Los webhooks empujan los cambios del ciclo de vida de un DTE a tu endpoint en vez de que tú hagas polling.
Eventos
Sección titulada «Eventos»| 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 |
Endpoints de la API
Sección titulada «Endpoints de la API»| 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. |
Registrar un endpoint
Sección titulada «Registrar un endpoint»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.
Verifica cada entrega — siempre
Sección titulada «Verifica cada entrega — siempre»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 });Reglas de verificación
Sección titulada «Reglas de verificación»- Verifica contra el body crudo. Re-serializar JSON parseado cambia el orden de bytes/espacios y la firma no calzará.
- Responde 2xx rápido. Haz el trabajo lento después de responder (encólalo). Las respuestas no-2xx y los timeouts se reentregan.
- Las entregas pueden llegar más de una vez. Usa
webhook-id(o tu propio estado porevent.data.id) para deduplicar. - 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.
Probar localmente
Sección titulada «Probar localmente»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}`,};Relacionado
Sección titulada «Relacionado»- Errores —
WebhookVerificationError - Quickstart — polling como alternativa