Skip to content

Integrations

Besides its own public page, a ticker can push every message to Telegram, Mastodon, Bluesky and Signal groups.

Integrations are configured at runtime, not in a config file

All credentials live in the database and are managed through the admin interface. There are no environment variables and no config.yml keys for them. Older documentation described telegram: and signal_group: config blocks and variables such as TICKER_TELEGRAM_TOKEN — these no longer exist and are silently ignored if present.

There are two levels:

Integration Instance-wide setup Per-ticker setup
Telegram required (bot token) channel
Signal required (signal-cli endpoint) group
Mastodon none server and credentials
Bluesky none handle and app password

Telegram and Signal need an instance-wide step by a super admin before editors can use them. Until that is done, the admin interface hides them.

Telegram

Instance-wide. Create a bot with @BotFather and note the token. As a super admin, enter it in the admin interface under the Telegram settings.

Stored as the telegram_settings record, with fields token and botUsername.

Per ticker. Create a Telegram channel, add the bot as an administrator with permission to post, then set the channel name on the ticker.

Signal

Instance-wide. Signal support talks to signal-cli running in daemon mode, which is not part of the Ticker stack — you run it yourself.

signal-cli's own JSON-RPC interface is required

Ticker calls signal-cli's native JSON-RPC methods (send, updateGroup, listGroups, quitGroup, remoteDelete) directly. A REST wrapper around signal-cli exposes different endpoints and will not work — point Ticker at the official image's --http endpoint.

Add it to the stack on the internal network:

  signal-cli:
    image: ghcr.io/asamk/signal-cli:0.13.2
    # The published image is amd64 only.
    platform: linux/amd64
    restart: unless-stopped
    # The image's entrypoint is already
    # "signal-cli --config=/var/lib/signal-cli", so only the subcommand is given.
    # --http must bind 0.0.0.0: the default is localhost, which is unreachable
    # from other containers.
    command: ["daemon", "--http=0.0.0.0:8080"]
    volumes:
      - signal-cli-data:/var/lib/signal-cli
    networks:
      - internal

…and add signal-cli-data: to the volumes: block at the bottom of the file.

The container runs as UID 999 and /var/lib/signal-cli already exists in the image, so a fresh named volume inherits the right ownership — unlike the API's uploads volume, this one needs no preparation.

Register the account first

signal-cli needs a registered or linked Signal account before the daemon is useful. Do this once, against the same volume, following signal-cli's own documentation:

# Link this instance as a secondary device to an existing Signal account
docker compose run --rm signal-cli link -n "Ticker"

This prints a sgnl://linkdevice?uuid=… URI. Turn it into a QR code (for example with qrencode) and scan it from Signal on your phone under Linked devices.

Registering a dedicated number instead of linking is also possible; see signal-cli's documentation, as it involves a captcha and an SMS verification step.

Afterwards start the daemon and confirm it answers:

docker compose up -d signal-cli
docker compose exec ticker-init sh -c \
  'wget -qO- --post-data="{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"listGroups\",\"params\":{\"account\":\"+491234567890\"}}" \
   --header="Content-Type: application/json" http://signal-cli:8080/api/v1/rpc'

In daemon mode signal-cli continuously receives messages, which the Signal protocol requires for group functionality to work reliably.

Configure Ticker

As a super admin, fill in the Signal settings in the admin interface:

Field Meaning
apiUrl Full URL of signal-cli's JSON-RPC endpoint, including the path: http://signal-cli:8080/api/v1/rpc
account The registered phone number in international format, e.g. +491234567890
avatar Optional path to an avatar image for created groups

The endpoint path /api/v1/rpc is not optional — it is where --http serves JSON-RPC, and Ticker POSTs to exactly the URL you configure.

avatar is passed through to signal-cli, which reads it from its own filesystem. The path must therefore exist inside the signal-cli container, not in the Ticker container. Mount the file in if you want one.

Stored as the signal_group_settings record. Signal is considered enabled once both apiUrl and account are set; avatar is optional.

Per ticker. Enabling Signal on a ticker creates a Signal group and returns an invite link that you can publish. Ticker admins can be promoted to group admins. Messages posted to the ticker are sent to the group, and deleting a message removes it there too.

Mastodon

Configured entirely per ticker: the server URL and the application credentials. Register an application on your Mastodon instance and enter the resulting values on the ticker. It is considered connected once the token, secret and access token are all present.

Bluesky

Configured entirely per ticker: the handle and an app password — generate one in the Bluesky settings rather than using your account password.

Optionally restrict who may reply to the posts. Values are followers, following, mentioned and nobody; leave empty to allow anyone. Several can be combined with commas, for example followers,mentioned.

Behaviour

Dispatch happens when a message is created, and integrations are also told about deletions and ticker updates. A failing integration is logged and does not block the message from appearing on the ticker itself, so a broken Telegram token will not take your public page down. Check the API logs if a message did not arrive somewhere:

docker compose logs ticker | grep bridge_name

Attachments are sent along as files, read straight from TICKER_UPLOAD_PATH — no public URL is involved, so an integration keeps working even if the interfaces are unreachable.