OpenWA는 본인 서버에 직접 띄우는 무료 오픈소스 WhatsApp API 게이트웨이다. 본인 시스템과 WhatsApp 사이에 다리를 놓아, 메시지 발송·수신·세션 관리·그룹·웹훅을 REST API 한 묶음으로 다룰 수 있게 해준다. "고객이 WhatsApp으로 '주문'을 보내면 자동 응답하고 구글 시트에 기록" 같은 자동화를, 외부 SaaS에 월 사용료를 내지 않고 본인 서버에서 만든다.
WhatsApp은 일반 개발자에게 공식 API를 잘 열어주지 않는다. 그래서 본인 시스템과 WhatsApp 사이에 중간 다리 서버(게이트웨이)가 필요한데, 상용 제품들은 이걸 비싸게 판다. OpenWA는 그 다리를 오픈소스(MIT)로 본인 서버에 직접 띄울 수 있게 만든 도구다.
하지만 초보자가 진짜 가져갈 보물은 따로 있다. 엔터프라이즈급 NestJS 아키텍처 — 특히 어댑터 패턴 — 가 잘 정리된 docs와 함께 들어 있어, 백엔드 설계를 통째로 배우는 살아있는 교재가 된다.
WhatsApp 자동화를 만들 때 보통 세 갈래 길이 있고, 앞의 두 갈래는 문제점이 명확하다.
세션당 또는 메시지당 과금이라 비용이 빠르게 누적된다. 본인 메시지 데이터가 외부 회사 서버에 저장돼 고객정보 유출 위험이 있고, 코드를 본인이 수정할 수도 없다.
메시지당 과금에 번호 등록이 까다롭고, 데이터는 모두 Meta 서버에 보관된다. 마케팅 메시지 등 일부 용도는 사실상 막혀 있다.
본인 서버나 NAS에 Docker로 띄우면 끝. 월 사용료 0원, 모든 데이터가 본인 서버에만, 코드는 마음껏 수정. 게다가 여러 개의 WhatsApp 번호를 한 인스턴스에서 동시에 관리한다.
| 비교 대상 | OpenWA의 위치 |
|---|---|
| WAHA Plus | 유료 셀프호스팅 게이트웨이. OpenWA는 동급 기능(멀티세션·웹훅·미디어)을 무료 MIT로 제공 |
| Twilio / MessageBird | 완전 관리형 SaaS라 운영은 편하지만 과금·데이터 외부 보관. OpenWA는 데이터 주권 + 무과금 |
| Meta Cloud API | 공식이라 안정적이지만 제약·과금 많음. OpenWA는 비공식 우회라 유연하지만 BAN 위험 존재 |
| raw whatsapp-web.js | 라이브러리만 쓰면 세션·웹훅·저장소·인증을 직접 다 짜야 함. OpenWA는 그 위에 게이트웨이 인프라를 얹어줌 |
※ 수치는 2026-06 기준 GitHub: ⭐7.7k · 포크 1.6k · 최신 릴리스 v0.2.6(2026-06). 초기 버전대라 프로덕션 채택 시 안정성은 직접 검증 권장.
| 기술 | 역할 | 상세 |
|---|---|---|
| NestJS 11 | 백엔드 프레임워크 | 모듈·DI(의존성 주입)·데코레이터 기반. 어댑터 교체의 토대 |
| Node.js 22 LTS | 런타임 | 서버 실행 환경 |
| TypeScript 5.x | 언어 | 인터페이스로 어댑터 계약(IStorageAdapter 등) 정의 |
| whatsapp-web.js | WhatsApp 엔진 | Puppeteer로 web.whatsapp.com을 자동 조작 (비공식 우회) |
| Swagger | API 문서 | 2785/api/docs에서 모든 엔드포인트를 브라우저로 직접 호출 |
| 계층 | 기본(개발) | 프로덕션 대안 |
|---|---|---|
| DB | SQLite | PostgreSQL |
| 저장소 | 로컬 파일시스템 | S3 / MinIO |
| 캐시 / 큐 | 메모리 | Redis (+ 큐 시스템) |
| 기술 | 역할 | 상세 |
|---|---|---|
| React 대시보드 | 웹 UI | 세션 생성·QR 스캔·상태 모니터링 (포트 2886) |
| Docker Compose | 배포 | profiles(postgres/redis/minio/with-dashboard/with-proxy/full)로 환경 구성 |
| Traefik | 리버스 프록시 | with-proxy 프로파일로 선택 활성화 |
| API Key 인증 | 보안 | X-API-Key 헤더 + CIDR 화이트리스트 + 레이트 리미팅 + 감사 로깅 |
출장 가서 본 적 있을 것이다. 한국·미국·유럽 콘센트 모양은 다 달라도 멀티 어댑터만 있으면 어디서든 노트북을 충전한다. 노트북은 콘센트가 무슨 모양인지 모른다 — 그냥 익숙한 모양에 꽂힐 뿐.
OpenWA의 메시지 서비스도 "저장소가 무엇인지" 모른다. IStorageAdapter라는 모양에 대고 "이 파일 저장해줘"라고만 부탁한다. 뒤에는 로컬 파일이든 S3이든 MinIO든 꽂혀 있다.
교체 가능한 부품은 3계층 — DB(SQLite↔PostgreSQL), 저장소(로컬↔S3/MinIO), 캐시(메모리↔Redis). 코드는 한 줄도 안 건드리고 설정/프로파일만 바꿔 갈아끼운다.
README는 "구성은 첫 실행 시 자동 생성"이라고만 명시하고 STORAGE_TYPE 같은 변수명을 직접 열거하지 않는다. 실제 키 이름은 생성된 설정 파일 또는 docs/05-database-design·config/에서 확인해야 한다. (본문의 변수명 예시는 설명용 가정.)
SessionManager가 Map<세션ID, Session> 형태로 모든 세션을 들고, 각 세션은 자기만의 Puppeteer 브라우저를 가진다. 한 사람이 여러 노트북에 각각 다른 WhatsApp을 로그인해둔 셈.
각 세션이 브라우저를 하나씩 띄우므로 세션 1개당 대략 300~500MB(환경에 따라 변동). 10개를 동시에 돌리면 수 GB가 필요하고, 작은 VPS/NAS에서는 OOM으로 죽을 수 있다. 미리 세션 수 × 약 500MB를 계산하라.
메시지 도착 등 이벤트를 본인 서버로 자동 HTTP POST한다. 위조 방지를 위해 HMAC-SHA256 서명을 본문에 박아, 비밀 키를 모르는 외부인이 가짜 이벤트를 쏘면 수신측이 거부한다. 발송 실패 시 큐가 자동 재시도해 메시지를 잃지 않는다.
engine/ 디렉토리가 whatsapp-web.js를 한 겹 감싸, 메시지 모듈이 특정 라이브러리에 직접 묶이지 않게 한다. 동작 원리는 Puppeteer로 실제 WhatsApp Web 페이지를 사람처럼 자동 조작하는 것 — Meta 입장에선 사람이 웹을 쓰는 것처럼 보인다.
Meta는 비공식 클라이언트로 의심되는 번호를 차단할 수 있다. 본인 메인 번호로 돌리다 막히면 개인 메시지까지 끊긴다. 자동화용 별도 번호를 쓰는 게 안전하다.
학습 동선 추천: main.ts → app.module.ts로 부팅 흐름을 잡고 → common/storage/에서 어댑터 인터페이스 1개를 정독 → modules/message/가 그 어댑터를 어떻게 "모양만 보고" 쓰는지 확인하면, 어댑터 패턴의 전체 그림이 한 번에 잡힌다.
| # | 문서 | 읽으면 얻는 것 |
|---|---|---|
| 01 | Project Overview | 전체 목적·범위 |
| 02 | Requirements Specification | 요구사항 정의 방식 |
| 03 | System Architecture | 구조도·모듈 관계 (핵심) |
| 04 | Security Design | 인증·HMAC·화이트리스트 |
| 05 | Database Design | 스키마·어댑터 매핑 |
| 06 | API Specification | 엔드포인트 계약 |
| 07 | API Collection | 엔드포인트 예제 모음 |
| 08 | Development Guidelines | 코딩 컨벤션 |
| 14 | Migration Guide | 버전 이전 |
배울 것: 인터페이스로 계약을 정의하고 DI Factory가 설정값 보고 구현체를 골라 주입하는 패턴. 본인 프로젝트의 로그를 "콘솔↔파일↔Loki", 메일을 "SMTP↔SendGrid↔SES"로 추상화해보면 환경 분기(if-else) 지옥이 통째로 사라진다.
배울 것: 기능을 session/message/webhook처럼 모듈로 쪼개고 의존성 주입으로 결합도를 낮추는 방법. modules/ 8개가 그대로 모범 사례다.
배울 것: 외부로 웹훅을 보낼 때 crypto.createHmac 한 줄로 서명을 박는 습관. 수신자가 위조 이벤트를 거부하게 해주는 1차 보안 방어선.
배울 것: 외부 호출(웹훅·발송)이 실패할 수 있다는 전제로 큐에 넣고 자동 재시도하는 설계. 일시적 장애에도 메시지를 잃지 않는다.
배울 것: 한 docker-compose 파일에 profiles: [postgres, redis, minio, with-dashboard, with-proxy, full]로 태그를 달아 같은 파일로 개발·스테이징·프로덕션을 다른 구성으로 띄우는 법.
배울 것: /health("프로세스 살아있나?")와 /ready("트래픽 받을 준비 됐나?")를 분리해 Kubernetes liveness/readiness probe에 그대로 매핑하는 운영 패턴.
| 항목 | 최소 | 권장 |
|---|---|---|
| Node.js | 22 LTS (README 명시) | 22 LTS |
| 실행 방식 | Docker / Docker Compose | Docker Compose + 프로파일 |
| RAM | 세션 1개 기준 약 1GB 이상* | 세션 수 × 약 500MB + 여유 |
| DB | SQLite (내장, 개발용) | PostgreSQL (프로덕션) |
| 네트워크 | 아웃바운드 인터넷 (WhatsApp Web 접속) | 웹훅 수신용 고정 IP/도메인 |
| WhatsApp 번호 | QR 스캔 가능한 폰 1대 | 자동화 전용 별도 번호 |
* RAM 수치는 README에 명시되지 않음 — 멀티세션이 Puppeteer 브라우저를 세션마다 띄우는 구조에서 도출한 추정치다. 실제 부하는 동시 세션 수·미디어 트래픽에 좌우된다.
| 포트 | 용도 |
|---|---|
| 2785 | REST API |
| 2785/api/docs | Swagger 문서 |
| 2886 | React 대시보드 |
레포를 클론해 띄우고, 대시보드에서 세션을 만들어 QR을 폰으로 스캔한 뒤 본인 다른 폰으로 메시지를 자동 전송해본다.
# 1. 클론 후 개발용 컴포즈로 기동
git clone https://github.com/rmyndharis/OpenWA
docker compose -f docker-compose.dev.yml up -d
# 2. 대시보드(:2886) 접속 → 세션 생성 → QR 스캔
# 3. 폰 WhatsApp → 연결된 기기 → 그 QR 스캔
localhost:2785/api/docs에서 코드 없이 클릭만으로 엔드포인트를 호출한다. 텍스트 메시지 발송 형식을 직접 확인.
# 텍스트 발송 (X-API-Key 헤더 필요)
POST /api/sessions/{sessionId}/messages/send-text
{ "chatId": "628123456789@c.us", "text": "Hello from OpenWA!" }
웹훅 URL을 등록하고, 들어오는 메시지를 받아 자동 회신하는 작은 서버를 만든다. HMAC 서명 검증까지 구현해 위조 요청을 거부해본다.
minio 프로파일로 S3 호환 스토리지를 띄우고, 미디어 저장 위치를 로컬 파일에서 객체 스토리지로 옮긴다. 코드 변경 없이 설정만으로 바뀌는 어댑터 패턴의 위력을 체감.
# MinIO 포함 프로파일로 기동
docker compose --profile minio up -d
# config/ 생성 파일에서 저장소 타입을 s3 계열로 전환 후 재시작
src/engine/interfaces/whatsapp-engine.interface.ts의 IWhatsAppEngine 시그니처를 확인하고, src/engine/adapters/ 구현체와 비교해 어댑터 패턴이 엔진 레이어에서 어떻게 동작하는지 파악한다. 한 번 해보면 어댑터 패턴이 본인 도구함의 영구 무기가 된다.
| 요일 | 학습 내용 | 실습 |
|---|---|---|
| 월 | Docker Compose 기동 | dev 컴포즈로 띄우고 세션 QR 스캔 |
| 화 | Swagger로 API 탐험 | send-text·세션·연락처 직접 호출 |
| 수 | 부팅 흐름 읽기 | main.ts → app.module.ts 따라가기 |
| 목 | docs 01~03 정독 | System Architecture 구조도 손으로 다시 그리기 |
| 금 | 모듈 경계 파악 | modules/ 8개 책임 한 줄씩 정리 |
| 요일 | 학습 내용 | 실습 |
|---|---|---|
| 월 | 인터페이스 = 계약 개념 | common/storage의 IStorageAdapter 정독 |
| 화 | 구현체 비교 | 로컬 vs S3 어댑터 코드 차이 분석 |
| 수 | DI Factory 주입 | 설정값 → 어느 구현체가 주입되는지 추적 |
| 목 | 저장소 교체 실습 | minio 프로파일로 전환(과제 4) |
| 금 | 나만의 어댑터 | IWhatsAppEngine 시그니처 분석 + 어댑터 스케치(과제 5) |
| 요일 | 학습 내용 | 실습 |
|---|---|---|
| 월 | 웹훅 등록·수신 | 수신 서버 만들어 이벤트 받기 |
| 화 | HMAC 서명 검증 | crypto.createHmac로 위조 요청 거부 |
| 수 | 큐 재시도 | 수신 서버를 잠깐 끄고 재시도 관찰 |
| 목 | 인증·레이트리밋 | API Key·CIDR 화이트리스트 설정 |
| 금 | Health/Ready | /health·/ready 응답 차이 확인 |
| 요일 | 학습 내용 | 실습 |
|---|---|---|
| 월 | SessionManager 구조 | Map<id,Session> + Puppeteer 추적 |
| 화 | 메모리 산정 | 세션 수 × 약 500MB로 용량 계획 |
| 수 | DB 어댑터 전환 | SQLite → PostgreSQL 프로파일 |
| 목 | full 프로파일 배포 | postgres+redis+minio 한 번에 기동 |
| 금 | 종합 자동화 | 웹훅→처리→회신 파이프라인 완성 |
| 키워드 | 설명 |
|---|---|
| 셀프호스팅 | 남의 서비스를 빌리지 않고 본인 서버/NAS에 직접 설치해 돌리는 방식. 월 사용료 0원, 데이터 외부 유출 없음, 운영 책임은 본인 |
| API 게이트웨이 | 외부 시스템(WhatsApp)과 내 앱 사이의 중간 서버. 인증·로깅·재시도 등 공통 처리를 모은다 |
| 어댑터 패턴 | 다른 인터페이스의 부품을 같은 "모양"으로 변환해 끼울 수 있게 하는 디자인 패턴. OpenWA의 DB/저장소/캐시 교체의 핵심 |
| 의존성 주입 (DI) | 객체가 필요한 부품을 직접 만들지 않고 외부(NestJS 컨테이너)에서 받아쓰는 방식. 어댑터 교체를 가능케 함 |
| NestJS | 모듈·DI·데코레이터 기반 Node.js 백엔드 프레임워크. OpenWA의 골격 |
| whatsapp-web.js | Puppeteer로 web.whatsapp.com을 자동 조작하는 라이브러리. 공식 API 없이 메시지를 주고받는 우회 엔진 |
| Puppeteer | Chrome 브라우저를 코드로 원격 조작하는 라이브러리. 사람이 클릭·타이핑하는 흉내를 낸다 |
| 세션 (Session) | 하나의 WhatsApp 번호 연결 단위. 각 세션은 자기만의 브라우저 인스턴스를 가짐 |
| 멀티세션 | 한 인스턴스에서 여러 WhatsApp 번호를 동시에 관리하는 능력. SessionManager가 담당 |
| 웹훅 (Webhook) | "이벤트 생기면 이 주소로 알려줘"라고 등록해두는 방식. 메시지 도착 시 OpenWA가 본인 서버로 HTTP POST |
| HMAC 서명 | 공유 비밀키로 메시지에 디지털 도장을 찍어 위조를 막는 방법(HMAC-SHA256). 수신자가 가짜 요청을 거부 |
| QR 페어링 | 대시보드의 QR을 폰 WhatsApp "연결된 기기"로 스캔해 번호를 연결하는 과정 |
| S3 / MinIO | 객체 스토리지. S3는 AWS 표준, MinIO는 그걸 본인 서버에 띄우는 오픈소스. 미디어 저장 어댑터의 대안 |
| Docker Compose Profile | 한 컴포즈 파일에 태그(postgres/redis/minio/full 등)를 달아 환경별로 다른 서비스 조합을 띄우는 기능 |
| Health / Ready | 프로세스 생존(/health)과 트래픽 수용 준비(/ready)를 분리한 운영 엔드포인트. K8s probe에 매핑 |
| 레이트 리미팅 | 과도한 요청을 제한하는 보안 기능. CIDR 화이트리스트·감사 로깅과 함께 제공 |
| BAN (차단) | Meta가 비공식 클라이언트로 의심되는 번호를 차단하는 것. 자동화 전용 번호 사용으로 위험 분리 |