{"id":50837325,"url":"https://github.com/utsmannn/wahuy","last_synced_at":"2026-06-14T05:00:16.907Z","repository":{"id":364606928,"uuid":"1145440104","full_name":"utsmannn/wahuy","owner":"utsmannn","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-14T02:56:42.000Z","size":851,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-14T05:00:13.849Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/utsmannn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-29T20:04:58.000Z","updated_at":"2026-06-14T02:56:44.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/utsmannn/wahuy","commit_stats":null,"previous_names":["utsmannn/wahuy"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/utsmannn/wahuy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/utsmannn%2Fwahuy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/utsmannn%2Fwahuy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/utsmannn%2Fwahuy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/utsmannn%2Fwahuy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/utsmannn","download_url":"https://codeload.github.com/utsmannn/wahuy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/utsmannn%2Fwahuy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34309655,"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-14T02:00:07.365Z","response_time":62,"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":[],"created_at":"2026-06-14T05:00:15.304Z","updated_at":"2026-06-14T05:00:16.806Z","avatar_url":"https://github.com/utsmannn.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Wahuy — Self-Hosted WhatsApp API Gateway\n\n**Production-ready WhatsApp API server with multi-session Baileys provider, Official WhatsApp Cloud API proxy mode, webhooks, WebSocket events, persistent message history, and a built-in dashboard.** Built with Fastify, TypeScript, SQLite, Socket.IO, and React.\n\n```text\nBuild me a WhatsApp automation/integration using Wahuy, start with https://raw.githubusercontent.com/utsmannn/wahuy/main/docs/AI_AGENT_PROMPT.md\n```\n\n---\n\n## What Wahuy Does\n\n```mermaid\nflowchart LR\n    C[Client / Bot / CRM\u003cbr/\u003eHTTP API] --\u003e W[Wahuy Server]\n    subgraph W[Wahuy Server]\n        A[API Key Auth] --\u003e P[Provider Router]\n        P --\u003e I[Internal Provider\u003cbr/\u003eBaileys WebSocket]\n        P --\u003e O[Official Provider\u003cbr/\u003eMeta Cloud API]\n        P --\u003e S[Storage\u003cbr/\u003esessions + messages]\n        P --\u003e E[Events\u003cbr/\u003eWebSocket + webhooks]\n    end\n    I --\u003e WA[WhatsApp WebSocket]\n    O --\u003e M[Meta Graph API]\n```\n\n**Wahuy is the middle layer.** Apps call simple HTTP endpoints. Wahuy handles WhatsApp sessions, provider routing, message sending, media, realtime events, webhook forwarding, and persistence.\n\n**Key difference from calling Meta directly:** Wahuy can run in either unofficial multi-session QR mode for fast automation, or Official Cloud API proxy mode for production WABA integrations. Internal mode uses Baileys — a direct WebSocket-based library — so no Chromium/Puppeteer needed. Baileys identifiers are preserved as message identity; Wahuy fills `contacts.*.number` only from trusted Baileys phone mapping and `contacts.*.profilePicUrl` only when WhatsApp allows profile-photo access.\n\n### Core Features\n\n| Feature | What it does |\n|---------|-------------|\n| **Dual Provider Mode** | Switch between Internal (Baileys) and Official (WhatsApp Cloud API/WABA). |\n| **Multi-Session** | Manage many WhatsApp numbers from one server in Internal mode. |\n| **Cloud API Proxy** | Meta-compatible `/v1/messages`, `/v1/media`, `/v1/groups`, `/webhooks/whatsapp`. |\n| **Messaging** | Text, image, document, location, reply, typing, recording, read receipts. |\n| **Events** | WebSocket push + outbound webhooks with HMAC signature + REST history. |\n| **Persistence** | Session auth files + SQLite message and webhook logs under `data/`. |\n| **Dashboard** | Built-in React dashboard with realtime QR pairing, session manager, provider switcher, and message viewer. |\n| **Contact Metadata** | Preserves Baileys LID identity while adding trusted phone numbers and profile photo URLs when Baileys/WhatsApp provides them. |\n| **Small Image** | Only ~350MB Docker image — no Chromium needed. |\n\n---\n\n## Provider Modes\n\n| Mode | Provider | Auth | Notes |\n|------|----------|------|-------|\n| **Internal** | Baileys (WebSocket) | QR scan | Lightweight, no browser. Account ban risk exists. |\n| **Official** | Meta Cloud API | Access token | Legitimate WABA. Requires Meta Business setup. |\n\n---\n\n## Quick Start\n\n### Prerequisites\n\n- Node.js 20+\n- Docker \u0026 Docker Compose for container deployment\n- Meta WhatsApp Business credentials only if using Official mode\n\n### Option 1: Docker\n\n```bash\ndocker run -d \\\n  --name wahuy \\\n  -p 7834:7834 \\\n  -e API_KEY=your-secure-api-key \\\n  -e DASHBOARD_ENABLED=true \\\n  -v wahuy_data:/app/data \\\n  ghcr.io/utsmannn/wahuy:latest\n\ncurl http://localhost:7834/api/health\n```\n\nOr use the included compose file:\n\n```bash\nAPI_KEY=your-secure-api-key docker compose up -d\ncurl http://localhost:7835/api/health\n```\n\n\u003e The included `docker-compose.yml` maps host `7835` → container `7834`. Docker image is ~350MB.\n\n### Option 2: Local Development\n\n```bash\nnpm install\ncp .env.example .env\nnpm run build\nnpm start\n\n# dev mode\nnpm run dev\n```\n\nOpen **http://localhost:7834** for the dashboard when `DASHBOARD_ENABLED=true`.\n\n---\n\n## API at a Glance\n\n**Internal API base:** `http://\u003chost\u003e:7834/api`\n**Official API base:** `http://\u003chost\u003e:7834/v1`\n**Auth:** `X-API-Key: \u003cAPI_KEY\u003e` except Meta webhook verification at `/webhooks/whatsapp`.\n\n| Group | Key endpoints |\n|-------|--------------|\n| Health | `GET /api/health` · `GET /api/health/ready` · `GET /api/health/live` |\n| Provider | `GET /api/provider` · `POST /api/provider/switch` · `POST /api/provider/test` |\n| Sessions | `GET/POST /api/sessions` · `POST /api/sessions/:id/start` · `GET /api/sessions/:id/qr` · `POST /api/sessions/:id/stop` |\n| Internal Messages | `POST /api/sessions/:id/messages/send` · `/send-image` · `/send-document` · `/reply` · `POST /typing` · `POST /read` · `GET /messages/:messageId/media` |\n| History | `GET /api/sessions/messages/history` · `GET /api/sessions/:id/conversations/:phone` · `GET /api/sessions/messages/stats` |\n| Webhooks | `GET/POST /api/webhooks` · `PUT/DELETE /api/webhooks/:id` · `POST /api/webhooks/:id/test` · `GET /api/webhooks/logs` |\n| Official API | `POST /v1/messages` · `GET/POST /v1/media` · `GET/POST/PATCH/DELETE /v1/groups` |\n| Meta Webhook | `GET/POST /webhooks/whatsapp` |\n\n**Full integration guide for AI agents:** [`docs/AI_AGENT_PROMPT.md`](docs/AI_AGENT_PROMPT.md)\n\n---\n\n## Common Flows\n\n### Internal mode — create session, scan QR, send message\n\n```bash\n# Create a session\ncurl -X POST http://localhost:7834/api/sessions \\\n  -H \"X-API-Key: \u003ckey\u003e\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"id\":\"main\",\"name\":\"Main WhatsApp\"}'\n\n# Start the session and get QR\ncurl -X POST http://localhost:7834/api/sessions/main/start -H \"X-API-Key: \u003ckey\u003e\"\ncurl http://localhost:7834/api/sessions/main/qr -H \"X-API-Key: \u003ckey\u003e\"\n# → returns { \"success\": true, \"data\": { \"qr\": \"data:image/png;base64,...\" } }\n\n# Poll until ready\ncurl http://localhost:7834/api/sessions/main/status -H \"X-API-Key: \u003ckey\u003e\"\n\n# Send text\ncurl -X POST http://localhost:7834/api/sessions/main/messages/send \\\n  -H \"X-API-Key: \u003ckey\u003e\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"to\":\"6281234567890\",\"text\":\"Hello from Wahuy\"}'\n```\n\n### Official mode — send Cloud API-compatible message\n\n```bash\ncurl -X POST http://localhost:7834/v1/messages \\\n  -H \"X-API-Key: \u003ckey\u003e\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"messaging_product\":\"whatsapp\",\n    \"to\":\"6281234567890\",\n    \"type\":\"text\",\n    \"text\":{\"body\":\"Hello from Wahuy Official mode\"}\n  }'\n```\n\n### Register an outbound webhook\n\n**Important:** The `sessions` field is **required** — use `[\"*\"]` for all sessions or specify session IDs. Empty sessions are rejected.\n\n```bash\ncurl -X POST http://localhost:7834/api/webhooks \\\n  -H \"X-API-Key: \u003ckey\u003e\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"url\":\"https://example.com/webhook\",\n    \"events\":[\"message.received\",\"message.sent\",\"session.ready\"],\n    \"sessions\":[\"main\"],\n    \"secret\":\"optional-hmac-secret\"\n  }'\n\n# Or to listen to ALL sessions:\n# \"sessions\":[\"*\"]\n```\n\n---\n\n## Three Ways to Receive Events\n\nWahuy provides three independent event channels — pick the ones that fit your architecture:\n\n| Channel | Protocol | Best for |\n|---------|----------|----------|\n| **WebSocket** | Socket.IO persistent connection | Bots, dashboards, realtime apps |\n| **REST History** | HTTP GET polling | Cron jobs, reports, backups |\n| **Outbound Webhooks** | HTTP POST to your URL | Serverless functions, Zapier, external logging |\n\nYou do **not** need to use webhooks to receive events. WebSocket and REST cover most use cases. Webhooks are ideal when your receiver can't maintain a persistent connection.\n\n### Indicators and received media\n\n- Typing indicator: `POST /api/sessions/:id/typing` with `{ \"to\": \"6281234567890\", \"duration\": 3000 }`.\n- Read indicator: `POST /api/sessions/:id/read` with at least `{ \"messageId\": \"...\" }`. Wahuy uses the stored Baileys message key when available; callers can also pass `chatId`/`remoteJid` and `participant` explicitly.\n- Internal received media: incoming events/history include media metadata when present. Download the base64 payload with `GET /api/sessions/:id/messages/:messageId/media`.\n- Official received media: use the existing Cloud API-compatible `GET /v1/media/:mediaId` endpoint.\n- Contact profile photos: Internal-mode message contacts include `contacts.*.profilePicUrl` when Baileys `profilePictureUrl` can fetch one for that participant/contact.\n\n### WebSocket Events\n\n```js\nimport { io } from 'socket.io-client';\n\nconst socket = io('http://localhost:7834', {\n  auth: { apiKey: '\u003ckey\u003e' }\n});\n\nsocket.emit('subscribe', { sessions: ['*'] });\nsocket.on('session:qr', console.log);      // QR data URL for pairing\nsocket.on('session:status', console.log);  // includes scan_qr, ready, disconnected, failed\nsocket.on('message:received', console.log);\nsocket.on('message:sent', console.log);\n```\n\nWhen a pairing QR is generated, Wahuy emits both `session:status` with `status: \"scan_qr\"` and `session:qr`. The dashboard uses these realtime events to show/update the QR modal without manual refresh. REST `GET /api/sessions/:id/qr` remains available as a fallback.\n\n### Message Contact Metadata\n\nInternal-mode messages may use Baileys v7 LID identifiers such as `12345@lid`. Wahuy keeps those identifiers in `from`, `to`, and `contacts.*.id` so callers can track the exact WhatsApp identity. Treat them as opaque IDs, not phone-number strings.\n\nPhone numbers are exposed only in `contacts.*.number` when Baileys explicitly provides trusted metadata via `remoteJidAlt`, `participantAlt`, `senderPn`, contact sync, history sync, `lid-mapping.update`, or the Baileys mapping store. If no mapping exists yet, `contacts.*.number` is `null` instead of a fake number derived from the visible identifier. When WhatsApp allows access, Wahuy also fills `contacts.*.profilePicUrl` from Baileys `profilePictureUrl`.\n\n### Webhook Payload Format\n\n```json\n{\n  \"event\": \"message.received\",\n  \"timestamp\": \"2026-01-15T10:30:00.000Z\",\n  \"session\": { \"id\": \"main\", \"phone\": null },\n  \"payload\": {\n    \"id\": \"BAE5...\",\n    \"from\": \"12345@lid\",\n    \"body\": \"Hello!\",\n    \"type\": \"chat\",\n    \"fromMe\": false,\n    \"hasMedia\": false,\n    \"contacts\": {\n      \"sender\": {\n        \"id\": \"12345@lid\",\n        \"number\": \"6281234567890\",\n        \"pushname\": \"Jane\",\n        \"profilePicUrl\": \"https://mmg.whatsapp.net/...\"\n      },\n      \"receiver\": {\n        \"id\": \"98765@lid\",\n        \"number\": \"6289876543210\"\n      }\n    }\n  }\n}\n```\n\n### Webhook Security Model\n\n- **`sessions` is mandatory** — webhook without sessions is rejected (400).\n- **`[\"*\"]`** — receive events from all sessions.\n- **`[\"main\", \"secondary\"]`** — only receive events from specific sessions.\n- **Legacy webhooks** with empty sessions are auto-migrated to `[\"*\"]` on startup (backward compatible).\n- **HMAC signature** optional via `secret` — set it and verify `X-Webhook-Signature` header.\n\n---\n\n## Configuration\n\nAll env vars with defaults are in [`.env.example`](.env.example) and [`src/config.ts`](src/config.ts).\n\n| Variable | Default | Notes |\n|----------|---------|-------|\n| `PORT` | `3000` | Docker commonly uses `7834`; set explicitly. |\n| `API_KEY` | `development-api-key` | **Change in production.** |\n| `API_KEYS` | — | Optional comma-separated extra API keys. |\n| `PROVIDER` | `internal` | `internal` or `official`. |\n| `DASHBOARD_ENABLED` | `true` | Serve built dashboard at `/`. |\n| `STORAGE_PATH` | `./data` | Sessions, message DB, webhook logs. |\n| `SESSION_RECONNECT_INTERVAL` | `5000` | Reconnect backoff base (ms). |\n| `SESSION_MAX_RECONNECT_ATTEMPTS` | `10` | Max auto-reconnect attempts. |\n| `OFFICIAL_ACCESS_TOKEN` | — | Required for Official mode. |\n| `OFFICIAL_APP_SECRET` | — | Required for Meta webhook signatures. |\n| `OFFICIAL_PHONE_NUMBER_ID` | — | Required for Official mode. |\n| `OFFICIAL_WEBHOOK_VERIFY_TOKEN` | — | Required for Meta webhook verification. |\n| `REDIS_URL` | — | Optional queue backend for Official mode. |\n\n---\n\n## Project Structure\n\n```text\nwahuy/\n├── src/\n│   ├── api/                    # REST API routes and auth middleware\n│   ├── providers/              # Internal (Baileys) + Official implementations\n│   ├── core/                   # Session manager, WhatsApp client, webhook dispatcher\n│   ├── storage/                # File/SQLite storage\n│   ├── websocket/              # Socket.IO event bridge\n│   └── utils/                  # Logging and helpers\n├── dashboard/                  # React dashboard (Vite)\n├── docs/                       # AI agent guide\n├── docker/                     # Docker image files (no Chromium)\n├── data/                       # Runtime data volume\n└── tests/                      # Unit/integration tests\n```\n\n---\n\n## Development\n\n```bash\nnpm run dev              # hot reload server\nnpm run build            # TypeScript build\nnpm test                 # all tests\nnpm run lint             # ESLint\nnpm run dashboard:dev    # dashboard dev server\nnpm run dashboard:build  # dashboard production build\n```\n\n---\n\n## Production Checklist\n\n- [ ] Set a strong `API_KEY` or `API_KEYS`\n- [ ] Change `DASHBOARD_USERNAME` and `DASHBOARD_PASSWORD`\n- [ ] Set `PORT`, `NODE_ENV=production`, and `STORAGE_PATH`\n- [ ] Mount persistent storage for `data/`\n- [ ] Put Wahuy behind HTTPS if exposed publicly\n- [ ] Use Official mode for production messaging when WABA is available\n- [ ] Always set `sessions` on outbound webhooks — use `[\"*\"]` or specific IDs\n- [ ] Configure webhook secrets and validate signatures on your receiver\n- [ ] Back up `data/` regularly\n\n---\n\n## Docs Index\n\n| Doc | Contents |\n|-----|----------|\n| [`docs/AI_AGENT_PROMPT.md`](docs/AI_AGENT_PROMPT.md) | AI agent guide: install, configure, API exploration, integration flows. |\n| [`.env.example`](.env.example) | Environment variable starter file. |\n| [`src/config.ts`](src/config.ts) | Canonical config defaults. |\n| [`src/api/index.ts`](src/api/index.ts) | Route registration and prefixes. |\n\n---\n\n## Security Note\n\nWahuy is not affiliated with WhatsApp Inc. or Meta Platforms Inc. Internal mode uses unofficial WhatsApp WebSocket automation via Baileys and may violate WhatsApp policies. Official mode uses Meta's WhatsApp Cloud API.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Futsmannn%2Fwahuy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Futsmannn%2Fwahuy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Futsmannn%2Fwahuy/lists"}