To get Claude Code push notifications with ntfy, install the ntfy app on your phone, subscribe to a topic with a hard-to-guess name, and add a Claude Code hook that runs curl -d "message" https://ntfy.sh/your-topic. A Notification hook tells you when Claude needs a permission or is waiting for you, and a Stop hook tells you the moment a turn ends. It is free, takes about ten minutes, and works on Mac, Windows and Linux.
01Step 1: pick a topic and subscribe
ntfy has no sign-up. A topic is created the first time someone publishes or subscribes to it, and anyone who knows the name can read it. ntfy's own documentation says the topic is essentially a password, so make it long and random, for example claude-7f3k9q2xw4mz. Topic names can use letters, numbers, underscores and dashes, up to 64 characters.
Install the ntfy app (App Store, Google Play or F-Droid), tap +, and subscribe to your topic on ntfy.sh.
Check it works from a terminal:
curl -H "Title: Test" -d "Hello from my computer" https://ntfy.sh/claude-7f3k9q2xw4mz
The phone should buzz within a second or two. On Windows, curl.exe ships with Windows 10 and 11 and accepts the same line.
02Step 2: a hook script (Mac and Linux)
Claude Code passes each hook a JSON object on standard input. Both events share fields such as cwd and hook_event_name. Notification adds message and notification_type, and Stop adds last_assistant_message, the text of Claude's final reply. This script turns either into a readable push. It needs jq (brew install jq, or apt install jq on Debian and Ubuntu).
Save it as ~/.claude/ntfy-notify.sh and run chmod +x ~/.claude/ntfy-notify.sh:
#!/bin/sh
TOPIC_URL="https://ntfy.sh/claude-7f3k9q2xw4mz"
input=$(cat)
event=$(printf '%s' "$input" | jq -r '.hook_event_name')
project=$(basename "$(printf '%s' "$input" | jq -r '.cwd')")
if [ "$event" = "Stop" ]; then
title="Claude finished: $project"
body=$(printf '%s' "$input" | jq -r '(.last_assistant_message // "Done") | .[0:300]')
priority=3
else
title="Claude needs you: $project"
body=$(printf '%s' "$input" | jq -r '.message // "Needs your attention"')
priority=4
fi
curl -s -H "Title: $title" -H "Priority: $priority" -H "Tags: robot" \
-d "$body" "$TOPIC_URL" > /dev/null
exit 0
Priority runs from 1 (min) to 5 (urgent). Tags adds an emoji to the notification; robot shows a robot face.
03Step 3: wire it into settings.json
Add the hooks to ~/.claude/settings.json. If the file already has a hooks object, add these events inside it rather than replacing it.
{
"hooks": {
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{ "type": "command", "command": "~/.claude/ntfy-notify.sh" }
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "~/.claude/ntfy-notify.sh", "async": true }
]
}
]
}
}
"async": true lets the Stop hook send in the background, so a slow network never delays Claude. Start a new Claude Code session, because hooks load when a session starts.
Stop or idle_prompt?
Stop fires the moment Claude finishes, even if you are sitting at the keyboard. If that is too much, drop the Stop block and use the matcher permission_prompt|idle_prompt instead. Anthropic's documentation says idle_prompt fires about 60 seconds after Claude finishes, only if you have not typed since, and permission_prompt fires once a permission prompt has waited about six seconds without a keystroke. That gives you a rough "only when I am away" filter for free. The Notification hook guide and the Stop hook guide cover every field and timing.
04Windows (PowerShell)
On Windows, save this as %USERPROFILE%\.claude\ntfy-notify.ps1. It needs nothing extra installed:
$in = [Console]::In.ReadToEnd() | ConvertFrom-Json
$project = Split-Path $in.cwd -Leaf
if ($in.hook_event_name -eq 'Stop') {
$title = "Claude finished: $project"
$body = [string]$in.last_assistant_message
if ($body.Length -gt 300) { $body = $body.Substring(0, 300) }
$priority = '3'
} else {
$title = "Claude needs you: $project"
$body = [string]$in.message
$priority = '4'
}
Invoke-RestMethod -Method Post -Uri 'https://ntfy.sh/claude-7f3k9q2xw4mz' `
-Headers @{ Title = $title; Priority = $priority } `
-Body ([Text.Encoding]::UTF8.GetBytes($body)) | Out-Null
Then use this hook entry in place of the command lines above:
{
"type": "command",
"shell": "powershell",
"command": "powershell -NoProfile -ExecutionPolicy Bypass -File \"$HOME\\.claude\\ntfy-notify.ps1\""
}
If you run Claude Code inside WSL, use the Mac and Linux script instead; WSL is Linux as far as the hook is concerned.
05Keeping it private
Everything in the push goes through the ntfy server, and on ntfy.sh anyone with the topic name can read it. The Stop message above includes the start of Claude's reply, which can contain code or file names. Three ways to tighten this:
- Send less. Replace the
Stopbody with a fixed string such as"Done"and keep only the project name in the title. - Use an access token. ntfy accepts
-H "Authorization: Bearer tk_..."on any publish. On your own server, setauth-default-access: "deny-all", create a user withntfy user add, and create a token withntfy token add. - Host your own server. ntfy is self-hostable. For instant delivery to the iPhone app, ntfy's docs say to set
upstream-base-url: "https://ntfy.sh", which forwards only a message ID and a topic hash to ntfy.sh so it can wake the phone; the app then fetches the message from your server.
If Claude Code runs on a remote server, the same hook works there unchanged, which makes ntfy a good fit for notifications over SSH.
06Where Claude Code Notifier fits
For one-way alerts, the setup above is a complete answer and we would not talk you out of it. It stops at telling you. When the push says Claude needs a permission, the session still waits until you get back to the keyboard.
ntfy does support action buttons that make an HTTP request, so you could build a reply path yourself: a PermissionRequest hook that publishes the request, waits for a response on a second topic, and returns a decision. Some open source projects do exactly that (see the comparison).
Claude Code Notifier packages that path. With a phone paired, you can allow once, allow for the session or deny a permission, pick an answer when Claude asks a question, and send the next instruction after Claude finishes, all from the phone. It also checks presence: if you typed in the last 30 seconds, the terminal asks as usual and nothing waits on the phone. If you are away, the terminal waits for your answer up to a ceiling you set, and stops waiting the moment you touch the keyboard. ccn runs on Mac, Windows and Linux; see the ccn page and pricing for the phone apps.
07FAQ
Is ntfy free for Claude Code notifications?
Publishing to and subscribing on ntfy.sh needs no account, and the server is open to anyone. ntfy also offers a paid tier and can be self-hosted. Check ntfy's own site for current limits.
Why does my ntfy hook not fire?
Start a new Claude Code session after editing settings.json, since hooks load at startup. Then run the curl test line by hand, and pipe sample JSON into the script, for example echo '{"hook_event_name":"Stop","cwd":"/tmp/x"}' | ~/.claude/ntfy-notify.sh. The troubleshooting checklist covers the rest.
Can I reply to Claude Code from an ntfy notification?
Not with a plain publish. ntfy's HTTP action buttons can send a request, but you need a hook that waits for the answer and turns it into a decision Claude Code understands.
Do I get a push for every notification type?
Only for the matchers you list. Omit matcher on the Notification entry to receive every type, including authentication and MCP dialogs.