signal-cli: the unofficial Signal client for servers and scripts
signal-cli provides an unofficial commandline, JSON-RPC and dbus interface for the Signal messenger.
At a glance
- What is it?
- signal-cli gives servers and scripts a command line, JSON-RPC and D-Bus way into Signal, with a three-month release window imposed by the official client expiry. Here is how it installs, how linking and sending work, and where it stops being the right tool.
- Who is it for?
- Adopt signal-cli if you need scripted or server-side Signal delivery and can commit to re-installing within the three-month window the README describes. Do not adopt it for end-user messaging on a phone, and do not treat the JSON-RPC daemon as a general-purpose REST API, because the interface is JSON-RPC over a socket and the README points at a Rust example client rather than an HTTP service.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What signal-cli is for, and who it is not for
The README states the intended audience plainly: signal-cli is primarily intended to be used on servers to notify admins of important events. That sentence should drive the adoption decision. The tool exposes Signal through three surfaces, a command line, a JSON-RPC daemon and a D-Bus interface, and the daemon surfaces exist specifically so that a monitoring system or a script can push a message without a human in the loop.
It is not a replacement for the Signal app. There is no graphical interface, no message history browser, and no support for the media features a phone client offers. Registration is possible from the command line, but the README warns that registering a number this way will unregister any existing client associated with the same number. If you already use Signal on a phone with that number, linking is the path the README recommends, and linking requires a compatible Signal mobile app to produce the linking code.
The other boundary is currency. signal-cli needs to be kept up-to-date to keep up with Signal-Server changes, because official Signal clients expire after three months and the server can then make incompatible changes. A release older than three months may not work correctly. That single constraint rules out the common pattern of installing once on a long-lived server and forgetting about it.
Accounts, linking and the storage directory
signal-cli works against an account, and the README distinguishes two kinds. A numbered account is a phone number in international format, including the country calling code, so it starts with a plus sign. A linked account without a phone number is addressed by its ACI, the Account ID.
The password and cryptographic keys are created when registering and stored in the current user's home directory, under $XDG_DATA_HOME/signal-cli/data/ or $HOME/.local/share/signal-cli/data/. That location matters operationally for two reasons. First, it is per-user, so a daemon running as a service account reads that account's home directory, not root's. Second, it is the state you must preserve across upgrades; reinstalling the binary while keeping the data directory is what makes an upgrade non-destructive.
One more constraint from the README deserves attention before you build anything on top of signal-cli: the Signal protocol expects incoming messages to be received regularly, using the daemon or the receive command. The README ties this to encryption working efficiently and to receiving updates to groups, expiration timers and other features. A script that only ever sends will drift out of sync with the account state.
Installing signal-cli on Linux and sending a first message
The README offers two system-wide Linux installs from the published release archives, one for the JVM build and one for the GraalVM native build. The JVM variant needs at least Java Runtime Environment 25 and the libsignal-client native library, which is bundled for x86_64 Linux, Windows and macOS. The snippet below resolves the latest release tag, downloads the tarball, unpacks it under /opt and links the launcher into /usr/local/bin.
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}".tar.gz
sudo tar xf signal-cli-"${VERSION}".tar.gz -C /opt
sudo ln -sf /opt/signal-cli-"${VERSION}"/bin/signal-cli /usr/local/bin/After that, signal-cli should be on your PATH. The native build follows the same shape but downloads the archive named with the -Linux-native suffix and links /opt/signal-cli directly; the README marks the GraalVM native build as experimental, so the JVM archive is the safer default.
If you already have a Signal account, link rather than register. The README gives the command as signal-cli link, which produces a linking code you scan from a compatible Signal mobile app.
signal-cli linkFor a fresh number, register and then verify with the code received by SMS or voice. The README notes that registering from signal-cli unregisters any existing client on that number, and that a PIN can be supplied with --pin if the account has one.
signal-cli -a ACCOUNT register
signal-cli -a ACCOUNT verify CODESending is a single command. The recipient is a phone number, or a username prefixed with u:, as in u:USERNAME.000. Message content can also be piped in from another process with --message-from-stdin, which is the form that fits a monitoring script.
signal-cli -a ACCOUNT send -m "This is a message" RECIPIENT
uname -a | signal-cli -a ACCOUNT send --message-from-stdin RECIPIENTReceiving is its own command, and the README's hint about regular receipt applies here as much as it does to the daemon.
signal-cli -a ACCOUNT receiveThe JSON-RPC and D-Bus daemon modes
For the server-notification use case the README points at daemon mode with a JSON-RPC interface and a D-Bus interface, each documented in its own man page in the man/ directory of the repository. The repository also ships a simple example client for the JSON-RPC interface, written in Rust, under the client/ directory, with its own client.Containerfile alongside the main Containerfile.
Two things are worth being explicit about, because the surrounding ecosystem of blog posts tends to blur them. First, this is JSON-RPC, not REST. There is no documented HTTP endpoint in the README, and the related search phrase signal cli rest api does not correspond to anything the README describes. If your integration layer only speaks HTTP, you will need something in between. Second, the daemon and D-Bus interfaces are the supported way to keep messages flowing; the README's warning about regular receipt applies to any architecture that only sends.
The repository layout also shows a data/ directory, a docs/ directory, a lib/ directory, a libsignal-version file and a reproducible-builds/ directory. The libsignal-version file is the kind of detail that matters when you pin a build: it records which libsignal revision the release was built against, and the README explains that signal-cli uses a patched libsignal-service-java extracted from the Signal-Android source.
Where signal-cli breaks, and what it cannot do
The hardest limitation is the release window. Because official Signal clients expire after three months and the Signal-Server can then make incompatible changes, the README states that signal-cli releases older than three months may not work correctly. This is not a bug you can patch around; it is a property of an unofficial client speaking to a server that only guarantees compatibility with its own clients. Any deployment needs a scheduled upgrade path, and any pinned version in a container image is a time bomb measured in weeks, not years.
The second limitation is verification friction. The README describes registration with SMS or voice, and notes that landline registration requires attempting SMS first, expecting a 400 (InvalidTransportModeException), waiting 60 seconds, then retrying with --voice. Registration may also require solving a CAPTCHA challenge, which the README links to a wiki page for. None of this is automatable end to end without a human or a CAPTCHA-solving step.
Third, the native library situation. The bundled libsignal-client binaries cover x86_64 Linux with a recent enough glibc, Windows and macOS. On other architectures the README points to a wiki page on providing the native library yourself. If you run ARM servers, plan for that work up front rather than discovering it at deploy time.
Finally, the licence. signal-cli is GPL-3.0. If you embed it in a product, the copyleft terms apply to the distributed work; that is a decision for your legal team, not something this article can settle.
signal-cli compared with the official Signal clients and with a plain webhook
The obvious alternative is the official Signal app itself. It is supported, it updates itself, and it never expires on a three-month clock. What it does not give you is a scriptable interface: there is no command line, no JSON-RPC daemon and no D-Bus surface, so a monitoring system cannot push a message through it without a human. That is the entire reason signal-cli exists, and it is the trade you are making: automation in exchange for maintaining an unofficial client against a moving server.
The second alternative is not a Signal client at all. If the notification does not have to arrive in Signal specifically, an HTTP webhook to a service that already exposes a stable API removes the upgrade treadmill entirely. The difference in approach is that signal-cli keeps the message inside the Signal network, encrypted the way Signal encrypts it, at the cost of a client you must keep current. A webhook gives you stability at the cost of the delivery channel. Pick based on whether Signal itself is a requirement or merely a convenient destination.
A third comparison is worth naming because the README itself does: signal-cli is built on a patched libsignal-service-java extracted from the Signal-Android source, not on the official Signal libraries. That is what makes the project possible and also what makes the three-month expiry rule apply to it.
Building from source and the upgrade cost
Building is Gradle-based. The README lists the clone, then ./gradlew build, with variants for a shell wrapper in build/install/signal-cli/bin, a tar file in build/distributions, a fat tar in build/libs/signal-cli-fat, and a direct run. If you have a recent Gradle installed, the README says you can replace ./gradlew with gradle.
git clone https://github.com/AsamK/signal-cli.git
./gradlew build
./gradlew installDist
./gradlew run --args="--help"The JSON-RPC data classes under src/main/java/org/asamk/signal/json can be turned into JSON Schema files with ./gradlew jsonSchemas, which land in build/generated/META-INF/schemas. That is a useful step if you generate a typed client for the daemon rather than hand-writing one.
./gradlew jsonSchemasUpgrade cost is the real recurring expense. Given the three-month compatibility window and the current release cadence, where v0.14.6, v0.14.7 and v0.14.8 arrived in July, August and September, the practical rhythm is a re-install every few weeks rather than every quarter. The data directory under $HOME/.local/share/signal-cli/data/ survives that, so the upgrade itself is the tarball swap shown above plus a restart of whatever daemon you run. Budget for that, and for reading CHANGELOG.md before each swap, because a server-side incompatibility will surface as a send or receive failure, not as a startup error.
Editorial conclusion
Adopt signal-cli if you need scripted or server-side Signal delivery and can commit to re-installing within the three-month window the README describes. Do not adopt it for end-user messaging on a phone, and do not treat the JSON-RPC daemon as a general-purpose REST API, because the interface is JSON-RPC over a socket and the README points at a Rust example client rather than an HTTP service. Before rolling it out, verify three things on your own host: that JRE 25 is available, that the bundled libsignal-client native library matches your architecture, and that a linked account can send and receive through the exact command you plan to automate.
Frequently asked questions
What is signal-cli?
It is an unofficial command line, JSON-RPC and D-Bus interface for the Signal messenger, written in Java and licensed GPL-3.0. The README says it is primarily intended to be used on servers to notify admins of important events.
How do I install signal-cli?
You can build it yourself with Gradle or download the provided binary files, which the README says should work on Linux, macOS and Windows. On Linux the README shows unpacking the release tarball under /opt and linking the launcher into /usr/local/bin.
How do I install signal-cli on Ubuntu?
The README's Linux instructions apply: resolve the latest release tag, download the .tar.gz, unpack it into /opt with sudo tar, and symlink /opt/signal-cli-VERSION/bin/signal-cli into /usr/local/bin. The JVM build needs at least JRE 25.
Does signal-cli have an API?
Yes, in daemon mode it exposes a JSON-RPC interface and a D-Bus interface, each with its own man page in the repository. The README also points to a simple example client for the JSON-RPC interface written in Rust, and does not describe an HTTP REST endpoint.
What should I do if signal-cli says the account is not registered?
The README treats registration and linking as separate paths: signal-cli -a ACCOUNT register starts a new registration, while signal-cli link attaches an existing account from a compatible Signal mobile app. Note that registering a number from signal-cli unregisters any existing client on that number.
What can I use instead of signal-cli?
The README does not name alternatives. The practical comparison is the official Signal client, which is supported and self-updating but offers no command line, JSON-RPC or D-Bus surface, so it cannot be driven by a server script.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/asamk-signal-cli)