CasCap.Api.SignalCli 0.10.31

Prefix Reserved
dotnet add package CasCap.Api.SignalCli --version 0.10.31
                    
NuGet\Install-Package CasCap.Api.SignalCli -Version 0.10.31
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CasCap.Api.SignalCli" Version="0.10.31" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CasCap.Api.SignalCli" Version="0.10.31" />
                    
Directory.Packages.props
<PackageReference Include="CasCap.Api.SignalCli" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CasCap.Api.SignalCli --version 0.10.31
                    
#r "nuget: CasCap.Api.SignalCli, 0.10.31"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CasCap.Api.SignalCli@0.10.31
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CasCap.Api.SignalCli&version=0.10.31
                    
Install as a Cake Addin
#tool nuget:?package=CasCap.Api.SignalCli&version=0.10.31
                    
Install as a Cake Tool

CasCap.Api.SignalCli

A .NET library that wraps the signal-cli REST API (generated against v0.98), providing a typed SignalCliRestClientService for sending and managing Signal encrypted messages, together with a health check and DI registration.

Installation

dotnet add package CasCap.Api.SignalCli

Transport Modes

TransportMode has four values, mirroring the MODE environment variable of the signal-cli REST API server, and must match whatever the server is running. Client-side there are only two behaviours: Normal and Native poll over HTTP, while JsonRpc and JsonRpcNative receive pushed frames over a WebSocket. The performance and memory characteristics below are properties of the server mode, not of this client.

Mode Server MODE Service Message reception Server characteristics
Normal normal SignalCliRestClientService HTTP polling (GET /v1/receive/{number}) Slowest, normal memory
Native native SignalCliRestClientService HTTP polling (GET /v1/receive/{number}) Medium, normal memory
JsonRpc json-rpc SignalCliJsonRpcClientService WebSocket push (ws://host/v1/receive/{number}) Faster, increased memory
JsonRpcNative json-rpc-native SignalCliJsonRpcClientService WebSocket push (ws://host/v1/receive/{number}) Fastest, normal memory

On the WebSocket transports the connection is persistent and reconnects automatically with exponential backoff (2 s → 2 min, up to 10 attempts); every non-receive operation still goes over HTTP via the underlying SignalCliRestClientService. Registration, verification and device-linking endpoints are unavailable while the server runs in either JSON-RPC mode, per upstream documentation.

Receiving Messages

Both transports implement ISignalCliReceiver, so inbound messages are consumed the same way regardless of TransportMode. Switching between HTTP polling and the WebSocket push stream is a configuration change, not a code change.

public sealed class EchoWorker(ISignalCliReceiver receiver, ISignalCliClient client,
    IOptions<SignalCliConfig> options) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var account = options.Value.PhoneNumber;
        await foreach (var msg in receiver.StreamMessagesAsync(stoppingToken))
        {
            var sender = msg.Envelope.Source;
            var text = msg.Envelope.DataMessage?.Message;
            if (sender is null || string.IsNullOrWhiteSpace(text))
                continue;

            await client.SendMessage(new SignalMessageRequest
            {
                Number = account,
                Recipients = [sender],
                Message = $"echo: {text}"
            }, stoppingToken);
        }
    }
}

StreamMessagesAsync is a cold stream: enumeration starts the transport (connecting the WebSocket where applicable) and abandoning the enumerator stops it. Cancelling the token ends the stream by throwing OperationCanceledException, per the usual IAsyncEnumerable convention. Only one active enumeration per instance is expected; concurrent consumers compete for messages rather than each receiving a copy.

Calling ConnectAsync first is optional but surfaces connection failures at startup instead of on the first message. On the REST transport it is a no-op.

Controller

The MVC controller lives in a separate package, CasCap.Api.SignalCli.AspNetCore, so that worker services, console apps and daemons can consume this library without taking a dependency on MVC or API versioning.

Purpose

ISignalCliClient is the abstraction over the full signal-cli REST API surface. Depend on it rather than the concrete SignalCliRestClientService so the client can be substituted with a fake in tests. It resolves to SignalCliRestClientService in every transport mode, since only message reception differs between them.

Every method returns null or false on failure and logs the cause; failures are not thrown. The exception is caller-requested cancellation, which propagates as OperationCanceledException so an abandoned call is never mistaken for an API error.

SignalCliRestClientService is the HttpClient-backed implementation:

General

Method Endpoint Description
GetAbout() GET /v1/about Returns version and build information
GetConfiguration() GET /v1/configuration Retrieves the signal-cli configuration
SetConfiguration(config) POST /v1/configuration Updates the signal-cli configuration

Messaging

Method Endpoint Description
SendMessage(SignalMessageRequest) POST /v2/send Sends a message to one or more recipients
ReceiveMessages(number) GET /v1/receive/{number} Receives pending messages for the specified account
ShowTypingIndicator(number, recipient) PUT /v1/typing-indicator/{number} Shows a typing indicator
HideTypingIndicator(number, recipient) DELETE /v1/typing-indicator/{number} Hides a typing indicator
SendReaction(number, recipient, reaction, targetAuthor, timestamp) POST /v1/reactions/{number} Sends a reaction to a message
RemoveReaction(number, recipient, reaction, targetAuthor, timestamp) DELETE /v1/reactions/{number} Removes a reaction from a message
SendReceipt(number, recipient, receiptType, timestamp) POST /v1/receipts/{number} Sends a read/viewed receipt
RemoteDelete(number, recipient, timestamp) DELETE /v1/remote-delete/{number} Remotely deletes a sent message

Registration

Method Endpoint Description
RegisterNumber(number) POST /v1/register/{number} Registers a phone number
VerifyNumber(number, token) POST /v1/register/{number}/verify/{token} Verifies registration
UnregisterNumber(number) POST /v1/unregister/{number} Unregisters a phone number

Accounts

Method Endpoint Description
ListAccounts() GET /v1/accounts Lists all registered accounts
SetPin(number, pin) POST /v1/accounts/{number}/pin Sets a registration PIN
RemovePin(number) DELETE /v1/accounts/{number}/pin Removes the registration PIN
SubmitRateLimitChallenge(number, challengeToken, captcha) POST /v1/accounts/{number}/rate-limit-challenge Submits a rate-limit challenge
UpdateAccountSettings(number, discoverableByNumber, shareNumber) PUT /v1/accounts/{number}/settings Updates account settings
SetUsername(number, username) POST /v1/accounts/{number}/username Sets a username
RemoveUsername(number) DELETE /v1/accounts/{number}/username Removes the username

Contacts

Method Endpoint Description
ListContacts(number) GET /v1/contacts/{number} Lists contacts for an account
UpdateContact(number, recipient, name, expirationInSeconds) PUT /v1/contacts/{number} Updates a contact
SyncContacts(number) POST /v1/contacts/{number}/sync Triggers a contact sync

Devices

Method Endpoint Description
GetQrCodeLink(deviceName) GET /v1/qrcodelink Retrieves a QR code link for device linking
GetQrCodeLinkRaw(deviceName) GET /v1/qrcodelink/raw Retrieves the device-link URI
ListLinkedDevices(number) GET /v1/devices/{number} Lists linked devices
AddDevice(number, uri) POST /v1/devices/{number} Links a new device
RemoveLinkedDevice(number, deviceId) DELETE /v1/devices/{number}/{deviceId} Removes a linked device
DeleteLocalAccountData(number) DELETE /v1/devices/{number}/local-data Deletes local account data

Groups

Method Endpoint Description
ListGroups(number) GET /v1/groups/{number} Lists all groups
GetGroup(number, groupId) GET /v1/groups/{number}/{groupId} Retrieves a specific group
CreateGroup(number, group) POST /v1/groups/{number} Creates a new group
UpdateGroup(number, groupId, group) PUT /v1/groups/{number}/{groupId} Updates a group
DeleteGroup(number, groupId) DELETE /v1/groups/{number}/{groupId} Deletes a group
AddGroupMembers(number, groupId, members) POST /v1/groups/{number}/{groupId}/members Adds members to a group
RemoveGroupMembers(number, groupId, members) DELETE /v1/groups/{number}/{groupId}/members Removes members from a group
AddGroupAdmins(number, groupId, admins) POST /v1/groups/{number}/{groupId}/admins Adds admins to a group
RemoveGroupAdmins(number, groupId, admins) DELETE /v1/groups/{number}/{groupId}/admins Removes admins from a group
JoinGroup(number, groupId) POST /v1/groups/{number}/{groupId}/join Joins a group
QuitGroup(number, groupId) POST /v1/groups/{number}/{groupId}/quit Leaves a group
BlockGroup(number, groupId) POST /v1/groups/{number}/{groupId}/block Blocks a group
GetGroupAvatar(number, groupId) GET /v1/groups/{number}/{groupId}/avatar Downloads a group avatar

Identities

Method Endpoint Description
ListIdentities(number) GET /v1/identities/{number} Lists all known identities
TrustIdentity(number, numberToTrust) PUT /v1/identities/{number}/trust/{numberToTrust} Trusts an identity

Attachments

Method Endpoint Description
ListAttachments() GET /v1/attachments Lists all attachments
GetAttachment(attachmentId) GET /v1/attachments/{attachmentId} Retrieves an attachment
DeleteAttachment(attachmentId) DELETE /v1/attachments/{attachmentId} Deletes an attachment

Profile

Method Endpoint Description
UpdateProfile(number, profile) PUT /v1/profiles/{number} Updates the Signal profile
Method Endpoint Description
SearchNumbers(number, numbers) GET /v1/search/{number} Searches for registered phone numbers

Sticker Packs

Method Endpoint Description
ListStickerPacks(number) GET /v1/sticker-packs/{number} Lists installed sticker packs
AddStickerPack(number, packId, packKey) POST /v1/sticker-packs/{number} Installs a sticker pack

Configuration

Registered via IServiceCollection.AddSignalCli(). Configuration section: CasCap:SignalCliConfig.

Setting Type Default Required Description
TransportMode SignalCliTransport JsonRpc Transport mode: Normal, Native (HTTP polling) or JsonRpc, JsonRpcNative (WebSocket)
BaseAddress string Base URL of the signal-cli REST API (e.g. http://localhost:8080)
HealthCheckUri string "v1/health" Path used to verify API connectivity
HealthCheck KubernetesProbeTypes Readiness Kubernetes probe type for the health check tag
PhoneNumber string Registered Signal sender number (e.g. "+49151...")
PhoneNumberDebug string? null Optional recipient number for debug/diagnostic messages ("Note to Self" feed)
SendTimeoutMs int 180000 Per-request timeout in milliseconds for POST /v2/send
ChannelCapacity int 256 Bounded capacity of the internal message channel (back-pressure when full)
ReceivePollIntervalMs int 1000 Delay between GET /v1/receive/{number} polls when streaming over the REST transport; ignored by the JSON-RPC transports
MaxReconnectAttempts int 10 Maximum number of WebSocket reconnection attempts before giving up
InitialReconnectDelayMs int 2000 Initial backoff delay in milliseconds for WebSocket reconnection
MaxReconnectDelayMs int 120000 Maximum backoff delay in milliseconds for WebSocket reconnection
ReceiveStalenessTimeoutMs int 0 Max silent period (ms) on the receive stream before the watchdog logs an error and forces a reconnect; 0 disables it. Tune above your longest expected inbound gap to avoid false positives on quiet accounts
BasicAuthEnabled bool false Whether to attach HTTP Basic credentials to outgoing requests (cross-cluster access via ingress)
Username string? null Basic auth username, required when BasicAuthEnabled is set unless ApiAuthConfig supplies one
Password string? null Basic auth password, required when BasicAuthEnabled is set unless ApiAuthConfig supplies one

Basic authentication

When the signal-cli REST API sits behind an authenticating reverse proxy, set BasicAuthEnabled and supply Username and Password. The credentials are applied to both the HttpClient and the JSON-RPC WebSocket handshake.

If Username and Password are left unset, the library falls back to CasCap:ApiAuthConfig, which suits hosts that already bind a single set of ingress credentials for every API they call. When neither source yields credentials, registration throws an InvalidOperationException naming both configuration keys rather than failing later with an opaque 401.

Configuration Examples

Minimal

{
  "CasCap": {
    "SignalCliConfig": {
      "BaseAddress": "http://signalcli.monitoring.svc.cluster.local",
      "PhoneNumber": "+49151..."
    }
  }
}

Fully configured

{
  "CasCap": {
    "SignalCliConfig": {
      "TransportMode": "JsonRpc",
      "BaseAddress": "http://signalcli.monitoring.svc.cluster.local",
      "HealthCheckUri": "v1/about",
      "HealthCheck": "Readiness",
      "PhoneNumber": "+49151...",
      "PhoneNumberDebug": "+49151...",
      "SendTimeoutMs": 180000,
      "ChannelCapacity": 256,
      "ReceivePollIntervalMs": 1000,
      "MaxReconnectAttempts": 10,
      "InitialReconnectDelayMs": 2000,
      "MaxReconnectDelayMs": 120000,
      "ReceiveStalenessTimeoutMs": 0
    }
  }
}

Health Check

SignalCliConnectionHealthCheck – Verifies that the signal-cli REST API is reachable by issuing a GET request to {BaseAddress}/{HealthCheckUri}.

Class Hierarchy

classDiagram
    direction LR

    HttpClientBase <|-- SignalCliRestClientService
    HttpEndpointCheckBase <|-- SignalCliConnectionHealthCheck
    ISignalCliClient <|.. SignalCliRestClientService
    ISignalCliReceiver <|.. SignalCliRestClientService
    ISignalCliReceiver <|.. SignalCliJsonRpcClientService
    SignalCliJsonRpcClientService ..> SignalCliRestClientService : delegates

    class ISignalCliClient {
        <<interface>>
        +GetAbout(CancellationToken) SignalAbout?
        +SendMessage(SignalMessageRequest, CancellationToken) SignalMessageResponse?
        +ReceiveMessages(number, CancellationToken) SignalReceivedMessage[]?
        +ListGroups(number, CancellationToken) SignalGroup[]?
        +ListContacts(number, allRecipients, CancellationToken) SignalContact[]?
    }

    class ISignalCliReceiver {
        <<interface>>
        +ConnectAsync(CancellationToken) Task
        +StreamMessagesAsync(CancellationToken) IAsyncEnumerable~SignalReceivedMessage~
    }

    class SignalCliRestClientService {
        -SignalCliConfig _config
        +GetAbout() SignalAbout?
        +SendMessage(SignalMessageRequest) SignalMessageResponse?
        +ReceiveMessages(number) SignalReceivedMessage[]?
        +ListAccounts() string[]?
        +ListGroups(number) SignalGroup[]?
        +ListContacts(number) SignalContact[]?
        +ListIdentities(number) SignalIdentity[]?
        +ListLinkedDevices(number) SignalDevice[]?
        +ListAttachments() string[]?
        +ListStickerPacks(number) SignalStickerPack[]?
        +SearchNumbers(number, numbers) SearchResult[]?
        -GetAsync~T~(requestUri) T?
        -PostAsync~T~(requestUri, body) T?
        -PostBoolAsync(requestUri, body) bool
        -PutAsync(requestUri, body) bool
        -DeleteAsync(requestUri, body) bool
        -DeleteAsync~T~(requestUri, body) T?
    }

    class SignalCliJsonRpcClientService {
        -SignalCliRestClientService _restClient
        -ClientWebSocket? _webSocket
        +ConnectAsync(CancellationToken) Task
        +StreamMessagesAsync(CancellationToken) IAsyncEnumerable~SignalReceivedMessage~
        -ReceiveLoopWithReconnectAsync(CancellationToken) Task
        +BuildWebSocketUri(baseAddress, phoneNumber)$ Uri
    }

    class SignalCliConnectionHealthCheck {
        +Name$ string
    }

    class SignalCliConfig {
        +TransportMode SignalCliTransport
        +BaseAddress string
        +HealthCheckUri string
        +HealthCheck KubernetesProbeTypes
        +SendTimeoutMs int
        +PhoneNumber string
        +PhoneNumberDebug string?
        +ChannelCapacity int
        +ReceivePollIntervalMs int
        +MaxReconnectAttempts int
        +InitialReconnectDelayMs int
        +MaxReconnectDelayMs int
        +BasicAuthEnabled bool
        +Username string?
        +Password string?
    }

    SignalCliRestClientService ..> SignalCliConfig : reads
    SignalCliJsonRpcClientService ..> SignalCliConfig : reads
    SignalCliConnectionHealthCheck ..> SignalCliConfig : reads

DI Registration Flow

flowchart LR
    A["AddSignalCli()"] --> B["Bind SignalCliConfig"]
    A --> C["Register HttpClient"]
    A --> J["ISignalCliClient \u2192 SignalCliRestClientService"]
    A --> D{"TransportMode?"}
    A --> E["Register SignalCliConnectionHealthCheck"]
    D -->|Normal / Native| F["INotifier + ISignalCliReceiver \u2192 SignalCliRestClientService"]
    D -->|JsonRpc / JsonRpcNative| G["INotifier + ISignalCliReceiver \u2192 SignalCliJsonRpcClientService"]
    G --> H["delegates non-receive ops"]
    H --> F2["SignalCliRestClientService"]
    C --> I["IHttpClientFactory"]
    I --> F
    I --> G
    I --> E

Dependencies

NuGet packages

Package Purpose
Microsoft.Extensions.Http HttpClient factory
Microsoft.Extensions.Diagnostics.HealthChecks Health check abstractions
CasCap.Common.Configuration Configuration binding helpers
CasCap.Common.Extensions Shared extension helpers
CasCap.Common.Logging Structured logging helpers
CasCap.Common.Net HTTP client base (HttpClientBase)
CasCap.Common.Extensions.Diagnostics.HealthChecks Kubernetes probe tag helpers
CasCap.Common.Services Shared service utilities

Project references

Project Purpose
CasCap.Common.Configuration Configuration binding helpers

License

This project is released under The Unlicense. See the LICENSE file for details.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on CasCap.Api.SignalCli:

Package Downloads
CasCap.Api.SignalCli.AspNetCore

ASP.NET Core integration for CasCap.Api.SignalCli — a versioned, read-only MVC controller exposing signal-cli account, contact, group, device, identity, attachment and sticker-pack queries.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.10.31 0 9/11/2026
0.10.28 104 8/18/2026
0.10.25 119 7/15/2026
0.10.24 139 6/26/2026
0.10.22 129 6/14/2026
0.10.21 125 6/12/2026
0.10.20 125 6/10/2026
0.10.19 218 5/26/2026
0.10.18 273 5/15/2026
0.10.14 226 5/8/2026