WhatsApp (Web channel)
상태: WhatsApp Web (Baileys) 기반으로 production-ready입니다. Gateway가 연결된 session을 소유합니다.페어링
알 수 없는 발신자에 대한 기본 DM 정책은 pairing입니다.
채널 문제 해결
채널 전반의 진단 및 복구 플레이북입니다.
Gateway configuration
전체 채널 설정 패턴과 예시입니다.
빠른 설정
가능하면 OpenClaw용 WhatsApp은 별도 번호로 운영하는 것이 좋습니다. 채널 metadata와 onboarding 흐름은 그 구성을 기준으로 최적화되어 있지만, 개인 번호 구성도 지원합니다.
배포 패턴
전용 번호 (권장)
전용 번호 (권장)
가장 깔끔한 운영 방식입니다.
- OpenClaw 전용 WhatsApp identity 사용
- 더 명확한 DM allowlist와 routing 경계
- self-chat 혼동 가능성 감소
개인 번호 fallback
개인 번호 fallback
Onboarding은 개인 번호 모드를 지원하며 self-chat에 안전한 기본 구성을 기록합니다.
dmPolicy: "allowlist"allowFrom에 개인 번호 포함selfChatMode: true
allowFrom을 기준으로 self-chat 보호가 동작합니다.WhatsApp Web 전용 채널 범위
WhatsApp Web 전용 채널 범위
현재 OpenClaw 채널 아키텍처에서 이 메시징 플랫폼 채널은 WhatsApp Web 기반(
Baileys)입니다.내장 chat-channel registry에는 별도의 Twilio WhatsApp messaging channel이 없습니다.런타임 모델
- Gateway가 WhatsApp socket과 reconnect loop를 소유합니다.
- outbound send는 대상 account에 active WhatsApp listener가 있어야 합니다.
- status chat과 broadcast chat은 무시됩니다. (
@status,@broadcast) - direct chat은 DM session 규칙을 사용합니다. (
session.dmScope; 기본main은 DM을 agent main session으로 합침) - group session은 분리됩니다. (
agent:<agentId>:whatsapp:group:<jid>)
접근 제어와 활성화
- DM policy
- Group policy + allowlists
- Mentions + /activation
channels.whatsapp.dmPolicy는 direct chat 접근을 제어합니다.pairing(기본값)allowlistopen(allowFrom에"*"가 있어야 함)disabled
allowFrom은 E.164 형식 번호를 받으며, 내부적으로 정규화됩니다.멀티 계정 override: channels.whatsapp.accounts.<id>.dmPolicy 및 allowFrom이 해당 account의 채널 기본값보다 우선합니다.런타임 동작:- pairing은 채널 allow-store에 저장되며 설정된
allowFrom과 병합됩니다. - allowlist가 비어 있으면 연결된 self number가 기본적으로 허용됩니다.
- outbound
fromMeDM은 자동 pairing되지 않습니다.
개인 번호와 self-chat 동작
연결된 self number가allowFrom에도 포함되어 있으면 WhatsApp self-chat 보호가 활성화됩니다.
- self-chat turn에서는 read receipt를 생략
- 자기 자신을 다시 ping하게 되는 mention-JID auto-trigger 동작을 무시
messages.responsePrefix가 unset이면 self-chat reply의 기본 prefix는[{identity.name}]또는[openclaw]
메시지 정규화와 컨텍스트
Inbound envelope + reply context
Inbound envelope + reply context
들어오는 WhatsApp 메시지는 공용 inbound envelope으로 래핑됩니다.quoted reply가 있으면 다음 형식으로 컨텍스트가 추가됩니다.가능할 때는 reply metadata 필드도 채워집니다. (
ReplyToId, ReplyToBody, ReplyToSender, sender JID/E.164)Media placeholders, location, contact 추출
Media placeholders, location, contact 추출
media-only inbound message는 다음과 같은 placeholder로 정규화됩니다.
<media:image><media:video><media:audio><media:document><media:sticker>
Pending group history injection
Pending group history injection
그룹에서는, 처리되지 않은 메시지를 버퍼링했다가 bot이 실제로 트리거될 때 컨텍스트로 주입할 수 있습니다.
- 기본 limit:
50 - config:
channels.whatsapp.historyLimit - fallback:
messages.groupChat.historyLimit 0이면 비활성화
[Chat messages since your last reply - for context][Current message - respond to this]
Read receipts
Read receipts
허용된 inbound WhatsApp message에는 기본적으로 read receipt를 보냅니다.전역 비활성화:account별 override:self-chat turn에서는 전역 설정이 켜져 있어도 read receipt를 보내지 않습니다.
전달, 청킹, 미디어
Text chunking
Text chunking
- 기본 chunk limit:
channels.whatsapp.textChunkLimit = 4000 channels.whatsapp.chunkMode = "length" | "newline"newline모드는 빈 줄 기준 문단 경계를 우선 사용하고, 불가능하면 길이 기준 청킹으로 fallback합니다.
Outbound media 동작
Outbound media 동작
- image, video, audio (PTT voice-note), document payload 지원
audio/ogg는 voice-note 호환성을 위해audio/ogg; codecs=opus로 재작성- animated GIF 재생은 video 전송 시
gifPlayback: true로 지원 - multi-media reply payload에서는 caption이 첫 번째 media item에 적용됨
- media source는 HTTP(S),
file://, 로컬 경로를 지원
Media size limit과 fallback 동작
Media size limit과 fallback 동작
- inbound media 저장 제한:
channels.whatsapp.mediaMaxMb(기본50) - outbound media 전송 제한:
channels.whatsapp.mediaMaxMb(기본50) - account별 override:
channels.whatsapp.accounts.<accountId>.mediaMaxMb - 이미지는 제한에 맞도록 자동 최적화됩니다. (resize/quality sweep)
- media 전송 실패 시, 첫 항목 fallback은 응답을 조용히 버리지 않고 경고 텍스트를 전송합니다.
Acknowledgment reactions
WhatsApp은channels.whatsapp.ackReaction으로 inbound 수신 직후 즉시 ack reaction을 보낼 수 있습니다.
- inbound가 수락되면 reply 전에 즉시 전송
- 실패해도 로그만 남기고 일반 reply delivery는 막지 않음
- group mode
mentions는 mention-triggered turn에서만 reaction을 보냄. group activationalways는 이 검사를 우회하는 역할을 함 - WhatsApp은
channels.whatsapp.ackReaction을 사용합니다. (messages.ackReactionlegacy 키는 여기서 사용되지 않음)
멀티 계정과 자격 증명
Account 선택과 기본값
Account 선택과 기본값
- account id는
channels.whatsapp.accounts에서 옴 - 기본 account 선택:
default가 있으면 그것을 사용, 없으면 정렬된 첫 account id를 사용 - account id는 내부 조회를 위해 정규화됩니다.
Credential 경로와 legacy 호환성
Credential 경로와 legacy 호환성
- 현재 auth 경로:
~/.openclaw/credentials/whatsapp/<accountId>/creds.json - backup 파일:
creds.json.bak ~/.openclaw/credentials/에 있던 legacy default auth도 default-account 흐름에서 인식되고 migration됩니다.
Logout 동작
Logout 동작
openclaw channels logout --channel whatsapp [--account <id>]는 해당 account의 WhatsApp auth state를 지웁니다.legacy auth directory에서는 oauth.json은 보존하고 Baileys auth 파일만 제거합니다.도구, actions, config writes
- Agent tool은 WhatsApp reaction action(
react)을 지원합니다. - Action gate:
channels.whatsapp.actions.reactionschannels.whatsapp.actions.polls
- 채널에서 시작된 config write는 기본적으로 허용됩니다. (
channels.whatsapp.configWrites=false로 비활성화)
문제 해결
연결되지 않음 (QR 필요)
연결되지 않음 (QR 필요)
증상: channel status가 not linked를 보고함해결:
연결은 되었지만 disconnect/reconnect loop 발생
연결은 되었지만 disconnect/reconnect loop 발생
증상: linked account인데 반복적인 disconnect 또는 reconnect 시도 발생해결:필요하면
channels login으로 다시 연결하세요.전송 시 active listener가 없음
전송 시 active listener가 없음
대상 account에 active gateway listener가 없으면 outbound send는 즉시 실패합니다.gateway가 실행 중이고 해당 account가 linked 상태인지 확인하세요.
그룹 메시지가 예상과 다르게 무시됨
그룹 메시지가 예상과 다르게 무시됨
다음 순서로 확인하세요.
groupPolicygroupAllowFrom/allowFromgroupsallowlist 항목- mention gating (
requireMention+ mention patterns) openclaw.json의 duplicate key (JSON5): 뒤에 오는 값이 앞의 값을 덮어쓰므로, scope별groupPolicy는 하나만 유지하세요.
Bun runtime 경고
Bun runtime 경고
WhatsApp gateway runtime은 Node를 사용해야 합니다. Bun은 안정적인 WhatsApp/Telegram gateway 운영과 호환되지 않는 것으로 간주됩니다.
설정 레퍼런스 포인터
기본 레퍼런스: 주요 WhatsApp 필드:- access:
dmPolicy,allowFrom,groupPolicy,groupAllowFrom,groups - delivery:
textChunkLimit,chunkMode,mediaMaxMb,sendReadReceipts,ackReaction - multi-account:
accounts.<id>.enabled,accounts.<id>.authDir, account-level overrides - operations:
configWrites,debounceMs,web.enabled,web.heartbeatSeconds,web.reconnect.* - session behavior:
session.dmScope,historyLimit,dmHistoryLimit,dms.<id>.historyLimit