{"id":50333689,"url":"https://github.com/rricajos/qrsgen","last_synced_at":"2026-06-02T10:01:10.063Z","repository":{"id":359544218,"uuid":"1246554116","full_name":"rricajos/qrsgen","owner":"rricajos","description":"WhatsApp ↔ HTTP API bridge en Go. Multi-instance + multi-tenant (downstream routing por owner_tag), lifecycle webhooks, audit log inmutable, outbox persistente, métricas Prometheus. Integrable con n8n y cualquier sistema que hable HTTP.","archived":false,"fork":false,"pushed_at":"2026-05-29T11:56:07.000Z","size":2247,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-29T12:30:38.924Z","etag":null,"topics":["audit-log","bridge","chatops","chatwoot","docker-swarm","golang","http-api","multi-instance","multi-tenant","outbox-pattern","prometheus","qr-code","webhook","whatsapp","whatsmeow"],"latest_commit_sha":null,"homepage":"https://rricajos.github.io/qrsgen","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rricajos.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE.md","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":null},"created_at":"2026-05-22T10:01:18.000Z","updated_at":"2026-05-29T11:56:09.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/rricajos/qrsgen","commit_stats":null,"previous_names":["rricajos/qrsgen"],"tags_count":60,"template":false,"template_full_name":null,"purl":"pkg:github/rricajos/qrsgen","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rricajos%2Fqrsgen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rricajos%2Fqrsgen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rricajos%2Fqrsgen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rricajos%2Fqrsgen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rricajos","download_url":"https://codeload.github.com/rricajos/qrsgen/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rricajos%2Fqrsgen/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33816488,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-02T02:00:07.132Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audit-log","bridge","chatops","chatwoot","docker-swarm","golang","http-api","multi-instance","multi-tenant","outbox-pattern","prometheus","qr-code","webhook","whatsapp","whatsmeow"],"created_at":"2026-05-29T12:00:44.943Z","updated_at":"2026-06-02T10:01:10.057Z","avatar_url":"https://github.com/rricajos.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# qrsgen\n\n[![CI](https://github.com/rricajos/qrsgen/actions/workflows/test.yml/badge.svg)](https://github.com/rricajos/qrsgen/actions/workflows/test.yml)\n[![CodeQL](https://github.com/rricajos/qrsgen/actions/workflows/codeql.yml/badge.svg)](https://github.com/rricajos/qrsgen/actions/workflows/codeql.yml)\n[![Go Report](https://goreportcard.com/badge/github.com/rricajos/qrsgen)](https://goreportcard.com/report/github.com/rricajos/qrsgen)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![Docs](https://img.shields.io/badge/docs-live-blue.svg)](https://rricajos.github.io/qrsgen/)\n\nWhatsApp ↔ HTTP API bridge en Go. Mantiene una sesión WhatsApp Web (vía\n[whatsmeow](https://github.com/tulir/whatsmeow)) por instancia y la expone\ncomo una API HTTP REST estándar — con outbox persistido, detector de\nban-risk, audit log inmutable y usage tracking listo para facturación.\n\n\u003e ⚠️ **Aviso legal importante**: este proyecto **no está afiliado** con\n\u003e WhatsApp / Meta. Usa una API no oficial obtenida por ingeniería inversa.\n\u003e Lee [DISCLAIMER.md](DISCLAIMER.md) antes de desplegar — explica el riesgo\n\u003e de baneo del número, ToS, GDPR, y limitación de responsabilidad.\n\n## Qué hace\n\n```\nTu sistema (n8n, app custom, CRM, etc.)\n        │\n        │ HTTP REST + Bearer auth (+ HMAC opcional)\n        ▼\n     qrsgen ──── WebSocket TLS ────► Meta servers\n        │\n        │ HTTP POST con incoming msgs\n        ▼ HTTP POST con lifecycle events\n   Tu webhook endpoint\n```\n\n- **Incoming** (cliente → tu sistema): qrsgen recibe el msg por\n  WebSocket, lo POSTea a tu webhook.\n- **Outgoing** (tu sistema → cliente): tu sistema POSTea a qrsgen;\n  qrsgen lo envía por WhatsApp.\n- **Outbox**: si la instancia está disconnected cuando llega un outgoing,\n  qrsgen lo encola hasta 5 min y reentrega cuando vuelve. El downstream\n  recibe `202 queued` con `queue_id` en lugar de un error.\n- **Lifecycle events** (conexión, QR, ban risk, outbox expirations, etc.):\n  POST a webhook configurable por instancia.\n\n## Por qué existe\n\n- **Multi-instancia real**: un binario gestiona N números, cada uno con\n  su WebSocket contra Meta.\n- **Multi-tenant / multi-downstream**: cada instancia puede llevar un\n  `owner_tag` que la mapea a un downstream propio (URL/token/account)\n  vía la tabla `bridge_tenant`. Un solo proceso qrsgen puede servir\n  varios clientes con destinos distintos. `/api/tenants/*` para CRUD.\n  Si no hay tenant configurado, fallback al `DOWNSTREAM_*` del env\n  (backward compat single-tenant).\n- **HTTP-first**: cualquier sistema que hable HTTP integra sin SDK.\n- **Persistencia robusta**: sesiones whatsmeow en Postgres → restarts no\n  requieren reescanear QR.\n- **Outbox 5 min TTL**: cero pérdida durante deploys o blips cortos.\n- **BanWatcher proactivo**: tres señales (velocity / diversity /\n  delivery ratio) y un score 0-1 + level para reducir ritmo antes de que\n  WhatsApp tome medidas.\n- **Audit log inmutable**: tabla con triggers que rechazan UPDATE/DELETE\n  a nivel DB. Forensics tamper-evident.\n- **Usage tracking persistido**: contadores diarios + agregado mensual\n  por `owner_tag` → endpoint `/api/usage/summary` listo para facturación.\n- **HMAC opcional** del webhook entrante. **Read-only rootfs** del\n  container.\n- **Lifecycle observable**: 12 eventos distintos, incluyendo\n  `ban_risk`, `outgoing_expired`, `spam_blocked`.\n- **Group prefix + retroactive rename** (v0.40.0+, persistido en\n  Postgres v0.41.0): mensajes de grupo llevan\n  `` `+phone · ~name` `` como header (tilde solo si el contacto no\n  está en la agenda). Cuando añades un contacto a tu agenda\n  WhatsApp, qrsgen **reescribe retroactivamente** los headers de\n  mensajes históricos en Chatwoot Y renombra el contact (v0.43.0).\n  Endpoint admin `POST /api/instances/:name/retroactive/reconcile`\n  para backfillear toda la agenda de golpe.\n- **Quote/reply context bidireccional** (v0.42.0+v0.44.0): cuando\n  un usuario WhatsApp responde a un mensaje, qrsgen renderiza el\n  citado como blockquote sobre el body en Chatwoot\n  (`` `↪ +phone · name` `` + texto). Al revés: cuando el agente\n  hace quote-reply en el composer de Chatwoot, qrsgen propaga la\n  respuesta como reply nativo de WhatsApp con `ContextInfo`.\n- **Avatar sync bidireccional, reactions, typing indicators, read\n  receipts, mark-as-read** (v0.31.0..v0.39.0): el estado en\n  WhatsApp se refleja en Chatwoot y viceversa donde tiene sentido\n  (doble check azul, \"está escribiendo\", reactions inline).\n\n## Integración\n\nqrsgen es agnóstico del downstream. Como referencia,\n[`docs/integrations/n8n.md`](docs/integrations/n8n.md) muestra cómo orquestar el\nflujo con [n8n](https://n8n.io/), pero cualquier otro stack (Zapier, Make,\nTemporal, app web propia, scripts) funciona igual — la integración es\nHTTP estándar.\n\n## Estado del proyecto\n\nEn producción con 4+ instancias activas. Tag actual: **v0.64.x**\n(feature-complete pre-v1.0.0). El\n[CHANGELOG](CHANGELOG.md) documenta cada release.\n\n**Features destacadas del ciclo v0.5x / v0.6x**:\n\n- **LID mention resolution** con cascada pushname → saved → phone →\n  redacted phone → raw (v0.53.0/1).\n- **Reactions as quote-reply** vía `content_attributes.in_reply_to`\n  (v0.53.2).\n- **Backdate worker** que corrige `messages.created_at` en Chatwoot\n  desde `external_created_at`, resolviendo el problema de imports\n  históricos con timestamp \"now\" (v0.54.0).\n- **On-demand history import con `days` param** acota la ventana\n  per-request sin tocar config global (v0.54.4).\n- **Edit message support** vía `whatsmeow.BuildEdit` (v0.60.0,\n  endpoint `POST /api/instances/:name/messages/:waid/edit`).\n- **`/api/version`** con SHA + build date inyectados por ldflags\n  (v0.55.0). Útil para deploy verification.\n- **OpenAPI 3.0 spec** en `docs/api/openapi.yaml` (v0.61.0).\n- **`Retry-After` respect** con `RateLimitError` tipado para 429s\n  del downstream (v0.59.0).\n\nRoadmap hacia v1.0.0: 7+ días de soak en v0.64.x → `v1.0.0-rc.1`\n→ 14+ días RC → `v1.0.0` final. Ver el `Unreleased` section del\n[CHANGELOG](CHANGELOG.md#unreleased) para el status completo.\n\n## Documentación\n\nToda la documentación está en https://rricajos.github.io/qrsgen/ y en\n[`docs/`](docs/):\n\n- [Architecture](docs/architecture/) — capas, flujos, persistencia, concurrencia.\n- [API](docs/api/) — endpoints, payloads, lifecycle webhooks, ejemplos curl.\n- [Deployment](docs/deployment/) — stack swarm + portabilidad multi-VPS + Traefik.\n- [Security](docs/security/) — 7 capas (Bearer / HMAC / firewall / TLS / hardening / audit / backups).\n- [Operations](docs/operations/) — runbook, métricas, troubleshooting.\n- [Integrations](docs/integrations/) — recetas para n8n, Python, etc.\n- [Migrations](docs/migrations/) — venir desde Evolution API / wajs / Baileys / SaaS, o salir de qrsgen.\n- [Backup runbook](ops/backup/README.md) — install, restore, drop-in config.\n- [Migration tools](tools/migrate/) — `bulk-provision.py` / `validate.py` / `export-config.py`.\n- [Examples](examples/) — recetas de integración self-contained: curl, Python (FastAPI), Node, n8n workflows, Grafana dashboard, recipe multi-tenant SaaS.\n\n## Stack técnico\n\n- **Go 1.25+** + **Echo v4** (HTTP framework).\n- **whatsmeow** (`go.mau.fi/whatsmeow`) — cliente WhatsApp Multi-Device.\n  Sesiones en Postgres vía `sqlstore`.\n- **pgx/v5** — driver Postgres.\n- **slog** (stdlib) — logger JSON estructurado.\n- **caarlos0/env** — parseo de env vars.\n- **skip2/go-qrcode** — generación PNG del QR.\n- **prometheus/client_golang** — métricas.\n\nImagen final ~25MB sobre `gcr.io/distroless/static-debian12:nonroot`.\nRead-only rootfs + tmpfs 64 MB en `/tmp`.\n\n## Quick start\n\n```bash\ncp .env.example .env\n# editar: POSTGRES_PASSWORD, QRSGEN_API_TOKEN, DOWNSTREAM_*\ndocker stack deploy -c docker-compose.yml qrsgen\n```\n\nO usa la imagen pre-built desde GHCR:\n\n```bash\ndocker pull ghcr.io/rricajos/qrsgen:latest\n```\n\n## Tests\n\n```bash\ndocker run --rm -v \"$PWD\":/src -w /src golang:1.25-alpine go test ./...\n```\n\nTests de integración (con DB real):\n\n```bash\nINTEGRATION_PG_DSN='postgres://postgres@localhost:5432/bridge?sslmode=disable' \\\ndocker run --rm -v \"$PWD\":/src -w /src --network host \\\n  -e INTEGRATION_PG_DSN \\\n  golang:1.25-alpine go test ./...\n```\n\n## Lifecycle events\n\nqrsgen emite los siguientes eventos HTTP POST a `events_webhook_url`\n(configurable por instancia):\n\n| Evento | Cuándo | Extras |\n|---|---|---|\n| `qr_generated` | Nuevo QR PNG disponible (cada ~20s mientras pairing). | `last_qr_msg_id` |\n| `paired` | `events.PairSuccess` tras escaneo. | – |\n| `connected` | WebSocket activo con JID válido. | – |\n| `reconnected` | Connected confirmado estable tras un `unreachable`. | – |\n| `unreachable` | WebSocket caído (tras 60s de silencio: blip silencioso si vuelve antes). | – |\n| `disconnected` | Sigue caído tras 2 min de grace. | – |\n| `logged_out` | Sesión invalidada server-side. Necesita nuevo QR. | – |\n| `strike` | TemporaryBan o ConnectFailure 4xx. **Acción inmediata.** | – |\n| `spam_blocked` | Outgoing duplicado bloqueado. | `count`, `preview` |\n| `ban_risk` | Detector cruzó threshold (velocity / diversity / delivery). | `alert`, `score`, `level`, ... |\n| `outgoing_expired` | Mensaje en outbox no se entregó antes del TTL (5 min). | `queue_id`, `remote_jid`, `preview` |\n| `backend_restarting` | SIGTERM recibido, shutdown iniciándose. | – |\n| `backend_started` | Bootstrap completo post-restart. | – |\n\n## Métricas Prometheus\n\n`GET /metrics` (sin auth):\n\n```\nqrsgen_messages_total{direction=\"in\"|\"out\",instance}\nqrsgen_spamguard_blocks_total{instance}\nqrsgen_lifecycle_events_total{instance,event}\nqrsgen_message_dispatch_errors_total{direction,instance,kind}\nqrsgen_active_instances\nqrsgen_total_instances\n```\n\nPlus métricas estándar Go runtime (`go_*`, `process_*`).\n\n## Releases\n\nTagging `vX.Y.Z` dispara GoReleaser:\n\n- Binarios linux/darwin/windows × amd64/arm64.\n- Imagen multi-arch en `ghcr.io/rricajos/qrsgen`.\n- SBOMs por arquitectura.\n- Cosign signatures para checksum y manifest.\n- GitHub Release con notas del CHANGELOG.\n\n## Licencia y avisos legales\n\n- [LICENSE](LICENSE) — MIT.\n- [DISCLAIMER.md](DISCLAIMER.md) — riesgos WhatsApp ToS, GDPR, limitación\n  de responsabilidad.\n- [NOTICE.md](NOTICE.md) — atribución a librerías de terceros y marcas.\n- [SECURITY.md](SECURITY.md) — política de divulgación responsable.\n- [CONTRIBUTING.md](CONTRIBUTING.md) — guía para contribuir.\n- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) — Contributor Covenant.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frricajos%2Fqrsgen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frricajos%2Fqrsgen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frricajos%2Fqrsgen/lists"}