Skip to content

Configuration

Setting up the bridge means creating a Slack app, collecting its two tokens, and writing a short TOML file.

Prerequisites

  • A Screenshotter server reachable from where the bridge runs, with require_auth_to_view = false.
  • Permission to create a Slack app in your workspace.
  • Outbound HTTPS from the bridge host to *.slack.com.

Create the Slack app

At https://api.slack.com/apps choose Create New App → From a manifest, pick your workspace, and paste the following, replacing the domain with the one your screenshot links use:

display_information:
  name: Screenshotter
features:
  bot_user:
    display_name: screenshotter
    always_online: true
  unfurl_domains:
    - screen.corp.example
oauth_config:
  scopes:
    bot:
      - links:read
      - links:write
      - remote_files:write
      - remote_files:read
      # Only with the matching user restriction in the bridge config:
      # - users:read        # block_external_users
      # - usergroups:read   # allowed_usergroups
settings:
  event_subscriptions:
    bot_events:
      - link_shared
  socket_mode_enabled: true
  org_deploy_enabled: false

The same result can be reached through the UI: enable Socket Mode, add the bot scopes above under OAuth & Permissions, subscribe to the link_shared bot event and register the unfurl domains under Event Subscriptions.

Two steps remain in both cases:

  1. Basic Information → App-Level Tokens → Generate Token and Scopes: add the connections:write scope and copy the xapp-… token. This is the app_token.
  2. OAuth & Permissions → Install to Workspace, then copy the Bot User OAuth Token (xoxb-…). This is the bot_token.

The App ID, Client ID, Client Secret and Signing Secret shown on the app’s pages are not used: with Socket Mode there is no inbound request to verify.

Adding a scope to an installed app only takes effect after Reinstall to Workspace on the OAuth & Permissions page.

Unfurl domains

Slack only reports links with an explicit http:// or https:// scheme whose host is one of the app’s unfurl domains, and the bridge additionally checks the host against its own unfurl_domains. The two lists must match. The domain does not have to be resolvable from anywhere: the link only supplies the image ID, and the image itself is fetched from screenshotter_base_url (or screenshotter_fetch_base_url, when set). A bare intranet name such as http://screen/abcdef works.

On a matching domain, any single-segment path is taken as an image ID (with an optional .png suffix), except the server’s own routes (upload, assets, static, thumb, admin, auth, favicon.ico).

Configuration file

Create config.toml. The minimum is:

screenshotter_base_url = "http://screenshotter.internal:8080"
unfurl_domains = ["screen.corp.example"]
bot_token_env_var = "SLACK_BOT_TOKEN"
app_token_env_var = "SLACK_APP_TOKEN"

Prefer the *_env_var forms, which keep the tokens out of the file; each token can be given either inline or through the environment, not both.

  • screenshotter_base_url (required): address of the server, without a trailing slash. The bridge fetches images from it and the card links to it, so a private address is fine as long as the bridge and the readers can reach it.
  • screenshotter_fetch_base_url: address the bridge itself uses to reach the server, without a trailing slash, when it differs from the one readers click through to — a Kubernetes Service, for instance, with screenshotter_base_url left as the address browsers open. Default: unset, i.e. fetch from screenshotter_base_url.
  • unfurl_domains (required): hostnames to render; must match the Slack app’s unfurl domains.
  • bot_token, app_token: the xoxb-… and xapp-… tokens; prefer bot_token_env_var and app_token_env_var.
  • max_dimension: longest side of the preview image, in pixels. Default: 1600; 0 sends the full resolution. Must be 0 or at least 300.
  • preview_wait: how long to wait for Slack to process the preview before showing the card. Default: 8s. With 0s the card appears immediately and the image fills in a few seconds later; the remote_files:read scope is then unnecessary.
  • request_timeout: timeout of each image fetch and Slack API call. Default: 30s.
  • max_concurrency: links handled in parallel; further events are dropped rather than queued. Default: 50.
  • max_image_workers: images decoded and resized in parallel, which bounds memory use. Default: 4.
  • max_links_per_message: links rendered from a single message. Default: 5, which is also Slack’s own limit.
  • block_external_users, allowed_usergroups: see below.
  • excluded_paths: extra first path segments never treated as image IDs, for a server with custom routes.
  • debug: verbose Socket Mode and API logging. Default: false. The output can include the bot token.

Preview images

The preview is a downscaled copy capped at max_dimension; readers get the full-resolution screenshot by clicking through. Slack rejects previews under 300 pixels on a side, so smaller screenshots are enlarged and padded with white rather than refused.

User restrictions

By default the bridge renders a recognised link for any user who can post it. Two optional settings narrow that:

  • block_external_users = true ignores links posted by users external to the workspace: Slack Connect participants, users from another team, and guest accounts. Requires the users:read scope.
  • allowed_usergroups = ["S0123ABCD"] only renders links posted by members of the listed user group IDs. Requires the usergroups:read scope.

Both fail closed: when Slack cannot be queried to evaluate the rule, the link is left alone. Lookups are cached for five minutes, so a membership change can take that long to apply.