Saltar al contenido

Guías

Webhooks

Avisos firmados cuando algo pasa: eventos, firma HMAC y reintentos.

Configurar

En el panel, Integraciones → Nuevo webhook: la dirección de tu servidor (https) y los eventos que quieres recibir. Al crearlo se muestra una sola vez su secreto, con el que verificas que cada aviso viene de Simpligastos. Guárdalo como guardas la llave.

La dirección tiene que ser pública: no se aceptan direcciones de redes internas, y Simpligastos no sigue redirecciones.

El aviso

En el momento en que pasa un evento suscrito, Simpligastos manda un POST con JSON. El aviso es liviano: trae el evento, la entidad y su id; el detalle lo pides con la API.

POST a tu dirección
{
  "evento": "rendicion.aprobada",
  "version": "1",
  "entidad": "rendicion",
  "id": "3e8bcc2a-85b8-49a0-8fa2-5339c8936d93",
  "detalle": { "comentario": "Ok" },
  "ocurrido_en": "2026-10-04T12:02:11.482391-03:00",
  "envio": "b1f0c9a2-2c1e-4c8a-9a51-0e3c4f5a6b7c"
}
Simpligastos-Evento
El evento, por ejemplo rendicion.aprobada.
Simpligastos-Envio
El id del envío. Es la llave de idempotencia: el mismo envío puede llegar dos veces.
Simpligastos-Firma
t=<segundos>,v1=<firma>: la firma HMAC-SHA256 del aviso con el secreto del webhook.
User-Agent
Simpligastos-Webhooks/1

Verificar la firma

La firma es el HMAC-SHA256, en hexadecimal, de t, un punto y el cuerpo tal como llegó, con el secreto del webhook. Calcúlala sobre el cuerpo crudo, antes de interpretarlo como JSON: si lo vuelves a serializar, cambia y la firma no calza. Compara en tiempo constante y descarta los avisos de más de 5 minutos.

import { createHmac, timingSafeEqual } from "node:crypto";

// cuerpo: el texto tal como llegó, antes de interpretarlo como JSON.
function verificar(cuerpo, encabezado, secreto) {
  const { t, v1 } = Object.fromEntries(encabezado.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // más de 5 minutos: repetido
  const esperado = Buffer.from(createHmac("sha256", secreto).update(`${t}.${cuerpo}`).digest("hex"));
  const recibido = Buffer.from(v1 ?? "");
  return recibido.length === esperado.length && timingSafeEqual(esperado, recibido);
}

Un receptor mínimo en Node, sin dependencias:

Receptor
import { createServer } from "node:http";

createServer(async (pedido, respuesta) => {
  let cuerpo = "";
  for await (const parte of pedido) cuerpo += parte;
  if (!verificar(cuerpo, pedido.headers["simpligastos-firma"] ?? "", process.env.SIMPLIGASTOS_SECRETO)) {
    respuesta.writeHead(400).end();
    return;
  }
  const aviso = JSON.parse(cuerpo);
  // Responde rápido y procesa aparte: el mismo envío puede llegar dos veces.
  respuesta.writeHead(204).end();
  procesar(aviso); // usa aviso.envio para no procesarlo dos veces
}).listen(3000);

Respuesta y reintentos

Responde cualquier 2xx en menos de 5 segundos; si tienes que hacer algo largo, responde primero y procésalo aparte. Si no responde a tiempo o responde otra cosa, se reintenta, esperando entre un intento y el siguiente 1 minuto, 5 minutos, 30 minutos, 2 horas, 6 horas, 12 horas. Pasadas 24 horas desde el evento, queda como fallido.

En el panel ves cada envío con su respuesta y puedes reenviarlo. Como un aviso puede llegar más de una vez, guarda el Simpligastos-Envio que ya procesaste y descarta los repetidos.

Los avisos pueden llegar en otro orden que el de los eventos. Si el orden importa, compara ocurrido_en.

Eventos

Cada evento es entidad.acción. Eliges cuáles recibir en cada webhook.

gasto

  • gasto.creado
  • gasto.leido
  • gasto.corregido

rendicion

  • rendicion.creada
  • rendicion.enviada
  • rendicion.paso_aprobado
  • rendicion.aprobada
  • rendicion.aprobada_sola
  • rendicion.aprobada_automatica
  • rendicion.rechazada
  • rendicion.cerrada
  • rendicion.contabilizada

persona

  • persona.invitada
  • persona.invitacion_aceptada
  • persona.modificada
  • persona.desactivada

fondo

  • fondo.creado
  • fondo.abonado
  • fondo.cerrado

solicitud_fondo

  • solicitud_fondo.pedida
  • solicitud_fondo.aprobada
  • solicitud_fondo.rechazada

pago

  • pago.pagado
  • pago.anulado

nomina

  • nomina.generada
  • nomina.pagada