Skip to content

Configuration

This page is the complete key-by-key reference. New to Notifycat? Read Configuration basics first — it shows the whole model (two files, one change loop) in two minutes; come back here to look up exact keys, defaults, and rules.

Notifycat is configured through a single config.yaml file that holds all non-secret settings. Secrets and infra-interpolation values live in environment variables (or .env for local development). For local development, .env is loaded automatically if the file exists. Outside Docker, the server and CLIs read .env from their working directory; a variable already set in the environment wins over the file.

The config file path defaults to ./config.yaml. Override with NOTIFYCAT_CONFIG_FILE. The Docker image default is /app/config.yaml.

The validation cache config.lock is written next to the config file. Override with NOTIFYCAT_LOCK_FILE when that folder is read-only — for example when the platform injects config.yaml as a file and the writable volume lives elsewhere.

Secrets should stay in environment variables or your deployment secret manager. Do not put real tokens in committed files. See config.example.yaml for a copy-paste starting point.

Secrets (environment variables only)

Which webhook secret and read token apply depends on git_provider: a github deployment uses the GITHUB_* variables, a bitbucket deployment uses the BITBUCKET_* variables. SLACK_BOT_TOKEN is always required; a deployment never needs the other provider's credentials.

Variable Required Notes
SLACK_BOT_TOKEN Required Slack bot token, usually starting with xoxb-.
SLACK_SIGNING_SECRET Optional Turns on Slack interactions: the server serves POST /webhook/slack/interactions and PR messages carry the Start review button. Find it under your Slack app's Basic Information → App Credentials → Signing Secret. If unset, notifycat stays outbound-only and posts no button.
GITHUB_WEBHOOK_SECRET Required for git_provider: github The secret configured on the GitHub webhook. Use a long random value; 32 characters or more is a good baseline.
GITHUB_TOKEN Optional (github) PAT used by notifycat-config validate and notifycat-doctor to read repository webhook config and by path routing to read a PR's changed files. Required scope: admin:repo_hook (or repo for private repositories). The server does not need this; if unset, the webhook-coverage and path-routing checks are skipped.
BITBUCKET_WEBHOOK_SECRET Required for git_provider: bitbucket The secret configured on the Bitbucket repository/workspace webhook. Bitbucket signs deliveries with X-Hub-Signature: sha256=<hmac>; an unsigned delivery (no secret configured) is rejected.
BITBUCKET_TOKEN Optional (bitbucket) Access token (Bearer) used by validate/doctor to read webhook config and by path routing to read changed files — exact GITHUB_TOKEN degradation parity. Least-privilege scopes: repository + pullrequest + webhook.
NOTIFYCAT_ADMIN_KEY_HASH Required when server.admin_addr is set bcrypt hash of the admin key, generated by notifycat-keygen. Must be wrapped in single quotes in .env so $ is not expanded.
BITBUCKET_AUTH_EMAIL Optional (bitbucket) Pair with BITBUCKET_TOKEN to switch to HTTP Basic with a scoped Atlassian API token (the Free-plan path for workspace-wildcard listing). Unset ⇒ Bearer.

The server and CLIs fail fast when SLACK_BOT_TOKEN is missing, or when the webhook secret required by the selected git_provider is missing (GITHUB_WEBHOOK_SECRET for git_provider: github, BITBUCKET_WEBHOOK_SECRET for git_provider: bitbucket). They also fail fast when server.admin_addr is set without NOTIFYCAT_ADMIN_KEY_HASH, when server.admin_addr equals server.addr, and when NOTIFYCAT_ADMIN_KEY_HASH is not a valid bcrypt hash.

GITHUB_TOKEN is also read by scripts/github-webhook-create.sh, but that script creates the webhook and only needs the Webhooks: Read and write permission on a fine-grained PAT. The validate/doctor reading path needs admin:repo_hook / repo. A single token that has both works everywhere; otherwise issue separate PATs.

Infra-interpolation (environment variables only)

These are not secrets but must live in .env because docker-compose and Caddy read them directly and cannot access config.yaml.

Variable Notes
DOMAIN Public DNS name. Caddy uses it as the virtual-host name and obtains a Let's Encrypt certificate. Required when using compose.yaml. Also set server.domain in config.yaml so notifycat-doctor can derive the webhook URL.
ACME_EMAIL Contact email for Let's Encrypt registration. Required when using compose.yaml — Caddy will fail to start without it.

config.yaml reference

git_provider

Key Default Notes
git_provider (required) The git host this deployment serves — github or bitbucket. An absent or unknown value fails startup with an error naming the key and pointing here.

git_provider selects which webhook secret is required (git_provider: github ⇒ GITHUB_WEBHOOK_SECRET, git_provider: bitbucket ⇒ BITBUCKET_WEBHOOK_SECRET; SLACK_BOT_TOKEN is always required regardless), which /webhook/... route the server registers (/webhook/github or /webhook/bitbucket), and which provider's validation probes run. A deployment serves exactly one provider and never needs the other's credentials. Switching git_provider against an existing database requires a fresh database; see Upgrading.

Bitbucket behavior notes

Two provider-specific behaviors are worth knowing when running git_provider: bitbucket:

  • Self-healing draft/ready transitions. Bitbucket has no distinct "opened as draft" vs "marked ready" events — both arrive as pullrequest:updated. Notifycat reads the PR's draft flag on each update: draft: true converts the tracked message to draft (deletes it), and draft: false on an OPEN PR treats it as ready-for-review. An updated event on a non-OPEN PR is ignored. This means a PR toggled draft↔ready re-announces correctly without a dedicated event.
  • The user-account-bot blind spot. Bot suppression keys on actor.type != "user" (so team and app_user actors are treated as bots). A bot that authenticates as an ordinary Bitbucket user account reports actor.type: "user" and is therefore indistinguishable from a human — its reviews are not suppressed even with reviews.ignore_ai_reviews: true. Prefer app-based integrations for reviewers you want suppressed.

server

Key Default Notes
server.addr :8080 HTTP listen address.
server.log_level info Supported values: debug, info, warn, error.
server.log_format text Use json for structured production logs.
server.domain (unset) Public domain name. notifycat-doctor derives https://$domain/webhook/github from this, and the server builds the agent invite redeem_command and the NOTIFYCAT_URL line of the redeem output from it. When unset, the redeem_command uses $NOTIFYCAT_URL instead, and the redeem output has no NOTIFYCAT_URL line.
server.admin_addr (unset) Listen address of the private admin server for agent invites and tokens. Must differ from server.addr. Plain HTTP; never publish it. Requires NOTIFYCAT_ADMIN_KEY_HASH.

agent_api

Key Default Notes
agent_api.enabled false Serves the /api/v1/ agent routes. When on, notifycat also records the open PRs of configured repositories for the agent feed. Off means no recording, no /api/v1/ routes and no admin server. Invites can only be created when server.admin_addr is set too; with the API on and no admin_addr, notifycat logs WARN admin server not started and existing tokens keep working. Turning the API off hides the routes but does not revoke tokens; turning it on again restores them. To revoke a token, use DELETE /admin/v1/tokens/{id}. Each start with the API off removes every stored feed PR, because nothing records a close while it is off. So turning the API on again starts the feed empty, like a first enable; subscriptions to mapped repositories stay. With the API on, each start removes the stored PRs of repositories that left mappings:. With the API on or off, each start removes the subscriptions to repositories that left mappings:; subscribe again if one is mapped again.

database

Key Default Notes
database.url file:./data/notifycat.db SQLite path or file: DSN. Stores the per-PR Slack message timestamps. Mappings live in config.yaml, not the database.

For Docker, set database.url in your mounted config.yaml. The recommended value is file:/app/notifycat.db (the historical image path) or file:/data/notifycat.db if you mount a /data volume for persistence.

slack

Key Default Notes
slack.base_url https://slack.com Override for tests only.
slack.reactions.enabled true Turns reaction updates on or off.
slack.reactions.new_pr eyes Added when a PR is opened. Doubles as the leading emoji of the new-PR message.
slack.reactions.merged_pr twisted_rightwards_arrows Added when a PR is merged.
slack.reactions.closed_pr x Added when a PR is closed without merge.
slack.reactions.approved white_check_mark Added when a review approves the PR.
slack.reactions.commented speech_balloon Added when a review comments on the PR.
slack.reactions.request_change exclamation Added when a review requests changes.
slack.reactions.bot_review robot_face Distinct marker added alongside the normal reaction when a bot reviewer's activity is not suppressed (i.e. reviews.ignore_ai_reviews: false). Lets bot reviews stay visible but recognisable. Set empty to keep bot reviews indistinguishable from human ones. Mutually exclusive with suppression: when reviews.ignore_ai_reviews: true the bot reaction is skipped entirely, so this marker never appears.

Use Slack emoji names without surrounding colons. For example, set approved: shipit, not :shipit:.

github

Key Default Notes
github.base_url https://api.github.com Override for GitHub Enterprise or tests. Used when git_provider: github.

bitbucket

Key Default Notes
bitbucket.base_url https://api.bitbucket.org/2.0 Override for tests. Used when git_provider: bitbucket.

cleanup

Key Default Notes
cleanup.message_ttl_days 30 Days a slack_messages row may go without an update before the in-process cleanup removes it. Must be > 0. The cleanup runs once at startup and then once every 24 hours; it only deletes the DB row, never the actual Slack message.
cleanup.delete_on_close false When true, a merged or declined PR has its Slack message deleted instead of updated with the [Merged]/[Closed] tag, and its database row is dropped. Every channel the PR fanned out to loses its message; no closing reaction is added. Overridable per repository tier. Deletion is permanent — Slack has no undo. See Remove messages on close.

reviews

Key Default Notes
reviews.ignore_ai_reviews false When true, suppress reactions.add for any review event whose sender.type == "Bot" — Copilot, Claude, Codex, dependabot, github-actions, release-please, and any other GitHub App or legacy bot account. Detection is intentionally coarse: notifycat does not distinguish AI reviewers from scripted bots. See Reactions & bot reviews for the trade-off and failure-mode guide.
reviews.dependabot_format true When true, PRs opened by dependabot[bot] or renovate[bot] post a compact Slack message instead of the standard "please review" format: :package: <bot> bumped <link> for routine bumps, or :rotating_light: <bot> security update <link> when the PR body shows a security advisory. Set false to render those PRs with the standard format. See Reactions & bot reviews → Dependabot and Renovate for detection details.

digest

Key Default Notes
digest.enabled false Turns the stuck-PR digest on or off. The feature is opt-in: omitting this section — or setting only schedule:/timezone: without enabled: true — leaves the digest off.
digest.schedule 0 9 * * * Standard 5-field cron expression, evaluated in digest.timezone. An invalid expression fails server startup.
digest.timezone UTC IANA timezone name (e.g. Europe/Kyiv) the schedule and the "stuck since before today" cutoff are evaluated in. Absent/empty means UTC. An unrecognized zone fails server startup. Global only — a per-repository digest: override may not set it.
digest.country (none) ISO 3166-1 alpha-2 country code (e.g. DE) selecting the public-holiday calendar the digest skips, evaluated in digest.timezone. Case-insensitive. Absent/empty means no holidays are skipped — weekends still are — and the server warns once at boot. An unrecognized code does not fail startup: the server warns, lists the supported set, and runs weekends-only. Global only — a per-repository digest: override may not set it. See Weekends and holidays for the supported set and its limits.

Weekends are always skipped and there is no key for it: Saturday and Sunday never get a digest, whatever digest.schedule says.

digest.timezone and digest.country also drive the sends of the scheduled notification strategy, even when digest.enabled is false.

mappings

Routing from repositories to Slack channels lives in the mappings: section of config.yaml. The per-repository-tier schema (0.18+) organizes each org into named repository tiers plus an optional "*" catch-all tier. Each tier sets its own channel and optional mentions; repository tiers inherit from the "*" tier. Behavioral settings (reactions, reviews, digest) can also be overridden per-repository tier; when omitted they inherit from the "*" tier or fall back to the global values. A tier's notifications: block picks the notification strategy: per-event (default) or scheduled. See Mappings for the full schema reference and examples.

Config CLI

The file is operated via the notifycat-config binary — list prints the parsed config, validate checks each entry end-to-end against Slack and the git host, with results cached in config.lock so only changed entries revalidate. The server runs the same validation at boot and refuses to start on failure. Commands, checks, and the remediation table: CLI → notifycat-config.