---
title: "Claude Code Notifications on Your Phone via Hooks"
description: "Get a phone alert when Claude Code needs permission or finishes a task. Copy-paste hook config using the Notification and Stop hooks and a webhook."
date: 2026-10-02
author: Nooc
authorAvatarLink: /images/avatars/nooc.webp
authorLink: https://nooc.me
tags:
  - Claude Code
  - AI coding agents
  - hooks
  - phone notifications
  - webhooks
  - developer productivity
---

# Claude Code Notifications on Your Phone: Know When It Needs You or Is Done

Claude Code stops in two situations that matter: it is blocked waiting for you (a permission prompt, a question), or it has finished. If you are not looking at the terminal, both look identical — nothing happens, and the session sits idle until you come back. The fix is a hook: a few lines in `settings.json` that send a webhook to your phone the moment either one occurs.

This guide gives you the config to copy, explains which hook event to use for which situation, and covers the details that make it work reliably: reading the hook input, shell quoting, and not drowning yourself in notifications.

## Which hook for which situation

Claude Code runs hooks on lifecycle events. Two of them cover this problem ([hooks reference](https://code.claude.com/docs/en/hooks)):

| You want to know when… | Hook event | Matcher / filter |
| --- | --- | --- |
| Claude is waiting for a permission decision | `Notification` | `permission_prompt` |
| Claude has been idle and is waiting for your input | `Notification` | `idle_prompt` |
| Claude finished responding | `Stop` | none needed |

The `Notification` hook's `matcher` filters on the notification type, so you can choose exactly which notifications reach your phone. The input arrives as JSON on stdin and includes `notification_type`, `message`, `cwd`, and `session_id`. `Notification` hooks cannot block or change anything — they are observers, which is what you want here.

`Stop` input includes `last_assistant_message`, the final text Claude produced. That is a free summary line for the notification.

## What you need

- Echobell on your phone ([App Store](https://apps.apple.com/app/apple-store/id6743597198?pt=128151925&ct=blog-claude-code-phone-notifications-en&mt=8) / [Google Play](https://play.google.com/store/apps/details?id=one.echobell.echobellandroid))
- `curl` and `jq` on the machine that runs Claude Code
- Two minutes

## Step 1 — Create two channels

Resist sending everything to one channel. In Echobell, create:

- **Claude Blocked**, type **Time Sensitive** or **Calling** — for permission prompts, where the agent is stalled and every minute is wasted compute and wall-clock time
- **Claude Done**, type **Normal** — an ordinary push, readable whenever

Copy each channel's webhook URL. It looks like `https://hook.echobell.one/t/<channel-token>`. Treat it like a password: anyone with it can ring your phone.

Store both as environment variables so they never land in a committed config file:

```bash
export ECHOBELL_CLAUDE_BLOCKED_URL="https://hook.echobell.one/t/<blocked-token>"
export ECHOBELL_CLAUDE_DONE_URL="https://hook.echobell.one/t/<done-token>"
```

Put those in your shell profile so Claude Code inherits them.

## Step 2 — Add the hooks

Add this to `~/.claude/settings.json` to apply to every project, or to `.claude/settings.local.json` for one repository:

```json
{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{title: \"Claude needs permission\", body: .message, project: (.cwd | split(\"/\") | last)}' | curl -sS -m 10 -X POST -H 'Content-Type: application/json' -d @- \"$ECHOBELL_CLAUDE_BLOCKED_URL\""
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{title: \"Claude finished\", body: (.last_assistant_message // \"Done\" | .[0:200]), project: (.cwd | split(\"/\") | last)}' | curl -sS -m 10 -X POST -H 'Content-Type: application/json' -d @- \"$ECHOBELL_CLAUDE_DONE_URL\""
          }
        ]
      }
    ]
  }
}
```

What each piece does:

- `jq -c '{...}'` reads the hook's JSON from stdin and builds a small payload, so Echobell receives exactly the fields you want rather than the whole hook document.
- `.cwd | split("/") | last` turns `/home/me/code/api` into `api`, so the notification says which project is asking.
- `.[0:200]` truncates the final message. Notification bodies are capped at 1500 bytes by the Echobell worker, and a lock screen shows far less anyway.
- `-m 10` gives `curl` a 10-second timeout so a network problem never leaves Claude Code waiting on your hook.

Echobell renders the keys you send through the channel's templates, and a new channel has no template that reads them — set them in the channel settings: title template `{{title}}`, body template `{{body}}`. Add `{{project}}` to either one if you want the project name shown, for example `{{title}} ({{project}})`.

## Step 3 — Test it without waiting for a real prompt

Run the same pipeline by hand with a fake hook payload:

```bash
echo '{"message":"Claude needs your permission to use Bash","cwd":"/home/me/code/api","notification_type":"permission_prompt"}' \
  | jq -c '{title: "Claude needs permission", body: .message, project: (.cwd | split("/") | last)}' \
  | curl -sS -m 10 -X POST -H 'Content-Type: application/json' -d @- "$ECHOBELL_CLAUDE_BLOCKED_URL"
```

If your phone lights up, the Echobell side works and any remaining problem is in how Claude Code reads the config. In Claude Code, run `/hooks` to confirm the hooks were loaded; settings changes made while a session is running may need the hooks menu or a restart to take effect.

## Step 4 — Make the blocked channel ring

A push notification loses to Do Not Disturb. A **Calling** channel behaves like an incoming call and gets through iOS Focus Mode, which matters most when you start a long task and walk away for the evening. If you would only want to be interrupted for some prompts, filter with an Echobell channel condition on the variables you send, for example `project == "api"`, rather than editing the hook. For the full variable list see the [conditions reference](/en/docs/conditions).

## Avoiding notification overload

A busy session can fire many `Stop` events — every response ends with one. Three ways to keep the signal:

- **Do not send `Stop` to a ringing channel.** Completion is not urgent; keep it on Normal.
- **Use `idle_prompt` instead of `Stop`** if you only want to hear when Claude has stopped and is waiting for you, rather than after every response.
- **Narrow by project** with a condition, so a scratch repository does not notify you at all.

## Running Claude Code remotely or in CI

The same approach works in cloud or remote environments, because the hook is just an outbound HTTPS request. Nothing has to be reachable from the internet. Set the two environment variables in the environment's secrets instead of a shell profile, and the notification reaches your phone wherever the session runs.

## Troubleshooting

**Nothing arrives, and the manual test also fails.** Check the URL variable is set in the shell that launches Claude Code: `echo "$ECHOBELL_CLAUDE_BLOCKED_URL"`. An empty variable makes `curl` fail silently.

**The manual test works but the hook does not fire.** Confirm the matcher: `permission_prompt` and `idle_prompt` are distinct, and a matcher that does not match simply never runs. Run `/hooks` and check the JSON is valid — a trailing comma disables the whole settings file's hooks.

**The notification body is blank.** The `jq` filter referenced a field that does not exist for that event. `message` exists on `Notification` input; `last_assistant_message` exists on `Stop` input. Use `//` to supply a default, as in the config above.

**It fires too often.** See the section above — move from `Stop` to `idle_prompt`, or add a condition.

## Frequently asked questions

### How do I get a notification when Claude Code needs permission?

Add a `Notification` hook with the matcher `permission_prompt` that sends a webhook to a channel. Echobell turns that request into a push or a call-style alert on your phone.

### How do I know when Claude Code finishes a task?

Add a `Stop` hook. It runs when Claude finishes responding, and its input includes `last_assistant_message`, which you can use as the notification body.

### Can the notification break through Do Not Disturb?

Yes, if the receiving channel is a Calling channel. It behaves like an incoming call, which iOS Focus Mode lets through. A normal push from any app does not.

### Does this work in Claude Code on the web or in remote sessions?

Yes. The hook only makes an outbound HTTPS request, so it works anywhere the session can reach the internet. Provide the channel URLs through the environment's secrets.

### Is the webhook URL safe to put in settings.json?

Prefer an environment variable, as shown above, so the token is not committed to a repository. If it leaks, create a new channel and retire the old one.

## Wrap-up

Two channels, two hooks, and a `jq | curl` pipeline: a **Calling** or Time Sensitive channel for `permission_prompt`, a Normal channel for `Stop`. After that you can start a long agent task, leave the room, and be pulled back only when the agent genuinely needs you.

[Download Echobell for iPhone](https://apps.apple.com/app/apple-store/id6743597198?pt=128151925&ct=blog-claude-code-phone-notifications-en&mt=8) or [get it on Google Play](https://play.google.com/store/apps/details?id=one.echobell.echobellandroid), then run the manual test above before trusting it with a long task.

## Related

- [Turning agent approval gates into phone calls](/en/blog/ai-agent-human-in-the-loop-alerts)
- [Get notified when a long terminal command finishes](/en/blog/notify-when-terminal-command-finishes)
- [Webhook integration guide](/en/docs/webhook)
- [Channel conditions reference](/en/docs/conditions)
- [Bypassing iOS Focus Mode for critical alerts](/en/blog/how-to-bypass-ios-focus-mode-for-critical-alerts)
