> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/nullclaw/nullclaw/llms.txt
> Use this file to discover all available pages before exploring further.

# Signal Channel

> Private messaging via signal-cli daemon

The Signal channel connects NullClaw to Signal Messenger using the signal-cli daemon's HTTP/JSON-RPC API for maximum privacy and security.

## Features

* End-to-end encrypted messaging
* Group chat support
* Allowlist-based access control
* UUID and phone number support
* Configurable group policies
* Real-time message delivery via Server-Sent Events (SSE)
* Typing indicators
* Attachment handling

## Prerequisites

You must have signal-cli installed and running in daemon mode:

```bash theme={null}
# Install signal-cli (example for macOS)
brew install signal-cli

# Register your phone number (first time only)
signal-cli -u +1234567890 register
signal-cli -u +1234567890 verify <CODE>

# Start daemon mode
signal-cli --account +1234567890 daemon --http 127.0.0.1:8080
```

See [signal-cli documentation](https://github.com/AsamK/signal-cli) for detailed installation and setup instructions.

## Configuration

Add Signal to your `config.json`:

```json theme={null}
{
  "channels": {
    "signal": {
      "accounts": {
        "main": {
          "http_url": "http://127.0.0.1:8080",
          "account": "+1234567890",
          "allow_from": ["+1111111111", "uuid:a1b2c3d4-..."],
          "group_allow_from": ["+1111111111"],
          "group_policy": "allowlist",
          "ignore_attachments": true,
          "ignore_stories": true
        }
      }
    }
  }
}
```

### Configuration Options

<ParamField path="http_url" type="string" required>
  Base URL of the signal-cli daemon (e.g., `http://127.0.0.1:8080`). Trailing slashes are automatically stripped.
</ParamField>

<ParamField path="account" type="string" required>
  Your Signal account phone number in E.164 format (e.g., `+1234567890`)
</ParamField>

<ParamField path="allow_from" type="string[]" default="[]">
  List of allowed senders (phone numbers or UUIDs). Empty list = deny all (secure by default).

  Formats:

  * Phone: `"+1234567890"`
  * UUID: `"uuid:a1b2c3d4-5678-90ab-cdef-1234567890ab"`
  * Wildcard: `"*"` (not recommended)
</ParamField>

<ParamField path="group_allow_from" type="string[]" default="[]">
  Senders allowed in group chats when `group_policy` is `"allowlist"`. If empty, falls back to `allow_from`.
</ParamField>

<ParamField path="group_policy" type="enum" default="allowlist">
  Group message handling:

  * `"open"` — Respond to all group messages
  * `"allowlist"` — Only respond to senders in `group_allow_from`
  * `"disabled"` — Ignore all group messages
</ParamField>

<ParamField path="ignore_attachments" type="boolean" default="true">
  Skip messages that contain only attachments with no text content
</ParamField>

<ParamField path="ignore_stories" type="boolean" default="true">
  Ignore Signal story posts
</ParamField>

<ParamField path="account_id" type="string" default="default">
  Account identifier for multi-account setups
</ParamField>

## Setup Guide

<Steps>
  <Step title="Install signal-cli">
    **macOS (Homebrew):**

    ```bash theme={null}
    brew install signal-cli
    ```

    **Linux (Debian/Ubuntu):**

    ```bash theme={null}
    # See https://github.com/AsamK/signal-cli for latest instructions
    wget https://github.com/AsamK/signal-cli/releases/download/v0.13.5/signal-cli_0.13.5_amd64.deb
    sudo dpkg -i signal-cli_0.13.5_amd64.deb
    ```

    **From source:**

    ```bash theme={null}
    git clone https://github.com/AsamK/signal-cli.git
    cd signal-cli
    ./gradlew installDist
    ```
  </Step>

  <Step title="Register Your Phone Number">
    ```bash theme={null}
    # Request verification code
    signal-cli -u +1234567890 register

    # Enter the code you received via SMS
    signal-cli -u +1234567890 verify 123456
    ```

    Note: Use a dedicated phone number for your bot, not your personal Signal account.
  </Step>

  <Step title="Start signal-cli Daemon">
    ```bash theme={null}
    signal-cli --account +1234567890 daemon --http 127.0.0.1:8080
    ```

    Leave this running in the background or set up as a system service.
  </Step>

  <Step title="Get Sender UUIDs or Phone Numbers">
    From your Signal app, share your contact information with someone who will send messages to the bot. You can use:

    * Phone numbers (e.g., `+1234567890`)
    * UUIDs from signal-cli logs when messages arrive
  </Step>

  <Step title="Configure NullClaw">
    Add Signal to `~/.nullclaw/config.json`:

    ```json theme={null}
    {
      "channels": {
        "signal": {
          "accounts": {
            "main": {
              "http_url": "http://127.0.0.1:8080",
              "account": "+1234567890",
              "allow_from": ["+1111111111"]
            }
          }
        }
      }
    }
    ```
  </Step>

  <Step title="Start NullClaw">
    Run `nullclaw` and send a Signal message to the bot's phone number from an allowed contact. You should receive a response.
  </Step>
</Steps>

## Group Chat Support

NullClaw supports Signal group chats with configurable policies:

### Allowlist-Based Groups

```json theme={null}
{
  "group_policy": "allowlist",
  "group_allow_from": ["+1111111111", "+2222222222"]
}
```

Only specified users can interact in groups.

### Open Groups

```json theme={null}
{
  "group_policy": "open"
}
```

Respond to all messages in groups (not recommended for privacy).

### Disabled Groups

```json theme={null}
{
  "group_policy": "disabled"
}
```

Ignore all group messages, only respond to 1:1 DMs.

## UUID vs Phone Number

Signal supports both phone numbers and UUIDs for identification:

**Phone numbers** (E.164 format):

```json theme={null}
{
  "allow_from": ["+1234567890", "+9876543210"]
}
```

**UUIDs** (for privacy mode users):

```json theme={null}
{
  "allow_from": [
    "uuid:a1b2c3d4-5678-90ab-cdef-1234567890ab",
    "uuid:b2c3d4e5-6789-01bc-defg-234567890abc"
  ]
}
```

**Mixed**:

```json theme={null}
{
  "allow_from": [
    "+1234567890",
    "uuid:a1b2c3d4-5678-90ab-cdef-1234567890ab"
  ]
}
```

## Message Limits

Signal has no strict character limit, but NullClaw chunks messages at 4096 characters for optimal UX.

## Attachment Handling

By default, NullClaw ignores attachment-only messages:

```json theme={null}
{
  "ignore_attachments": true
}
```

Set to `false` to process messages with attachments. Attachment content is not passed to the agent (only text).

## Running as a System Service

### systemd (Linux)

Create `/etc/systemd/system/signal-cli.service`:

```ini theme={null}
[Unit]
Description=signal-cli daemon
After=network.target

[Service]
Type=simple
User=nullclaw
ExecStart=/usr/local/bin/signal-cli --account +1234567890 daemon --http 127.0.0.1:8080
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
```

Enable and start:

```bash theme={null}
sudo systemctl enable signal-cli
sudo systemctl start signal-cli
```

### launchd (macOS)

Create `~/Library/LaunchAgents/com.signal-cli.daemon.plist`:

```xml theme={null}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.signal-cli.daemon</string>
    <key>ProgramArguments</key>
    <array>
        <string>/opt/homebrew/bin/signal-cli</string>
        <string>--account</string>
        <string>+1234567890</string>
        <string>daemon</string>
        <string>--http</string>
        <string>127.0.0.1:8080</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
</dict>
</plist>
```

Load:

```bash theme={null}
launchctl load ~/Library/LaunchAgents/com.signal-cli.daemon.plist
```

## Troubleshooting

### Daemon Not Starting

1. Verify signal-cli is installed: `signal-cli --version`
2. Check that phone number is registered: `signal-cli -u +1234567890 listIdentities`
3. Ensure port 8080 is not in use: `lsof -i :8080`
4. Check daemon logs for errors

### Messages Not Received

1. Verify daemon is running: `curl http://127.0.0.1:8080/api/v1/check`
2. Check sender is in `allow_from` list
3. Verify phone number format is E.164 (`+` prefix, country code)
4. For groups, check `group_policy` and `group_allow_from`
5. Review NullClaw logs: `nullclaw --log-level debug`

### Connection Refused

1. Verify `http_url` matches daemon address (default: `http://127.0.0.1:8080`)
2. Check firewall rules if daemon is on remote host
3. Ensure daemon is listening on correct interface (not just localhost if remote)

### UUID Mismatches

1. UUIDs change when users reset their Signal account
2. Check signal-cli logs for current sender UUIDs
3. Update `allow_from` with new UUIDs as needed

## Source Code

Implementation: `src/channels/signal.zig`
