Qué incluye
Un servicio completo que lees en una tarde, no un framework. Tu lógica va en una sola función, handle(); todo lo que la rodea ya está hecho y probado.
sender --POST--> /webhook/{source}
1. read the body (size limit)
2. verify the HMAC signature -> 401 if wrong, nothing stored
3. dedupe by delivery id -> the same event twice is handled once
4. insert into SQLite -> 202 Accepted, fast
worker ----------> handle(event) retries with backoff, dead after N attempts
Características
- Tres esquemas de firma. Estilo GitHub, estilo Stripe (con ventana anti-replay) y Standard Webhooks, el que usa Polar. La comparación se hace en tiempo constante.
- Rotación de secretos.
WEBHOOK_SECRETacepta una lista separada por comas, de modo que durante una rotación valen el secreto viejo y el nuevo, y no se pierde ningún evento. - Deduplicación. Por id de entrega (con Standard Webhooks se usa automáticamente el header
webhook-id) o, si no hay, por un hash del cuerpo. Se apoya en una restricción de unicidad, así que sobrevive a los reinicios. - Queue duradera. SQLite en modo WAL; la petición se confirma tras un solo commit. Backoff exponencial, dead-letter tras
MAX_ATTEMPTSy recuperación de los eventos que un crash dejó enprocessing. - Concurrencia.
WORKERS=Nworkers; un evento nunca se entrega a dos workers. - Operación.
server replaypara los eventos muertos,server purgeyRETENTION_DAYSpara las filas viejas,/metricsen formato Prometheus y un log de acceso en JSON con ids de petición que nunca contiene payloads, headers ni secretos. - Handlers seguros.
worker.Permanent(err)aparta un payload que nunca podrá procesarse, sin gastar retries. Si el apagado llega en mitad de un handler, el evento vuelve a la queue sin perder un intento. - Ejemplos. Un handler de
order.paidde Polar y un router JSON genérico, ambos con tests. Incluye Dockerfile y unidad de systemd.
Go puro (sin cgo). Dependencias: la biblioteca estándar y modernc.org/sqlite.
Mediciones
Método: el generador de carga incluido (make loadtest) contra el binario compilado, en un portátil con Apple Silicon, 32 conexiones, payloads de 512 bytes, HTTP real sobre loopback, durabilidad por defecto (WAL, synchronous=FULL), 5,000 eventos por ejecución, tres ejecuciones.
| Qué | Resultado |
|---|---|
| Eventos aceptados por HTTP (verificar, insertar de forma duradera, 202), estilo GitHub | de 7,200 a 9,200 por segundo aproximadamente (p50 2.7 ms, p99 unos 17 ms) |
| Lo mismo, con Standard Webhooks | unos 9,400 por segundo |
| De punta a punta, desde la primera petición hasta la queue vacía (handler que no hace nada), 1 worker | de 3,100 a 3,200 por segundo aproximadamente |
| Vaciado de la queue, con un handler que duerme 5 ms (como una llamada de red), 1 / 8 / 32 workers | unos 163 / 1,463 / 5,264 por segundo |
El coste principal es la base de datos, no HTTP: todo el acceso a SQLite pasa por una sola conexión, así que más workers compensan cuando tu handler espera algo, como una llamada de red u otra base de datos.
Cómo se probó
make test ejecuta go vet y 41 tests con el detector de race conditions. Cubren firmas manipuladas, vencidas, del futuro y mal formadas; el vector de respuesta conocida de Standard Webhooks tomado de la biblioteca de referencia en Go; la rotación de secretos; que un 401 no guarda nada; entregas duplicadas; peticiones demasiado grandes o con el método equivocado; retries y backoff; dead-letter; recuperación tras un crash; orden; muchos workers sin procesar nunca un evento dos veces; replay; purge; y la actualización de una base de datos de la versión 0.1.0. Además, el binario compilado a partir del zip empaquetado se probó con peticiones HTTP reales y firmadas para los tres esquemas, incluido el replay de una misma entrega.
Para quién es
Para desarrolladores que necesitan recibir webhooks de Polar, Stripe, GitHub o similares y quieren las partes poco vistosas bien resueltas: firmas, retries, deduplicación y una queue que sobreviva a un crash.
Está pensado para un solo servidor: un único proceso, un único escritor SQLite y eventos procesados en orden por defecto. La entrega es at-least-once, así que tu handler debe ser idempotente.
Preguntas frecuentes
¿Verifica los webhooks de Polar?
Sí. Polar usa Standard Webhooks, que está soportado, y el starter incluye un handler de ejemplo para los eventos de pedidos de Polar.
¿Puedo usar solo el código de las firmas?
Sí. Está disponible por separado como un módulo pequeño con licencia MIT, webhooksig; mira la página de herramientas gratuitas.
¿Necesito CGO o un compilador de C?
No. El driver de SQLite es Go puro.
¿Qué versión de Go hace falta?
Go 1.24 o posterior.
¿Qué licencia tiene?
Úsalo en tus propios proyectos. No revendas el código fuente como un starter de la competencia.
Historial de cambios
- 0.2.1: driver de SQLite actualizado a v1.44.0 (requiere Go 1.24 o superior).
- 0.2.0: Standard Webhooks, rotación de secretos, varios workers, comandos replay y purge,
/metrics, log de acceso, ejemplo de Polar, cifras medidas. - 0.1.0: primera versión.
Leer más
- Webhook signature checks that fail or quietly pass in Go (en inglés)
- A durable webhook retry queue in SQLite (en inglés)
- Webhook idempotency and dedupe for SQLite and Postgres (en inglés)
- Verify Standard Webhooks (Polar) signatures in Go (en inglés)
- Receiving signed webhooks in Go with a durable queue (en inglés)
Obtener
Polar, como merchant of record, se ocupa del pago, los impuestos y las facturas. Al terminar la compra recibes un enlace de descarga.