Skip to content

Connect OpenClaw to Signal: Quick Setup Guide

Before you get started, make sure you have these items ready:

  • OpenClaw installed on your server (the Linux steps below were tested on Ubuntu 24).
  • signal-cli available on the host where your gateway runs.
  • A phone number that can receive a verification SMS (if you choose the SMS registration path).
  • Browser access for the Signal captcha (signalcaptchas.org) during the registration process.

Setting this up is straightforward. Just follow these steps:

  1. Use a separate Signal number for the bot.
  2. Install signal-cli. Note that you will need Java if you are using the JVM build.
  3. Choose one setup path:
    • Path A (QR link): Run signal-cli link -n "OpenClaw" and scan the resulting code with your Signal app.
    • Path B (SMS register): Register a dedicated number using the captcha and SMS verification process.
  4. Configure OpenClaw and restart the gateway.
  5. Send a first DM and approve the pairing by running openclaw pairing approve signal <CODE>.

Minimal config:

{
channels: {
signal: {
enabled: true,
account: "+15551234567",
cliPath: "signal-cli",
dmPolicy: "pairing",
allowFrom: ["+15557654321"],
},
},
}

Field reference:

FieldDescription
accountBot phone number in E.164 format (+15551234567)
cliPathPath to signal-cli (signal-cli if on PATH)
dmPolicyDM access policy (pairing recommended)
allowFromPhone numbers or uuid:<id> values allowed to DM

This is a Signal channel that works via signal-cli instead of an embedded libsignal. It uses deterministic routing, which ensures that replies always go back to Signal correctly. Direct messages share the main session of the agent, while groups are kept isolated using the format agent:<agentId>:signal:group:<groupId>.

By default, Signal can write config updates triggered by /config set|unset. This requires commands.config: true to be active.

If you want to disable this behavior, use the following configuration:

{
channels: { signal: { configWrites: false } },
}

You need to know how the gateway handles numbers before you start. The gateway connects to a Signal device, which is basically your signal-cli account.

If you decide to run the bot on your personal Signal account, keep in mind that it will ignore your own messages to prevent loop protection. If you want the “I text the bot and it replies” experience, you should use a separate bot number.

Section titled “Setup path A: link existing Signal account (QR)”

If you want to link an account you already have, follow these steps:

  1. Install signal-cli (you can use the JVM or native build).
  2. Link your bot account by running:
    • signal-cli link -n "OpenClaw" and then scan the QR code that appears using your Signal app.
  3. Configure Signal and start the gateway.

Here is an example configuration:

{
channels: {
signal: {
enabled: true,
account: "+15551234567",
cliPath: "signal-cli",
dmPolicy: "pairing",
allowFrom: ["+15557654321"],
},
},
}

If you need multi-account support, you can use channels.signal.accounts with per-account settings and an optional name. You can find the shared pattern in the gateway/configuration documentation.

Setup path B: register dedicated bot number (SMS, Linux)

Section titled “Setup path B: register dedicated bot number (SMS, Linux)”

Use this path when you want a dedicated bot number instead of linking an existing app account.

  1. Get a number that can receive SMS (or voice verification if you are using a landline). Using a dedicated bot number is the best way to avoid account or session conflicts.
  2. Install signal-cli on your gateway host:
Terminal window
VERSION=$(curl -Ls -o /dev/null -w %{url_effective} https://github.com/AsamK/signal-cli/releases/latest | sed -e 's/^.*\/v//')
curl -L -O "https://github.com/AsamK/signal-cli/releases/download/v${VERSION}/signal-cli-${VERSION}-Linux-native.tar.gz"
sudo tar xf "signal-cli-${VERSION}-Linux-native.tar.gz" -C /opt
sudo ln -sf /opt/signal-cli /usr/local/bin/
signal-cli --version

If you choose the JVM build (signal-cli-${VERSION}.tar.gz), make sure you install JRE 25+ first. You should keep signal-cli updated because old releases can break when Signal server APIs change.

  1. Register and verify your number:
Terminal window
signal-cli -a +<BOT_PHONE_NUMBER> register

If the system requires a captcha:

  1. Open https://signalcaptchas.org/registration/generate.html.
  2. Complete the captcha and copy the signalcaptcha://... link target from the “Open Signal” button.
  3. Try to run this from the same external IP as your browser session if possible.
  4. Run the registration again immediately, as captcha tokens expire fast:
Terminal window
signal-cli -a +<BOT_PHONE_NUMBER> register --captcha '<SIGNALCAPTCHA_URL>'
signal-cli -a +<BOT_PHONE_NUMBER> verify <VERIFICATION_CODE>
  1. Configure OpenClaw, restart the gateway, and verify the channel:
Terminal window
# If you run the gateway as a user systemd service:
systemctl --user restart openclaw-gateway
# Then verify:
openclaw doctor
openclaw channels status --probe
  1. Pair your DM sender:
    • Send any message to the bot number.
    • Approve the code on the server: openclaw pairing approve signal <PAIRING_CODE>.
    • Save the bot number as a contact on your phone so you don’t see “Unknown contact” warnings.

Important: Registering a phone number account with signal-cli can de-authenticate your main Signal app session for that number. You should prefer a dedicated bot number, or use the QR link mode if you want to keep your existing phone setup active.

Upstream references:

  • signal-cli README: 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)

If you want to manage signal-cli yourself—perhaps to avoid slow JVM cold starts, handle container initialization, or manage shared CPUs—you can run the daemon separately and point OpenClaw at it:

{
channels: {
signal: {
httpUrl: "http://127.0.0.1:8080",
autoStart: false,
},
},
}

This setup skips the auto-spawn process and the startup wait inside OpenClaw. If you experience slow starts while auto-spawning, you can adjust channels.signal.startupTimeoutMs.

Managing who can talk to your bot is a big deal. For DMs, the default setting is channels.signal.dmPolicy = "pairing". If someone unknown sends a message, they get a pairing code that expires in an hour. You can handle these requests using these commands:

  • openclaw pairing list signal
  • openclaw pairing approve signal <CODE>

This pairing process is the standard way to exchange tokens for Signal DMs. You can find more details in the Pairing documentation. If you have senders identified only by UUID (from sourceUuid), they are stored as uuid:<id> in your channels.signal.allowFrom list.

For groups, you have three options for channels.signal.groupPolicy: open, allowlist, or disabled. When you use the allowlist setting, channels.signal.groupAllowFrom determines who can trigger the bot. You can also get specific with channels.signal.groups["<group-id>" | "*"] to override things like requireMention, tools, and toolsBySender.

If you are running multiple accounts, use channels.signal.accounts.<id>.groups for specific overrides. Just a heads up: if channels.signal is completely missing from your config, the system defaults to groupPolicy="allowlist" for group checks, even if you set a different default in channels.defaults.groupPolicy.

Under the hood, signal-cli runs as a daemon. The gateway picks up events using SSE. Every inbound message gets normalized into a shared channel envelope so the rest of the system understands it. When the bot replies, it always routes back to the original number or group.

Signal has some limits you should know about. Outbound text gets split into chunks based on channels.signal.textChunkLimit, which defaults to 4000 characters. If you want cleaner breaks, set channels.signal.chunkMode="newline" to split on blank lines before the system checks the length.

Attachments work via base64 data fetched from signal-cli. There is a media cap of 8MB by default (channels.signal.mediaMaxMb), but you can change that. If you want to skip downloading media entirely, use channels.signal.ignoreAttachments.

For group history context, the bot looks at channels.signal.historyLimit or channels.signal.accounts.*.historyLimit. If those aren’t set, it falls back to messages.groupChat.historyLimit. The default is 50, but you can set it to 0 to disable it.

To make the bot feel more natural, OpenClaw sends typing indicators using signal-cli sendTyping. It refreshes these signals while a reply is running.

You can also enable read receipts for allowed DMs by setting channels.signal.sendReadReceipts to true. Keep in mind that signal-cli does not support read receipts for groups yet.

You can use the message action=react tool with channel=signal to interact with messages. When you want to target a specific message, you’ll need the sender’s E.164 phone number or their UUID. If you use a UUID, you can get it from your pairing output and format it as uuid:<id>, though the bare UUID works as well.

The messageId you need is the Signal timestamp for the specific message you are reacting to. If you are dealing with group reactions, you must include the targetAuthor or targetAuthorUuid.

Here are a few examples of how this looks in practice:

message action=react channel=signal target=uuid:123e4567-e89b-12d3-a456-426614174000 messageId=1737630212345 emoji=🔥
message action=react channel=signal target=+15551234567 messageId=1737630212345 emoji=🔥 remove=true
message action=react channel=signal target=signal:group:<groupId> targetAuthor=uuid:<sender-uuid> messageId=1737630212345 emoji=✅

You can manage how these reactions behave through your configuration. The channels.signal.actions.reactions setting lets you enable or disable these actions, and it is set to true by default. The channels.signal.reactionLevel setting determines how the agent handles reactions. You can set this to off, ack, minimal, or extensive.

If you choose off or ack, the agent won’t be able to react, and the tool will return an error. Choosing minimal or extensive enables the reactions and sets the guidance level. If you need to customize this for specific accounts, you can use overrides like channels.signal.accounts.<id>.actions.reactions and channels.signal.accounts.<id>.reactionLevel.

When you are working with the CLI or setting up cron jobs, you have several ways to specify where your messages should go.

For direct messages, you can use signal:+15551234567 or the plain E.164 number. If you prefer using UUIDs for DMs, use the uuid:<id> format or just the bare UUID. For group chats, use the signal:group:<groupId> format. If your Signal account supports it, you can also use username:<name> as a target.

If things aren’t working as expected, run through this command ladder first to see where the break is:

Terminal window
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe

You can also confirm your DM pairing state if you suspect a connection issue:

Terminal window
openclaw pairing list signal

Here are the common failures you might run into:

  • The daemon is reachable but there are no replies: You should verify your account and daemon settings, specifically httpUrl and account, along with your receive mode.
  • DMs are ignored: This usually happens when the sender is still pending pairing approval.
  • Group messages are ignored: Check if group sender or mention gating is blocking delivery.
  • Config validation errors after you’ve made edits: Just run openclaw doctor --fix to clean things up.
  • Signal is missing from diagnostics: Double-check that channels.signal.enabled: true is set in your config.

If you need to check the system state further, use these extra commands:

Terminal window
openclaw pairing list signal
pgrep -af signal-cli
grep -i "signal" "/tmp/openclaw/openclaw-$(date +%Y-%m-%d).log" | tail -20

For a full triage flow, head over to /channels/troubleshooting.

  • Your signal-cli account keys are stored locally, typically found at ~/.local/share/signal-cli/data/.
  • Make sure you back up your Signal account state before you start a server migration or a rebuild.
  • It is best to keep channels.signal.dmPolicy: "pairing" active unless you have a specific reason for broader DM access.
  • You only need SMS verification for registration or recovery flows. Keep in mind that losing control of your number or account can make re-registration a lot harder.

If you’re looking for the full configuration, you can find it here: Configuration.

When you’re setting up the Signal provider, you have several options to play with:

  • channels.signal.enabled: This turns the channel on or off when you start up.
  • channels.signal.account: Use the E.164 format for your bot account here.
  • channels.signal.cliPath: This is the path to your signal-cli installation.
  • channels.signal.httpUrl: If you have a full daemon URL, put it here. It will override any host or port settings.
  • channels.signal.httpHost, channels.signal.httpPort: These define where the daemon binds. The default is 127.0.0.1:8080.
  • channels.signal.autoStart: This tells the system to automatically start the daemon. It defaults to true if you haven’t set an httpUrl.
  • channels.signal.startupTimeoutMs: This is how long the system waits for startup in milliseconds. It’s capped at 120000.
  • channels.signal.receiveMode: You can set this to on-start or manual.
  • channels.signal.ignoreAttachments: Use this if you want to skip downloading attachments.
  • channels.signal.ignoreStories: This lets you ignore any stories coming from the daemon.
  • channels.signal.sendReadReceipts: This handles forwarding read receipts.
  • channels.signal.dmPolicy: You can choose between pairing, allowlist, open, or disabled. The default is pairing.
  • channels.signal.allowFrom: This is your DM allowlist. You can use E.164 numbers or uuid:<id>. If you set the policy to open, you’ll need to use "*" here. Since Signal doesn’t use usernames, you’ll need phone or UUID IDs.
  • channels.signal.groupPolicy: Set this to open, allowlist, or disabled. It defaults to allowlist.
  • channels.signal.groupAllowFrom: This is the allowlist for people sending messages in groups.
  • channels.signal.groups: You can set per-group overrides here, keyed by the Signal group ID (or use "*"). It supports fields like requireMention, tools, and toolsBySender.
  • channels.signal.accounts.<id>.groups: This is just the per-account version of the groups setting if you’re running a multi-account setup.
  • channels.signal.historyLimit: This sets the maximum number of group messages to include for context. Setting it to 0 turns it off.
  • channels.signal.dmHistoryLimit: This limits DM history based on user turns. If you need to override this for a specific user, use channels.signal.dms["<phone_or_uuid>"].historyLimit.
  • channels.signal.textChunkLimit: This defines the size of outbound message chunks in characters.
  • channels.signal.chunkMode: You can use length (the default) or newline. newline splits messages at blank lines (paragraph boundaries) before it hits the length limit.
  • channels.signal.mediaMaxMb: This sets the cap for both inbound and outbound media in MB.

There are also a few global options that affect Signal:

  • agents.list[].groupChat.mentionPatterns: Keep in mind that Signal doesn’t support native mentions.
  • messages.groupChat.mentionPatterns: This acts as your global fallback.
  • messages.responsePrefix.

If you want to look into this further, check out these resources:

AI Setup Assistant

OpenClaw

OpenClaw Expert

Still stuck?

If this page didn't answer your case, ask OpenClaw Expert for step-by-step guidance.