openwa について
OpenWA は、WhatsApp 自動化を構築するチーム向けのオープンソースでセルフホスト可能な API ゲートウェイです。REST API、ダッシュボード、複数セッション、Webhook、リアルタイムイベントに加え、JavaScript/TypeScript と Python の SDK を備えています。
主な機能
- 01REST API と React ダッシュボード
- 02複数の WhatsApp アカウントセッション管理
- 03メッセージ、連絡先、グループ、チャンネルの API
- 04署名と再試行に対応する Webhook
- 05Socket.IO によるリアルタイムイベント
- 06JavaScript/TypeScript と Python の SDK
活用シーン
- 01複数の WhatsApp アカウントでメッセージを自動化
- 02Webhook で受信メッセージイベントを処理
- 03セッションとメッセージイベントをリアルタイム監視
- 04SDK を使って WhatsApp 操作を統合
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ thatjuan/ agent-skills --skill openwaインストール後、Claude Code で/openwaを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.mdreferences/architecture.mdconfiguration.mddeployment.mdgotchas.mdoperation.mdrest-api.mdsdk.mdwebhooks-and-events.md詳細
OpenWA
OpenWA はセルフホスト可能なオープンソースの WhatsApp API ゲートウェイです(MIT、NestJS 11/TypeScript/Node 22)。whatsapp-web.js(Puppeteer + Chromium)をポート 2785 の REST API の背後でラップし、ポート 2886 で React ダッシュボードを提供します。また、Webhook、Socket.IO のリアルタイムイベント、プラグ可能なストレージ/キャッシュ/データベース、プラグインフックシステムを備えた、複数セッションの WhatsApp 自動化に対応しています。
製品の構成
| コンポーネント | ポート | 役割 |
|---|---|---|
openwa-api(NestJS) | 2785 | REST + WebSocket + Webhook ディスパッチャー |
dashboard(React 19 + Vite) | 2886 | セッション/API キー/Webhook の UI |
| Swagger UI | 2785 | /api/docs |
| Traefik(オプション) | 80、8080 | リバースプロキシ + ダッシュボード(プロファイル with-proxy) |
| PostgreSQL(オプション) | 5432 | プロファイル postgres |
| Redis(オプション) | 6379 | プロファイル redis。BullMQ キューを有効化します |
| MinIO(オプション) | 9000、9001 | プロファイル minio(S3 互換ストレージ) |
グローバル API プレフィックスは /api です(src/main.ts:128 app.setGlobalPrefix('api'))。以下のすべてのパスは <baseUrl>/api を基準とします。
技術スタック — 確認済み
| レイヤー | 技術 | 根拠 |
|---|---|---|
| フレームワーク | NestJS 11.x | package.json |
| WA エンジン | whatsapp-web.js 1.26.1(Puppeteer + Chromium) | package.json、src/engine/engine.factory.ts |
| メイン DB | SQLite(常時使用 — API キー、監査ログを保存) | src/app.module.ts |
| データ DB | SQLite または PostgreSQL(セッション、メッセージ、Webhook) | src/app.module.ts |
| ORM | TypeORM 0.3.x | package.json |
| キュー | BullMQ 5.x(QUEUE_ENABLED で有効化) | src/app.module.ts:31 |
| キャッシュ | Redis(オプション、REDIS_ENABLED で有効化) | src/common/cache/ |
| ストレージ | ローカルまたは S3/MinIO | src/common/storage/ |
| リアルタイム | 名前空間 /events の Socket.IO 4.8 | src/modules/events/events.gateway.ts |
| バリデーション | class-validator、class-transformer | package.json |
| SDK | @openwa/sdk(JS/TS)+ openwa(Python) | sdk/javascript/、sdk/python/ |
リポジトリの構成
src/
main.ts # Boot + .env precedence + first-run API key
app.module.ts # Composition; conditional QueueModule
config/configuration.ts # Env → config object
core/{hooks,plugins}/ # Plugin hook system
engine/ # IWhatsAppEngine interface + adapters
modules/
session/ # Session CRUD + in-memory engine map
message/ # Send & history endpoints
webhook/ # Per-session webhook subscriptions
events/ # Socket.IO gateway (/events namespace)
auth/ # API-key auth + roles
contact/ group/ label/ channel/ status/ catalog/
queue/ # BullMQ jobs (only if QUEUE_ENABLED)
health/ # /health, /health/live, /health/ready
infra/ settings/ stats/ audit/ docker/ plugins/
dashboard/ # React 19 + Vite SPA
sdk/{javascript,python}/ # Client libraries
traefik/{traefik.yml,dynamic.yml}
docker-compose.yml # Profiled production stack
docker-compose.dev.yml # SQLite + local dev quick-start
Dockerfile # Multi-stage Node 22-slim + Chromium
docs/ # 22 markdown design docs
認証 — X-API-Key
認証には、X-API-Key ヘッダーで送信するハッシュ化された API キーを使用します(main.ts:122 の CORS 許可リストに従い、Authorization: Bearer も使用できます)。キーはメインの SQLite DB にハッシュ化して保存され、ロール(ADMIN、OPERATOR、VIEWER)が割り当てられます(src/modules/auth/entities/api-key.entity.ts)。
curl -H "X-API-Key: owa_k1_xxxxxxxx..." http://localhost:2785/api/sessions
- キー形式:
owa_k1_<32 bytes hex>(src/modules/auth/auth.service.ts:95) - 初回起動時のブートストラップ:API キーが存在しない場合、OpenWA はキーを1つ作成して
data/.api-keyに書き込み、起動バナーに表示します。開発環境(NODE_ENV=development)では、ブートストラップキーは文字列dev-admin-keyです。 - ロールによる制限:書き込みエンドポイントには
@RequireRole(ApiKeyRole.OPERATOR)が設定されています。読み取りは大部分が API キーによって公開されています。API キーの CRUD には ADMIN が必要です。 - キーごとの任意のスコープ設定:IP 許可リスト(
allowedIps)とセッション許可リスト(allowedSessions)。
キーの作成/失効のフローについては、認証と API キーのライフサイクルを参照してください。
セッション — 複数アカウントの WhatsApp
各 WhatsApp アカウントは、永続化される Session 行1件と、セッション ID をキーとするメモリ内エンジン(IWhatsAppEngine)1つに対応します(src/modules/session/session.service.ts)。セッションは次の状態を経て進行します。
CREATED → INITIALIZING → QR_READY → AUTHENTICATING → READY
↘ FAILED
any → DISCONNECTED (also forced on every API restart)
セッションを稼働状態にするための最小限のワークフロー:
# 1. Create
curl -X POST http://localhost:2785/api/sessions \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"name":"bot-1"}'
# 2. Start (initialises Chromium + WA engine)
curl -X POST http://localhost:2785/api/sessions/$ID/start -H "X-API-Key: $KEY"
# 3. Fetch QR — returns SVG data while status = QR_READY
curl http://localhost:2785/api/sessions/$ID/qr -H "X-API-Key: $KEY"
# … scan with WhatsApp mobile …
# 4. Poll status (or subscribe via /events) until status = READY
curl http://localhost:2785/api/sessions/$ID -H "X-API-Key: $KEY"
チャット ID は WhatsApp Web の規則に従います。ダイレクトチャットは <phone>@c.us、グループは <id>@g.us、チャンネルは <id>@newsletter です。形式が無効な場合、送信は失敗しますが、エラーは通知されません。
ライフサイクル全体、メモリ内エンジンマップ、プロキシオプション、再起動時のリセット動作については、operation.mdを参照してください。
REST API — コントローラーカタログ
すべてのルートは /api の配下にマウントされます。括弧内のロールは、デフォルトの VIEWER 以上より厳しい場合に必要です。
| ドメイン | ルート | ファイル |
|---|---|---|
| セッション | POST /sessions (OPERATOR)、GET /sessions、GET /sessions/:id、DELETE /sessions/:id (OPERATOR)、POST /sessions/:id/start (OPERATOR)、POST /sessions/:id/stop (OPERATOR)、GET /sessions/:id/qr、GET /sessions/:id/groups、GET /sessions/stats/overview | session.controller.ts |
| メッセージ | GET /sessions/:sessionId/messages、POST /sessions/:sessionId/messages/send-text、…/send-image、…/send-video、…/send-audio、…/send-document、…/send-location、…/send-contact、…/send-sticker、…/reply、…/forward、…/react、GET …/:chatId/:messageId/reactions、POST …/delete、POST …/send-bulk、GET …/batch/:batchId、POST …/batch/:batchId/cancel | message.controller.ts |
| Webhook | /sessions/:sessionId/webhooks 配下の POST/GET/GET :id/PUT :id/DELETE :id、および POST :id/test | webhook.controller.ts |
| API キー | POST /auth/api-keys (ADMIN)、GET、GET :id、PUT :id、DELETE :id、POST :id/revoke | auth.controller.ts |
| 認証プローブ | GET /auth/validate (ヘッダー X-API-Key) | auth-validate.controller.ts |
| ヘルスチェック | GET /health、GET /health/live、GET /health/ready (公開、K8s 形式) | health.controller.ts |
| 連絡先/グループ/ラベル/チャンネル/ステータス/カタログ | /sessions/:sessionId/... 配下の各モジュールのコントローラー | modules/<name>/ |
| インフラ | 設定、統計、移行用のエクスポート/インポート | modules/infra/ |
正式な仕様は、コントローラーファイルと自動公開される OpenAPI(http://localhost:2785/api/docs)に記載されています。ロール/認証に関する注記付きの全ルート一覧は、rest-api.md を参照してください。
Webhook — セッションごと、署名付き、再試行あり
Webhook は URL、events 配列、および任意の secret/カスタム headers を指定して、セッションごとに作成します(POST /api/sessions/:sessionId/webhooks)。QUEUE_ENABLED=true の場合は BullMQ 経由で配信され(再試行あり)、それ以外の場合は同期的に配信されます。
配信ごとに付与されるヘッダー:
| ヘッダー | 意味 |
|---|---|
X-OpenWA-Event | イベント名(例:message.received) |
X-OpenWA-Delivery-Id | 配信試行をまとめる親ディスパッチごとの一意の ID |
X-OpenWA-Idempotency-Key | (イベント × 対象) ごとに固定されるキー — コンシューマーが重複を排除するためのキー |
X-OpenWA-Retry-Count | 初回は 0、以降は attemptsMade |
X-OpenWA-Signature | webhook.secret を使って JSON ボディから計算した sha256=<hex hmac>(シークレットがない場合は省略) |
デフォルト値:WEBHOOK_TIMEOUT=10000 ms、WEBHOOK_MAX_RETRIES=3、WEBHOOK_RETRY_DELAY=5000 ms。
購読可能なイベントタイプ(WebSocket でも有効 — src/modules/events/dto/ws-messages.dto.ts):
message.received、message.sent、message.ack、message.revoked、session.status、session.qr、session.authenticated、session.disconnected、group.join、group.leave、group.update — すべてを対象にする場合は *。
署名の検証、配信フロー、キューと同期処理の違い、冪等性の仕様、再試行のタイミングについては、webhooks-and-events.md を参照してください。
リアルタイム — Socket.IO /events
Webhook と同じイベント分類が、/events 名前空間の Socket.IO 経由で配信されます。プロトコルはカスタム JSON(Socket.IO のイベントエミッターではありません)です。クライアントは {type:'subscribe', sessionId, events, requestId?} を送信し、サーバー側から {type:'event', ...}/{type:'subscribed', ...}/{type:'error', ...}/{type:'pong'} を受信します。
import { io } from 'socket.io-client';
const sock = io('http://localhost:2785/events', { auth: { apiKey: KEY } });
sock.emit('message', { type: 'subscribe', sessionId: '*', events: ['message.received'] });
sock.on('message', (msg) => { /* handle msg.type === 'event' */ });
ワイヤーフォーマット、認証モード、ルーム構成(session:<id>:<event>)、ping/pong については、webhooks-and-events.md#websocket を参照してください。
デプロイ
ドキュメントに記載されている3つの方法:
| 方法 | コマンド |
|---|---|
| ローカル開発(SQLite、Redis/キューなし、すべてを単一環境で実行) | docker compose -f docker-compose.dev.yml up -d |
| 本番環境の基本構成 | docker compose up -d |
| 本番環境のフルスタック(Postgres + Redis + MinIO + Traefik + Dashboard) | docker compose --profile full up -d |
イメージは、Chromium を組み込んだマルチステージの node:22-slim で、PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium を設定しています。コンテナのヘルスチェックは、30秒ごとに node -e "require('http').get('http://localhost:2785/api/health', ...)" を実行します。
Compose プロファイル(postgres、redis、minio、with-dashboard、with-proxy、full)、ボリュームの配置(openwa-data:/app/data)、Docker ソケットのマウント(/var/run/docker.sock:ro — オーケストレーションに modules/docker が使用)、Traefik の静的/動的設定、K8s のヘルスプローブについては、deployment.md を参照してください。
設定 — 環境変数による3層の優先順位
ブートストラップ(src/main.ts:11-65)では、優先順位の最上位はプロセス環境変数となるよう、override: false を指定して dotenv を読み込みます。
process.env > ./.env > ./data/.env.generated
.env.generated はダッシュボードが書き込むファイルであり、優先順位は最下位です。よくある落とし穴は、ダッシュボードでの保存が Docker の environment: ブロックを上書きすると期待してしまうことですが、上書きされません。
初回起動時に data/.env.generated が存在しない場合、OpenWA は SQLite、ローカルストレージ、キュー無効の設定でファイルを書き込み、API キーを含むウェルカムバナーを表示します。
app.module.ts では、2つの TypeORM データベースを設定しています。
main接続 — 常に SQLite です。API キーと監査ログを保存します。ノード内にとどめてください(移行しようとしないでください)。data接続 — SQLite または PostgreSQL です。セッション、メッセージ、Webhook を保存します。同期のデフォルトは SQLite ではtrue、Postgres ではfalseです(その場合はnpm run migration:run:prodを実行します)。
環境変数の完全なリファレンス、.env.minimal、2つのデータベースの図、QUEUE_ENABLED による制御については、configuration.md を参照してください。
SDK
import { OpenWAClient } from '@openwa/sdk';
const client = new OpenWAClient({ baseUrl: 'http://localhost:2785', apiKey: KEY });
await client.sessions.create({ name: 'bot-1' });
await client.sessions.start(id);
await client.messages.sendText(id, { chatId: '[email protected]', text: 'hi' });
公開されている @openwa/sdk は、手書きの最小限の API(sdk/javascript/src/index.ts)を提供しており、OpenAPI 仕様から再生成することを前提としています。まだラッパーがないエンドポイントでは、client.request('POST', '/api/...', body) を呼び出すか、X-API-Key を使って fetch を使用してください。Python SDK は sdk/python/openwa/ にあります。
SDK のインストール、型付きラッパー、生成フロー、Python 版については、sdk.md を参照してください。
アーキテクチャと拡張性
OpenWA のプラグイン対応は、機能フラグだけでなく、インターフェース境界によって担保されています。
IWhatsAppEngine(src/engine/interfaces/) —whatsapp-web.jsを抽象化します。代替エンジン(Baileys など)は、engine.factory.tsを介してその背後に組み込めます。ENGINE_TYPEで選択します。StorageService— ローカルファイルシステムまたは S3/MinIO を使用し、STORAGE_TYPEで切り替えます。CacheService— インメモリまたは Redis を使用し、REDIS_ENABLEDで制御します。- プラグインフック —
core/hooks/が名前付きのフックチェーンを実行します。メッセージ送信パイプラインでは、ディスパッチ前にmessage:sendingが実行され(プラグインは変更またはブロックが可能)、成功時にmessage:sentが実行されます。PLUGINS_ENABLED=trueの場合、プラグインは自動的に読み込まれます。 - キューの制御 —
QUEUE_ENABLED === 'true'の場合にのみ、QueueModuleをrequireします(src/app.module.ts:31-38)。無効の場合、Webhook の再試行はベストエフォートの同期処理のみです。
モジュールマップ、フック一覧、プラグインローダー、スケーリングモデル(エンジンはノードに固定、API キーはローカルに保持、データベースは共有可能)については、architecture.md を参照してください。
注意点
ドキュメントを読んでも、コードを確認しないと見落としがちな挙動をいくつか紹介します。
.env.generatedの優先順位は最も低く、最も高いわけではありません。- API を再起動すると、すべての
READY/INITIALIZING/QR_READY/AUTHENTICATINGセッションが強制的にDISCONNECTEDにリセットされます(エンジンはメモリ上にあるため、再起動すると失われます)。 QUEUE_ENABLED=false(デフォルト)の場合、Webhook に再試行はありません。同期的なベストエフォート方式でディスパッチされます。- SQLite のデフォルトは
synchronize: trueであり、本番環境では危険です。Postgres ではマイグレーションに切り替えてください。 - API キーは SQLite の
mainDB に保存されます。dataDB を Postgres に切り替えても、API キーは移行されません。 phoneとpushNameは、セッションがREADYに達するまでnullです。- チャット ID の形式は
<phone>@c.us/<id>@g.usである必要があります。形式が間違っていると、何も通知されずに失敗します。 - Chromium は必須です。Docker イメージでは
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromiumがハードコードされています。
ファイル名と行番号を含む注釈付きの全リスト:gotchas.md。
リファレンス一覧
| リファレンス | 内容 |
|---|---|
| deployment.md | Dockerfile のステージ、Compose プロファイル、Traefik、K8s プローブ、ボリューム |
| configuration.md | 環境変数のリファレンス、優先順位、2 つの DB の構成、キューの制御 |
| operation.md | セッションのライフサイクル、QR 認証、API キーの初期設定とライフサイクル、ダッシュボード |
| rest-api.md | コントローラーからルートまでの全カタログ、ロールデコレーター、リクエスト/レスポンスの形式 |
| webhooks-and-events.md | Webhook ヘッダー、HMAC、再試行、冪等性、Socket.IO /events の通信形式 |
| sdk.md | JS/TS および Python クライアントの使い方、fetch による代替方法、再生成に関する注意事項 |
| architecture.md | モジュール構成、エンジン/ストレージ/キャッシュの差し替え、プラグインフック、スケーリング |
| gotchas.md | ソースコードで確認した、直感的には分かりにくい挙動 |