KelrikDocs
Documentation

Kelrik docs

Everything you need to get going with Kelrik Terminal and its integrations.

Getting started

  1. Create a Kelrik account. Your password never leaves your browser: it derives the keys that encrypt your vault. Every new account includes three months of Pro.
  2. Open the web app in the same tab and add your first host, or import an ~/.ssh/config.
  3. Connect a Slack, Teams, Discord, Telegram or ntfy destination so your servers can reach you.

Use the apps with this account

The Mac, iPhone and iPad apps (in beta) sign in to any Kelrik server. To use your account from this site, open Settings → Account → Kelrik server in the app and enter:

https://kelrik.app/api/v1

Then sign in with your email and password. Your hosts, keys and settings sync end to end encrypted between all of your devices.

The web app runs SSH through Kelrik's gateway, because browsers can't open SSH connections themselves. The gateway only connects to public hosts; to reach servers on a private network, use the Mac or iOS app.

Your account and password

Integrations

Integrations send a short message to the places you already watch when something on a server needs you: a long job ended, a deploy failed, a backup didn't run, or Claude Code or Codex finished or is waiting for permission.

There are two parts. Destinations are where messages go. Notify keys are private URLs your hosts and tools send messages to. Each key can send to every destination or just some. Integrations are part of Pro, which every new account has free for three months.

Destinations

Slack

  1. Open api.slack.com/apps → Create New App → From scratch, and pick your workspace.
  2. Open Incoming Webhooks, switch it on, then Add New Webhook and choose a channel.
  3. Copy the URL (it starts with https://hooks.slack.com/services/) into Kelrik.

Workflow Builder trigger URLs (/triggers/) expect their own format and aren't supported.

Microsoft Teams

  1. In Teams, open Workflows, from the channel's ••• menu or the Workflows app.
  2. Choose the template Send webhook alerts to a channel, pick the team and channel, and Save.
  3. Copy the webhook URL it shows into Kelrik. It's on logic.azure.com or powerplatform.com.

Kelrik posts an Adaptive Card. The old Office 365 connector URLs (webhook.office.com) stopped working in May 2026 when Microsoft retired them.

Discord

  1. Open the channel's Edit Channel → Integrations → Webhooks → New Webhook.
  2. Choose Copy Webhook URL and paste it into Kelrik.

Messages arrive as embeds from “Kelrik”. Mentions such as @everyone are always disabled.

Telegram

  1. Message @BotFather, send /newbot, and copy the token.
  2. Open your new bot and press Start, or add it to a group or channel.
  3. Paste the token into Kelrik and press Find my chats, then pick the chat.

Messages come from your own bot, so they stay between you and Telegram's servers. Kelrik stores the token encrypted.

ntfy

  1. Install ntfy on iPhone or Android, or open its web app.
  2. Subscribe to the topic Kelrik suggests (it's random, because anyone who knows a topic on ntfy.sh can read it).
  3. For your own ntfy server or a protected topic, enter the server address and an access token (tk_…).

Webhooks

Kelrik POSTs JSON to your https:// endpoint and signs it so you can check it came from Kelrik. See the format and how to verify it.

Webhooks can only reach public addresses.

Notify keys

Create a key on the integrations page for each host or tool, named so you'll recognise it (“prod-api-1”, “Claude Code on my laptop”). Its URL looks like this:

https://kelrik.app/n/kn_YOUR_KEY

Anyone with the URL can send you messages, so treat it like a password. If one leaks, press New URL: the old one stops working right away. The quickest test:

curl -d "Hello from $(hostname)" https://kelrik.app/n/kn_YOUR_KEY

For everyday use, add the kn helper to your shell. kn "message" sends a line; kn -- command runs a command and pings you when it ends, with its exit status and how long it took:

# ~/.bashrc or ~/.zshrc
kn() {
  local url='https://kelrik.app/n/kn_YOUR_KEY'
  if [ "$1" = "--" ]; then
    shift
    local start=$(date +%s) rc
    "$@"; rc=$?
    curl -fsS -m 10 -o /dev/null \
      --data-urlencode "title=$([ $rc -eq 0 ] && echo Done || echo Failed): $*" \
      --data-urlencode "text=exit $rc after $(( $(date +%s) - start ))s" \
      --data-urlencode "level=$([ $rc -eq 0 ] && echo success || echo error)" \
      --data-urlencode "host=$(hostname)" "$url"
    return $rc
  fi
  curl -fsS -m 10 -o /dev/null --data-urlencode "text=$*" --data-urlencode "host=$(hostname)" "$url"
}

Cron jobs can report only their failures: 0 3 * * * /usr/local/bin/backup.sh || curl -fsS -d "Backup failed on $(hostname)" https://kelrik.app/n/kn_YOUR_KEY

Claude Code and Codex

Coding agents running on your servers can tell you when they finish a turn or need a decision, so you can close the laptop and still know when to come back. The integrations page shows these setups with your real URL filled in.

Claude Code: add to ~/.claude/settings.json on the host (merge with any hooks you already have). Notification fires when Claude needs a permission or is waiting for you; Stop fires when it finishes a turn.

{
  "hooks": {
    "Notification": [
      { "hooks": [{ "type": "command", "command": "curl -fsS -m 10 -o /dev/null -H 'Content-Type: application/json' -H \"X-Host: $(hostname)\" --data-binary @- 'https://kelrik.app/n/kn_YOUR_KEY' || true" }] }
    ],
    "Stop": [
      { "hooks": [{ "type": "command", "command": "curl -fsS -m 10 -o /dev/null -H 'Content-Type: application/json' -H \"X-Host: $(hostname)\" --data-binary @- 'https://kelrik.app/n/kn_YOUR_KEY' || true" }] }
    ]
  }
}

Codex: add this line to ~/.codex/config.toml, above any [section]. Codex runs it after each turn with a JSON summary, and Kelrik turns it into “Codex finished” with the start of its last reply.

notify = ["bash", "-c", 'curl -fsS -m 10 -o /dev/null -H "Content-Type: application/json" -H "X-Host: $(hostname)" --data-binary "$1" https://kelrik.app/n/kn_YOUR_KEY || true', "kelrik-notify"]

Agent messages are shortened to 400 characters. They pass through Kelrik's server to reach your chat app, so leave these hooks off on hosts whose output must never leave them.

Notify API

Send a POST (or PUT) to your key's URL. The key can also go in a header instead of the path, which keeps it out of URLs and logs:

curl -H "Authorization: Bearer kn_YOUR_KEY" -d "Deploy finished" https://kelrik.app/n

Kelrik understands these bodies:

BodyWhat happens
Plain textA short single line becomes the title; anything longer becomes the message.
JSONtitle, text (or message), level, host, url.
Form fieldsThe same names: curl --data-urlencode "title=…" --data-urlencode "text=…".
Claude Code hook inputRecognised automatically: “Claude Code needs you” or “Claude Code finished”.
Codex notify JSONRecognised automatically: “Codex finished” with the last reply.

Headers can fill in what the body leaves out: Title, X-Level (or ntfy-style Priority 1–5), X-Host and Click (a link). level is one of info, success, warning or error, and shows as a coloured LED.

AnswerMeaning
202Accepted: {"ok":true,"id":"evt_…","queued":3}. Delivery happens right after; results appear on the integrations page.
404Unknown key: it was deleted or replaced with a new URL.
402Integrations are paused because the account's Pro trial or plan ended.
413The body is over 16 KB.
429Too many messages; wait the number of seconds in Retry-After.

Verifying webhooks

Webhook destinations receive this JSON:

{
  "type": "kelrik.notification",
  "id": "evt_9mQ2xY7rTn0bC1dE",
  "event": "agent.needs_input",
  "level": "warning",
  "title": "Claude Code needs you",
  "text": "Claude needs your permission to use Bash",
  "host": "prod-api-1",
  "key": "prod-api-1",
  "dir": "api",
  "url": null,
  "source": "claude-code",
  "at": "2026-10-08T14:02:11.000Z"
}

event is message, agent.needs_input, agent.finished, agent.notification, test or paused. Each request has an X-Kelrik-Signature: t=<unix time>,v1=<hex> header: an HMAC-SHA256, keyed with your destination's signing secret, of the timestamp, a dot, and the raw body. Check it, and reject old timestamps:

// Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyKelrik(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const ts = Number(parts.t);
  if (!ts || Math.abs(Date.now() / 1000 - ts) > toleranceSec) return false;
  const want = createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest();
  const got = Buffer.from(parts.v1 || "", "hex");
  return got.length === want.length && timingSafeEqual(got, want);
}
# Python
import hashlib, hmac, time

def verify_kelrik(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    ts = int(parts.get("t", "0"))
    if abs(time.time() - ts) > tolerance:
        return False
    want = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(want, parts.get("v1", ""))

Limits

Troubleshooting

The test message didn't arrive
The integrations page shows the service's answer next to the destination. “Not found” usually means the webhook was deleted on the other side; remove it in Kelrik and connect a fresh one.
curl says Unknown notify key
The key was deleted or given a new URL. Copy the current URL from the integrations page.
Claude Code doesn't notify
Check that curl is installed on the host and that the hooks are in the settings file of the user running Claude. Run the quick test from the same host to rule out the network.
Messages stopped
If Pro has ended, Kelrik posts one “notifications are paused” message and the API answers 402. Your setup is kept.