Claude Code Stop Hook: Get a WhatsApp Message When It Finishes
Copy one shell script and one settings entry, and Claude Code sends a WhatsApp message to your own number each time it finishes a turn. The script and its request were tested against a local mock server.
Daniel Roth5 min read

On this page
Short answer: register a Stop hook in your Claude Code settings that runs a script, and have the script POST one text message to https://api.blueticks.co/v1/messages/<your chat id>. Claude Code then messages your phone every time it finishes a turn.
This page is the working example. For the setup through MCP, read Connect Claude Code to WhatsApp. For the wider picture (approvals, CI, what the connection needs while you are away), read Claude Code on WhatsApp.
What you need
- A Blueticks account with your WhatsApp number connected, and a
bt_live_key from the Blueticks API page. Blueticks shows the key once. jqandcurlon your machine.- Your own WhatsApp chat id, for example
15551234567@c.us(your number, then@c.us).
Step 1: the script
Save this as ~/.claude/hooks/whatsapp-done.sh and run chmod +x on it.
#!/usr/bin/env bash
# Stop hook: send one WhatsApp message to your own number when Claude Code finishes a turn.
# Needs jq, curl and two environment variables:
# BLUETICKS_API_KEY your bt_live_ key
# BLUETICKS_TO your own chat id, for example 15551234567@c.us
KEY="${BLUETICKS_API_KEY:-}"
TO="${BLUETICKS_TO:-}"
[ -n "$KEY" ] && [ -n "$TO" ] || exit 0 # not configured: do nothing
body=$(jq '{
type: "text",
text: ("Claude Code finished in " + ((.cwd // "your project") | split("/") | last) + ": "
+ ((.last_assistant_message // "Finished.") | .[0:300]))
}') || exit 0
curl -sS --max-time 10 -X POST \
"${BLUETICKS_API_URL:-https://api.blueticks.co}/v1/messages/${TO}" \
-H "Authorization: Bearer ${KEY}" \
-H "Content-Type: application/json" \
-d "$body" > /dev/null || true
exit 0What it does, line by line:
- Claude Code passes the hook a JSON object on stdin. The script reads
cwd(to name the project) andlast_assistant_message(Claude's final text), per the Claude Code hooks reference. It keeps the first 300 characters. - The request is
POST /v1/messages/{chatId}with a bearer key. Per the Blueticks OpenAPI spec, it sends immediately to the chat in the path, and the key needs themessages:writescope. - It exits 0 in every path: when either variable is unset it does nothing, and a failed or unreachable request is ignored. The script does not use
set -u, which would make bash exit 1 on an unset variable. A failed send never interrupts Claude Code. The hooks reference says exit code 2 on a Stop hook keeps Claude working, which you do not want here.
Step 2: register the hook
Add this to ~/.claude/settings.json (all projects) or .claude/settings.json (one project):
{
"hooks": {
"Stop": [
{
"hooks": [
{ "type": "command", "command": "~/.claude/hooks/whatsapp-done.sh" }
]
}
]
}
}Stop takes no matcher; the hooks reference says it always fires. Export your key and number where Claude Code can see them, for example in your shell profile:
export BLUETICKS_API_KEY="bt_live_your_key_here"
export BLUETICKS_TO="15551234567@c.us"Treat the key like a password and keep it out of source control, per the authentication docs.
Step 3: check it
Start Claude Code, ask for something short, and wait for the turn to end. A WhatsApp message from your own number to your own number should arrive. If nothing arrives, run the script by hand with a sample payload:
echo '{"cwd":"/home/me/my-app","last_assistant_message":"Tests pass."}' | ~/.claude/hooks/whatsapp-done.shWhat was tested, and what was not
I ran this script against a local mock server that records requests. It sent POST /v1/messages/15551234567@c.us with Authorization: Bearer <key>, Content-Type: application/json, and a body of {"type":"text","text":"Claude Code finished in my-app: Tests pass."}. A quote and a newline in the message survived as valid JSON, an input longer than 300 characters was cut, and a missing field fell back to the default text. The script exited 0 when the server was unreachable, and exited 0 without sending anything when BLUETICKS_API_KEY or BLUETICKS_TO was unset or empty.
I did not send a live WhatsApp message for this page, so delivery to a real phone is not covered by that test.
Limits to know about
- Plan limits. The hosted MCP page says the free plan allows 5 requests per 6 hours, shared with the API, and paid plans have no request limit. A hook that fires on every turn can use up the free allowance. See the hosted MCP page.
- The connection must be up. On Pro the connection runs in the cloud; on Basic and Standard it runs in your browser, so WhatsApp Web has to stay open. See Claude Code on WhatsApp.
- It sends part of Claude's last reply. The message includes up to 300 characters of
last_assistant_message. Remove that part of the script if your replies can contain anything you do not want on your phone. - Your own number only. The example messages you. WhatsApp's rules apply to every message sent from your number, and consent is yours to collect if you ever point it at someone else.
Add an alert for when Claude is waiting on you
The hooks reference documents a Notification event with a permission_prompt matcher, which fires when Claude needs you to approve a tool use and the prompt has waited about six seconds. You can register a script under it with "matcher": "permission_prompt". I did not test this script under Notification, and I have not checked which fields that event passes on stdin, so adapt the message text before relying on it.
FAQ
Does a Stop hook fire when Claude is interrupted?
No. The hooks reference says a Stop hook does not run if the stoppage was caused by a user interrupt.
Can the hook also wait for my reply?
Sending is one request. Reading a reply and acting on it is a separate pattern; see human-in-the-loop WhatsApp approvals.
Where is the key shown?
The Blueticks API page, once, when you create it.
Try it on your own number. Create a key and paste the script above, or start free at blueticks.co/signup.

