AlphornAlphorn Docs

Claude Code

Get notified when Claude Code finishes, waits for input, or needs permission. Claude Code hooks (Stop, Notification) that send to Slack, Telegram, or SMS.

Claude Code runs shell commands on lifecycle events through hooks. Two events matter for notifications:

  • Notification - Claude needs you: a permission prompt, or it has been waiting for input.
  • Stop - Claude finished responding. Fires after every response, so use it if you want a message for each completed turn.

1. Create the hook script

The hook receives the event as JSON on stdin. This script turns it into an Alphorn notification:

~/.claude/hooks/alphorn-notify.sh
#!/usr/bin/env bash
# Forwards Claude Code Stop and Notification hook events to Alphorn.
jq -c '{
  title: ("Claude Code - " + (.cwd | split("/") | last)),
  message: (if .hook_event_name == "Stop"
            then (.last_assistant_message // "Finished")
            else (.message // "Needs your attention") end),
  priority: (if .notification_type == "permission_prompt" then 4
             elif .hook_event_name == "Stop" then 2 else 3 end),
  tags: ["ai-agent", "claude-code",
         (if .hook_event_name == "Stop" then "completed"
          elif .notification_type == "permission_prompt" then "permission"
          else "waiting" end)]
}' | curl -fsS --max-time 5 -X POST "$ALPHORN_WEBHOOK_URL" \
  -H "Authorization: Bearer $ALPHORN_API_KEY" \
  -H "Content-Type: application/json" \
  -d @- > /dev/null
exit 0
chmod +x ~/.claude/hooks/alphorn-notify.sh

The script needs jq and curl, and reads ALPHORN_WEBHOOK_URL and ALPHORN_API_KEY from the environment (set below). It always exits 0: on a Stop hook, exit code 2 tells Claude to keep working instead of stopping.

2. Register the hooks

Add the hooks to ~/.claude/settings.json (all projects), .claude/settings.json (one project, committed), or .claude/settings.local.json (one project, not committed):

~/.claude/settings.json
{
  "env": {
    "ALPHORN_WEBHOOK_URL": "https://app.alphorn.dev/n/V1StGXR8_Z5jdHi6B-myT",
    "ALPHORN_API_KEY": "alp_..."
  },
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt|idle_prompt",
        "hooks": [
          { "type": "command", "command": "~/.claude/hooks/alphorn-notify.sh" }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          { "type": "command", "command": "~/.claude/hooks/alphorn-notify.sh", "async": true }
        ]
      }
    ]
  }
}

Leave out the Stop block if you only want to hear from Claude when it is blocked. "async": true runs the hook in the background so Claude does not wait for the HTTP request. Instead of the env block, you can export ALPHORN_WEBHOOK_URL and ALPHORN_API_KEY in your shell profile. Keep the key out of .claude/settings.json if you commit that file.

Notification matchers

The matcher on Notification is matched against the notification type:

MatcherWhen it fires
permission_promptClaude needs permission to use a tool
idle_promptClaude is waiting for your input
elicitation_dialogAn MCP server is asking for input
auth_successAuthentication completed
(empty string)All notification types

Stop does not support matchers; a matcher there is ignored.

Event fields

Fields from the hook input you can use in the jq program:

EventFields
Allsession_id, cwd, hook_event_name, transcript_path
Notificationmessage, title, notification_type
Stoplast_assistant_message (Claude's final response text)

Routing

Route by the tags the script sets, for example:

ChannelFilter
Telegram or SMSTags has any of permission
SlackTags has any of claude-code

See AI Agent Notifications for the shared conventions, an AGENTS.md snippet, and an Agent Skill that lets Claude send notifications on its own.

On this page