WhatsApp (Web 채널)

상태: WhatsApp Web(Baileys) 기반 프로덕션 레벨. 게이트웨이가 연결 세션을 소유합니다.

빠른 설정

1단계: WhatsApp 접근 정책 설정

{
  channels: {
    whatsapp: {
      dmPolicy: "pairing",
      allowFrom: ["+15551234567"],
      groupPolicy: "allowlist",
      groupAllowFrom: ["+15551234567"],
    },
  },
}

2단계: WhatsApp 연결 (QR)

openclaw channels login --channel whatsapp
특정 계정의 경우:
openclaw channels login --channel whatsapp --account work

3단계: 게이트웨이 시작

openclaw gateway

4단계: 첫 페어링 요청 승인 (페어링 모드 사용 시)

openclaw pairing list whatsapp
openclaw pairing approve whatsapp <CODE>
페어링 요청은 1시간 후 만료됩니다. 채널당 대기 중인 요청은 최대 3개로 제한됩니다.

참고: WhatsApp은 가능하면 별도의 번호로 운영하는 것을 권장합니다. (채널 메타데이터와 온보딩 흐름이 해당 설정에 최적화되어 있지만, 개인 번호 설정도 지원됩니다.)

배포 패턴

전용 번호 (권장)
가장 깔끔한 운영 모드입니다:

- OpenClaw 전용 WhatsApp 계정 사용
- DM 허용 목록과 라우팅 경계가 명확함
- 셀프 채팅 혼동 가능성이 낮음

최소 정책 패턴:

```json5
{
  channels: {
    whatsapp: {
      dmPolicy: "allowlist",
      allowFrom: ["+15551234567"],
    },
  },
}
```
개인 번호 폴백
온보딩은 개인 번호 모드를 지원하며 셀프 채팅에 적합한 기본 설정을 기록합니다:

- `dmPolicy: "allowlist"`
- `allowFrom`에 개인 번호 포함
- `selfChatMode: true`

런타임에서 셀프 채팅 보호는 연결된 자기 번호와 `allowFrom`을 기반으로 동작합니다.
WhatsApp Web 전용 채널 범위
현재 OpenClaw 채널 아키텍처에서 메시징 플랫폼 채널은 WhatsApp Web 기반(`Baileys`)입니다.

내장 채팅 채널 레지스트리에는 별도의 Twilio WhatsApp 메시징 채널이 없습니다.

런타임 모델

  • 게이트웨이가 WhatsApp 소켓과 재연결 루프를 소유합니다.
  • 아웃바운드 전송에는 대상 계정의 활성 WhatsApp 리스너가 필요합니다.
  • 상태 및 브로드캐스트 채팅은 무시됩니다 (@status, @broadcast).
  • 다이렉트 채팅은 DM 세션 규칙을 사용합니다 (session.dmScope, 기본값 main은 DM을 에이전트 메인 세션으로 통합).
  • 그룹 세션은 격리됩니다 (agent:<agentId>:whatsapp:group:<jid>).

접근 제어 및 활성화

DM 정책

`channels.whatsapp.dmPolicy`로 다이렉트 채팅 접근을 제어합니다:

- `pairing` (기본값)
- `allowlist`
- `open` (`allowFrom`에 `"*"` 필요)
- `disabled`

`allowFrom`은 E.164 형식 번호를 허용합니다 (내부적으로 정규화됨).

멀티 계정 재정의: `channels.whatsapp.accounts.<id>.dmPolicy` (및 `allowFrom`)가 해당 계정의 채널 수준 기본값보다 우선합니다.

런타임 동작 상세:

- 페어링은 채널 허용 저장소에 유지되며 설정된 `allowFrom`과 병합됩니다
- 허용 목록이 설정되지 않은 경우 연결된 자기 번호가 기본으로 허용됩니다
- 아웃바운드 `fromMe` DM은 자동 페어링되지 않습니다

그룹 정책 + 허용 목록

그룹 접근에는 두 가지 계층이 있습니다:

1. **그룹 멤버십 허용 목록** (`channels.whatsapp.groups`)
   - `groups`를 생략하면 모든 그룹이 대상
   - `groups`가 있으면 그룹 허용 목록으로 동작 (`"*"` 허용)

2. **그룹 발신자 정책** (`channels.whatsapp.groupPolicy` + `groupAllowFrom`)
   - `open`: 발신자 허용 목록 우회
   - `allowlist`: 발신자가 `groupAllowFrom`과 일치해야 함 (또는 `*`)
   - `disabled`: 모든 그룹 인바운드 차단

발신자 허용 목록 폴백:

- `groupAllowFrom`이 미설정이면 런타임에서 `allowFrom`을 폴백으로 사용
- 발신자 허용 목록은 멘션/답장 활성화 전에 평가됨

참고: `channels.whatsapp` 블록이 전혀 없는 경우, `channels.defaults.groupPolicy`가 설정되어 있어도 런타임 그룹 정책 폴백은 `allowlist`입니다 (경고 로그 포함).

멘션 + 활성화

그룹 응답은 기본적으로 멘션이 필요합니다.

멘션 감지 대상:

- 봇 ID에 대한 명시적 WhatsApp 멘션
- 설정된 멘션 정규식 패턴 (`agents.list[].groupChat.mentionPatterns`, 폴백 `messages.groupChat.mentionPatterns`)
- 암묵적 봇 답장 감지 (답장 발신자가 봇 ID와 일치)

보안 참고:

- 인용/답장은 멘션 게이팅만 충족하며 발신자 인가를 부여하지 **않습니다**
- `groupPolicy: "allowlist"`에서는 허용 목록에 없는 발신자가 허용된 사용자의 메시지에 답장해도 차단됩니다

세션 수준 활성화 명령:

- `/activation mention`
- `/activation always`

`activation`은 세션 상태를 업데이트합니다 (전역 설정이 아님). 소유자 게이팅이 적용됩니다.

개인 번호 및 셀프 채팅 동작

연결된 자기 번호가 allowFrom에도 포함되어 있으면 WhatsApp 셀프 채팅 보호가 활성화됩니다:

  • 셀프 채팅 턴에서 읽음 확인 건너뛰기
  • 자기 자신에게 핑하는 멘션-JID 자동 트리거 동작 무시
  • messages.responsePrefix가 미설정이면 셀프 채팅 응답은 기본적으로 [{identity.name}] 또는 [openclaw]로 설정

메시지 정규화 및 컨텍스트

인바운드 봉투 + 답장 컨텍스트
수신 WhatsApp 메시지는 공유 인바운드 봉투로 래핑됩니다.

인용 답장이 있는 경우 컨텍스트가 다음 형식으로 추가됩니다:

```text
[Replying to <sender> id:<stanzaId>]
<quoted body or media placeholder>
[/Replying]
```

답장 메타데이터 필드도 가능한 경우 채워집니다 (`ReplyToId`, `ReplyToBody`, `ReplyToSender`, sender JID/E.164).
미디어 플레이스홀더 및 위치/연락처 추출
미디어 전용 인바운드 메시지는 다음과 같은 플레이스홀더로 정규화됩니다:

- `<media:image>`
- `<media:video>`
- `<media:audio>`
- `<media:document>`
- `<media:sticker>`

위치 및 연락처 페이로드는 라우팅 전에 텍스트 컨텍스트로 정규화됩니다.
대기 중인 그룹 히스토리 주입
그룹의 경우 미처리 메시지가 버퍼링되어 봇이 트리거될 때 컨텍스트로 주입될 수 있습니다.

- 기본 제한: `50`
- 설정: `channels.whatsapp.historyLimit`
- 폴백: `messages.groupChat.historyLimit`
- `0`으로 비활성화

주입 마커:

- `[Chat messages since your last reply - for context]`
- `[Current message - respond to this]`
읽음 확인
수락된 인바운드 WhatsApp 메시지에 대해 읽음 확인이 기본으로 활성화됩니다.

전역 비활성화:

```json5
{
  channels: {
    whatsapp: {
      sendReadReceipts: false,
    },
  },
}
```

계정별 재정의:

```json5
{
  channels: {
    whatsapp: {
      accounts: {
        work: {
          sendReadReceipts: false,
        },
      },
    },
  },
}
```

셀프 채팅 턴은 전역 활성화 상태에서도 읽음 확인을 건너뜁니다.

전달, 청킹, 미디어

텍스트 청킹
- 기본 청크 제한: `channels.whatsapp.textChunkLimit = 4000`
- `channels.whatsapp.chunkMode = "length" | "newline"`
- `newline` 모드는 단락 경계(빈 줄)를 우선하며, 길이 기반 청킹으로 폴백
아웃바운드 미디어 동작
- 이미지, 동영상, 오디오 (PTT 음성 메모), 문서 페이로드 지원
- `audio/ogg`는 음성 메모 호환을 위해 `audio/ogg; codecs=opus`로 재작성됨
- 애니메이션 GIF 재생은 동영상 전송 시 `gifPlayback: true`로 지원
- 캡션은 멀티 미디어 응답 페이로드 전송 시 첫 번째 미디어 항목에 적용
- 미디어 소스는 HTTP(S), `file://`, 또는 로컬 경로 가능
미디어 크기 제한 및 폴백 동작
- 인바운드 미디어 저장 상한: `channels.whatsapp.mediaMaxMb` (기본 `50`)
- 아웃바운드 미디어 전송 상한: `channels.whatsapp.mediaMaxMb` (기본 `50`)
- 계정별 재정의: `channels.whatsapp.accounts.<accountId>.mediaMaxMb`
- 이미지는 제한에 맞도록 자동 최적화 (리사이즈/품질 조정)
- 미디어 전송 실패 시 응답을 무시하지 않고 첫 번째 항목 폴백으로 텍스트 경고 전송

수신 확인 리액션

WhatsApp은 channels.whatsapp.ackReaction을 통해 인바운드 수신 시 즉각적인 확인 리액션을 지원합니다.

{
  channels: {
    whatsapp: {
      ackReaction: {
        emoji: "👀",
        direct: true,
        group: "mentions", // always | mentions | never
      },
    },
  },
}

동작 참고:

  • 인바운드 수락 직후 전송 (응답 전)
  • 실패 시 로깅되지만 정상 응답 전달을 차단하지 않음
  • 그룹 모드 mentions는 멘션으로 트리거된 턴에 리액션하며, 그룹 활성화 always는 이 검사의 우회 역할
  • WhatsApp은 channels.whatsapp.ackReaction을 사용 (레거시 messages.ackReaction은 여기서 사용되지 않음)

멀티 계정 및 자격 증명

계정 선택 및 기본값
- 계정 ID는 `channels.whatsapp.accounts`에서 가져옴
- 기본 계정 선택: `default`가 있으면 사용, 아니면 첫 번째 설정된 계정 ID (정렬 순)
- 계정 ID는 조회를 위해 내부적으로 정규화됨
자격 증명 경로 및 레거시 호환성
- 현재 인증 경로: `~/.openclaw/credentials/whatsapp/<accountId>/creds.json`
- 백업 파일: `creds.json.bak`
- `~/.openclaw/credentials/`의 레거시 기본 인증은 기본 계정 흐름에서 여전히 인식/마이그레이션됨
로그아웃 동작
`openclaw channels logout --channel whatsapp [--account <id>]`는 해당 계정의 WhatsApp 인증 상태를 초기화합니다.

레거시 인증 디렉토리에서는 `oauth.json`이 보존되고 Baileys 인증 파일은 제거됩니다.

도구, 액션, 설정 쓰기

  • 에이전트 도구 지원에는 WhatsApp 리액션 액션 (react)이 포함됩니다.
  • 액션 게이트:
    • channels.whatsapp.actions.reactions
    • channels.whatsapp.actions.polls
  • 채널에서 시작된 설정 쓰기는 기본으로 활성화됩니다 (channels.whatsapp.configWrites=false로 비활성화).

문제 해결

미연결 (QR 필요)
증상: 채널 상태가 미연결로 표시.

해결:

```bash
openclaw channels login --channel whatsapp
openclaw channels status
```
연결됨 하지만 연결 끊김 / 재연결 반복
증상: 연결된 계정이 반복적으로 연결 끊김 또는 재연결을 시도.

해결:

```bash
openclaw doctor
openclaw logs --follow
```

필요 시 `channels login`으로 재연결합니다.
전송 시 활성 리스너 없음
대상 계정에 활성 게이트웨이 리스너가 없으면 아웃바운드 전송이 즉시 실패합니다.

게이트웨이가 실행 중이고 계정이 연결되어 있는지 확인합니다.
그룹 메시지가 예상치 못하게 무시됨
다음 순서로 확인합니다:

- `groupPolicy`
- `groupAllowFrom` / `allowFrom`
- `groups` 허용 목록 항목
- 멘션 게이팅 (`requireMention` + 멘션 패턴)
- `openclaw.json`(JSON5)의 중복 키: 나중의 항목이 이전 항목을 덮어쓰므로 범위당 단일 `groupPolicy`를 유지
Bun 런타임 경고
WhatsApp 게이트웨이 런타임은 Node를 사용해야 합니다. Bun은 안정적인 WhatsApp/Telegram 게이트웨이 운영에 비호환으로 표시됩니다.

설정 참조 포인터

기본 참조:

주요 WhatsApp 필드:

  • 접근: dmPolicy, allowFrom, groupPolicy, groupAllowFrom, groups
  • 전달: textChunkLimit, chunkMode, mediaMaxMb, sendReadReceipts, ackReaction
  • 멀티 계정: accounts.<id>.enabled, accounts.<id>.authDir, 계정 수준 재정의
  • 운영: configWrites, debounceMs, web.enabled, web.heartbeatSeconds, web.reconnect.*
  • 세션 동작: session.dmScope, historyLimit, dmHistoryLimit, dms.<id>.historyLimit

관련 문서