@openclaw/signal). The gateway talks to signal-cli over HTTP: either the native daemon (JSON-RPC + SSE) or the bbernhard/signal-cli-rest-api container (REST + WebSocket). OpenClaw does not embed libsignal.
The number model (read this first)
- The gateway connects to a Signal device: the
signal-cliaccount. - Running the bot on your personal Signal account makes it ignore your own messages (loop protection).
- For βI text the bot and it replies,β use a separate bot number.
Install
openclaw plugins install clawhub:@openclaw/signal or npm:@openclaw/signal. plugins install registers and enables the plugin; no separate enable step is needed. See Plugins for general install rules.
Quick setup
1
Pick a number
Use a separate Signal number for the bot (recommended).
2
Install the plugin
3
Run the guided setup
signal-cli is on PATH and, when missing, offers to install it: downloads the official native GraalVM build on Linux x86-64, or installs via Homebrew on macOS and other architectures. It then prompts for the bot number and signal-cli path.For non-interactive setup, openclaw channels add --channel signal also accepts --signal-number <e164> for the bot phone number, plus --http-host <host> and --http-port <port> for the Signal daemon endpoint (default 127.0.0.1:8080).4
5
Verify and pair
openclaw pairing approve signal <CODE>.
Multi-account support: use
channels.signal.accounts with per-account config and optional name. Each named account owns its transport; it does not inherit the top-level transport. The top-level transport belongs only to the implicit default account. See Multi-account channels for the shared pattern.
What it is
- Deterministic routing: replies always go back to Signal.
- DMs share the agentβs main session; groups are isolated (
agent:<agentId>:signal:group:<groupId>). - By default, Signal may write config updates triggered by
/config set|unset(requirescommands.config: true). Disable withchannels.signal.configWrites: false.
Setup path A: link existing Signal account (QR)
- Install
signal-cli(JVM or native build), or letopenclaw channels addinstall it for you. - Link a bot account:
signal-cli link -n "OpenClaw", then scan the QR in Signal. - Configure Signal and start the gateway.
Setup path B: register dedicated bot number (SMS, Linux)
Use this for a dedicated bot number instead of linking an existing Signal app account. The flow below is tested on Ubuntu 24.- Get a number that can receive SMS (or voice verification for landlines). A dedicated bot number avoids account/session conflicts.
- Install
signal-clion the gateway host:
signal-cli-${VERSION}.tar.gz), install a JRE first. Keep signal-cli updated; upstream notes old releases can break as Signal server APIs change.
- Register and verify the number:
- Open
https://signalcaptchas.org/registration/generate.html. - Complete the captcha, copy the
signalcaptcha://...link target from βOpen Signalβ. - Run from the same external IP as the browser session when possible (captcha tokens expire quickly).
- Register and verify immediately:
- Configure OpenClaw, restart the gateway, verify the channel:
- Pair your DM sender:
- Send any message to the bot number.
- Approve on the server:
openclaw pairing approve signal <PAIRING_CODE>. - Save the bot number as a contact on your phone to avoid βUnknown contactβ.
signal-cliREADME:https://github.com/AsamK/signal-cli- Captcha flow:
https://github.com/AsamK/signal-cli/wiki/Registration-with-captcha - Linking flow:
https://github.com/AsamK/signal-cli/wiki/Linking-other-devices-(Provisioning)
External native daemon mode
To managesignal-cli yourself (slow JVM cold starts, container init, shared CPUs), run the daemon separately and point OpenClaw at it:
For non-interactive setup, select the endpoint kind explicitly when needed:
channels.signal.transport.startupTimeoutMs.
Container mode (bbernhard/signal-cli-rest-api)
Instead of runningsignal-cli natively, use the bbernhard/signal-cli-rest-api Docker container, which wraps signal-cli behind a REST + WebSocket interface.
- The container must run with
MODE=json-rpcfor real-time message receiving. - Register or link your Signal account inside the container before connecting OpenClaw.
docker-compose.yml service:
transport.kind controls which protocol and process lifecycle OpenClaw uses:
Setup and
openclaw doctor --fix may probe an existing endpoint once to identify its concrete kind. Runtime operations do not auto-detect or switch protocols.
Container mode supports the same Signal operations as native mode where the container exposes matching APIs: sends, receives, attachments, typing indicators, read/viewed receipts, reactions, groups, and styled text. OpenClaw translates native Signal RPC calls into the containerβs REST payloads, including group.{base64(internal_id)} group IDs and text_mode: "styled" for formatted text.
Operational notes:
- Use
MODE=json-rpcfor receiving.MODE=normalcan make/v1/aboutlook healthy, but/v1/receive/{account}will not WebSocket-upgrade, so container receive streaming will fail its probe. - Set
kind: "container"for the bbernhard REST API andkind: "external-native"for nativesignal-cliJSON-RPC/SSE. - Container attachment downloads honor the same media byte limits as native mode. Oversized responses are rejected before being fully buffered when the server sends
Content-Length, and while streaming otherwise.
Access control (DMs + groups)
DMs:- Default:
channels.signal.dmPolicy = "pairing". - Unknown senders get a pairing code; messages are ignored until approved (codes expire after 1 hour).
- Approve via
openclaw pairing list signalandopenclaw pairing approve signal <CODE>. - Pairing is the default token exchange for Signal DMs. Details: Pairing
- UUID-only senders (from
sourceUuid) are stored asuuid:<id>inchannels.signal.allowFrom.
channels.signal.groupPolicy = open | allowlist | disabled.channels.signal.groupAllowFromcontrols which groups or senders can trigger group replies whenallowlistis set; entries can be Signal group IDs (raw,group:<id>, orsignal:group:<id>), sender phone numbers,uuid:<id>values, or*.channels.signal.groups["<group-id>" | "*"]can override group behavior withrequireMention,tools, andtoolsBySender.- Use
channels.signal.accounts.<id>.groupsfor per-account overrides in multi-account setups. - Allowlisting a Signal group through
groupAllowFromdoes not disable mention gating by itself. A specifically configuredchannels.signal.groups["<group-id>"]entry processes every group message unlessrequireMention=trueis set. - With
requireMention=true, Signal native @mentions are matched from structured mention metadata against the bot account phone oraccountUuid. ConfiguredmentionPatternsremain a plain-text fallback. - Runtime note: if
channels.signalis completely missing, runtime falls back togroupPolicy="allowlist"for group checks (even ifchannels.defaults.groupPolicyis set).
How it works (behavior)
- Native mode:
signal-cliruns as a daemon; the gateway reads events via SSE. - Container mode: the gateway sends via REST API and receives via WebSocket.
- Inbound messages are normalized into the shared channel envelope.
- Replies always route back to the same number or group.
- Replies to inbound messages include native Signal quote metadata when the backend accepts the inbound timestamp and author; if quote metadata is missing or rejected, OpenClaw sends the reply as a normal message.
- Configure native quote use with
channels.signal.replyToMode = off | first | all | batched, orchannels.signal.replyToModeByChatType.direct/groupfor per-chat-type overrides. Account-level values underchannels.signal.accounts.<id>take precedence.
Media + limits
- Outbound text is chunked to
channels.signal.textChunkLimit(default 4000). - Optional newline chunking: set
channels.signal.streaming.chunkMode="newline"to split on blank lines (paragraph boundaries) before length chunking. - Attachments are supported (base64 fetched from
signal-cli). - Voice-note attachments use the
signal-clifilename as a MIME fallback whencontentTypeis missing, so audio transcription can still classify AAC voice memos. - Default media cap:
channels.signal.mediaMaxMb(default 8). - Use
channels.signal.ignoreAttachmentsto skip downloading media for any transport. - Group history context uses
channels.signal.historyLimit(orchannels.signal.accounts.*.historyLimit), falling back tomessages.groupChat.historyLimit. Set0to disable (default 50).
Typing + read receipts
- Typing indicators: OpenClaw sends typing signals via
signal-cli sendTypingand refreshes them while a reply is running. - Read receipts: when
channels.signal.sendReadReceiptsis true, OpenClaw forwards read receipts for allowed DMs. signal-clidoes not expose read receipts for groups.
Lifecycle status reactions
Setmessages.statusReactions.enabled: true to let Signal show the shared queued/thinking/tool/compaction/done/error reaction lifecycle on inbound turns. Signal uses the inbound message timestamp as the reaction target; group reactions are sent with the Signal group ID plus the original sender as the target author.
Status reactions also require an ack reaction and a matching messages.ackReactionScope (direct, group-all, group-mentions, or all). Set channels.signal.reactionLevel: "off" to disable Signal status reactions.
Signal restores the initial ack reaction after the final done/error state.
Reactions (message tool)
Usemessage action=react with channel=signal.
- Targets: sender E.164 or UUID (use
uuid:<id>from pairing output; a bare UUID also works). messageIdis the Signal timestamp for the message youβre reacting to.- Group reactions require
targetAuthorortargetAuthorUuid.
channels.signal.actions.reactions: enable/disable reaction actions (default true).channels.signal.reactionLevel:off | ack | minimal | extensive(defaultminimal).off/ackdisables agent reactions (message toolreacterrors).minimal/extensiveenables agent reactions and sets the guidance level.
- Per-account overrides:
channels.signal.accounts.<id>.actions.reactions,channels.signal.accounts.<id>.reactionLevel.
Approval reactions
Signal exec and plugin approval prompts use the top-levelapprovals.exec and approvals.plugin routing blocks. Signal has no channels.signal.execApprovals block.
πapproves once.πdenies.- Use
/approve <id> allow-alwayswhen a request offers persistent approval.
channels.signal.allowFrom, channels.signal.defaultTo, or the matching account-level fields. Direct same-chat exec approval prompts can still suppress the duplicate local /approve fallback without explicit approvers; no-approver group approvals keep the local fallback visible.
Question reactions
For anask_user prompt with one non-secret, single-select question and one to four options, Signal shows 1οΈβ£ through 4οΈβ£ beside the option labels. React to the delivered prompt with the matching number to answer it. OpenClaw verifies the reaction targets the bot-authored message, then maps the number to the canonical option through the Gateway. Stale or duplicate taps are ignored. Multi-question, multi-select, and free-text prompts remain text-reply-only; normal Signal DM/group admission rules authorize the sender.
Delivery targets (CLI/cron)
- DMs:
signal:+15551234567(or plain E.164). - UUID DMs:
uuid:<id>(or bare UUID). - Groups:
signal:group:<groupId>. - Usernames:
username:<name>(if supported by your Signal account).
Aliases
Configure aliases for stable names on recurring Signal targets. Aliases are OpenClaw-side config only; they do not create or edit Signal contacts.openclaw directory peers list --channel signal and openclaw directory groups list --channel signal list configured aliases. The Signal directory is config-backed; it does not live-query Signal contacts or mutate the Signal account.
Troubleshooting
Run this ladder first:- Daemon reachable but no replies: verify
account,transport.kind, the transport URL, and receive mode. - DMs ignored: sender is pending pairing approval.
- Group messages ignored: group sender/mention gating blocks delivery.
- Config validation errors after edits: run
openclaw doctor --fix. - Signal missing from diagnostics: confirm
channels.signal.enabled: true.
Security notes
signal-clistores account keys locally (typically~/.local/share/signal-cli/data/).- Back up Signal account state before server migration or rebuild.
- Keep
channels.signal.dmPolicy: "pairing"unless you explicitly want broader DM access. - SMS verification is only needed for registration or recovery flows, but losing control of the number/account can complicate re-registration.
Configuration reference (Signal)
Full configuration: Configuration Provider options:channels.signal.enabled: enable/disable channel startup.channels.signal.account: E.164 for the bot account.channels.signal.accountUuid: optional bot account UUID for native @mention detection and loop protection.channels.signal.transport: account-owned transport. Omit it for managed native defaults.channels.signal.transport.kind:managed-native | external-native | container.channels.signal.transport.url: required forexternal-nativeandcontainer; optional formanaged-nativewhen its connection endpoint differs from the daemon bind.channels.signal.transport.cliPath: managed-native path tosignal-cli.channels.signal.transport.configPath: optional managed-nativesignal-cli --configdirectory.channels.signal.transport.httpHost,channels.signal.transport.httpPort: managed-native daemon bind (default127.0.0.1:8080).channels.signal.transport.startupTimeoutMs: managed-native startup wait in ms (min 1000, cap 120000; default 30000).channels.signal.transport.receiveMode: managed-nativeon-start | manual.channels.signal.ignoreAttachments: skip inbound attachment downloads for this account.channels.signal.transport.ignoreStories: managed-native story toggle.channels.signal.sendReadReceipts: forward read receipts.channels.signal.dmPolicy:pairing | allowlist | open | disabled(default: pairing).channels.signal.allowFrom: DM allowlist (E.164 oruuid:<id>).openrequires"*". Signal has no usernames; use phone/UUID IDs.channels.signal.aliases: OpenClaw-side aliases for DM or group delivery targets.channels.signal.groupPolicy:open | allowlist | disabled(default: allowlist).channels.signal.groupAllowFrom: group allowlist; accepts Signal group IDs (raw,group:<id>, orsignal:group:<id>), sender E.164 numbers, oruuid:<id>values.channels.signal.groups: per-group overrides keyed by Signal group ID (or"*"). Supported fields:requireMention,tools,toolsBySender.channels.signal.accounts.<id>.groups: per-account version ofchannels.signal.groupsfor multi-account setups.channels.signal.accounts.<id>.aliases: per-account aliases, merged with top-level aliases.channels.signal.replyToMode: native reply quote mode,off | first | all | batched(default:all).channels.signal.replyToModeByChatType.direct,channels.signal.replyToModeByChatType.group: per-chat-type native reply quote overrides.channels.signal.accounts.<id>.replyToMode,channels.signal.accounts.<id>.replyToModeByChatType.direct,channels.signal.accounts.<id>.replyToModeByChatType.group: per-account reply quote overrides.channels.signal.historyLimit: max group messages to include as context (0 disables).channels.signal.dmHistoryLimit: DM history limit in user turns. Per-user overrides:channels.signal.dms["<phone_or_uuid>"].historyLimit.channels.signal.textChunkLimit: outbound chunk size in characters (default 4000).channels.signal.streaming.chunkMode:length(default) ornewlineto split on blank lines (paragraph boundaries) before length chunking.channels.signal.mediaMaxMb: inbound/outbound media cap in MB (default 8).channels.signal.reactionLevel:off | ack | minimal | extensive(defaultminimal). See Reactions.channels.signal.reactionNotifications:off | own | all | allowlist(defaultown) - when the agent is notified of incoming reactions from others.channels.signal.reactionAllowlist: senders whose reactions notify the agent whenreactionNotifications: "allowlist".channels.signal.streaming.block.enabled,channels.signal.streaming.block.coalesce: block-mode streaming controls shared across channels. See Streaming.
agents.entries.*.groupChat.mentionPatterns(plain-text fallback; Signal native @mentions are detected from structured metadata when the bot account identity is configured).messages.groupChat.mentionPatterns(global fallback).channels.signal.responsePrefixor an account-levelresponsePrefix.
Related
- Channels Overview - all supported channels
- Pairing - DM authentication and pairing flow
- Groups - group chat behavior and mention gating
- Channel Routing - session routing for messages
- Security - access model and hardening