AckPush GuideOpen AckPush

Answer your apps and agents from anywhere

AckPush is one inbox for the questions your apps and AI agents need you to answer. They send a message; it reaches your phone as a notification with buttons, and the web inbox. You answer with one tap, a reply or a snooze, and the app gets the answer right away.

How it works

  • Answer once, done everywhere. Answering on one device clears the message on the others.
  • Buttons, replies and snoozes. A message can offer up to 3 actions: buttons (Approve, Deny…), a typed reply, or “remind me later”.
  • The app hears back. Through a signed webhook, or by waiting on the message.

Get started

Signing in on the phone, allowing notifications, checking Devices and sending a test
Sign in on the phone, allow notifications, check Devices, send yourself a test.
  1. Install the AckPush app on your phone (iPhone or Android). AckPush is invite-only for now: request an invite and the install link comes from the AckPush team.
  2. Sign in with Google (or Apple on iPhone) and allow notifications when the phone asks. The app registers your phone automatically.
  3. Sign in on the web at ackpush.com with the same account. Check Devices: your phone should be listed with a recent “Last seen”.
  4. Send yourself a test from Send a test. It should reach your phone in a few seconds; answer it there or on the web.
On iPhone, notification buttons appear when you press and hold (or swipe left and tap View). On Android, expand the notification.
Nothing arriving? If you chose Don’t Allow, turn notifications on for AckPush in the phone’s Settings, then open the app once. See Troubleshooting.

Answer messages

Selecting a message in the web inbox and pressing Approve
The web inbox: pick a message, press Approve (or A).

Three ways to answer

  • From the notification: tap a button, or choose Reply and type. No need to open the app.
  • In the phone app: tap the notification (or open AckPush) to see the message, its deadline and all its actions.
  • On the web: the inbox updates live. Needs answer lists questions still waiting; Snoozed, Answered and All show the rest.

On your phone

Opening a message from its notification and snoozing it until tomorrow
Tap a notification to open the message; Snooze… brings it back later.
  1. From the notification: press and hold it (iPhone) or expand it (Android), then tap a button. On a locked iPhone, Approve asks for Face ID or your passcode first; Deny, No and Decline show in red.
  2. Reply in your own words: tap Reply, type, and tap Send. The app or agent gets your text.
  3. Open it: tap the notification to see the whole message, its deadline and every choice. Needs answer lists what is still waiting; All shows everything.
  4. Not now: in the message, tap Snooze… and pick In 1 hour, Tomorrow 9 AM or Next week. It leaves Needs answer and comes back with a fresh notification.
Answer once, anywhere: the notification clears on your other phones and the web inbox updates. The gear icon in the app shows which account and phone are registered.

Keyboard shortcuts (web)

J / KNext / previous message
APress the first button (usually Approve)
DPress the second button (usually Deny)
RWrite a reply (⌘↩ or Ctrl+↩ sends it)
SSnooze: in 1 or 3 hours, tomorrow 9 AM, next Monday, or a time you pick
EscClose the snooze menu, then the message
Snooze hides a message and brings it back, with a fresh notification, at the time you chose. A message can also have a deadline; after it, the message expires and the app is told.

Connect an app

Anything that can make an HTTPS request can send you messages: a server, a script, a cron job, an AI agent.

Creating an app and an API token in the console
Create an app, then a token. Copy the token: it is shown only once.
  1. Open Apps and tokens and create an app with the name you want to see on notifications (for example “PropPulse”).
  2. On the app’s page, create a token with a label that says where it is used. Copy it right away: AckPush stores only a hash and cannot show it again. Lost it? Revoke it and create another.
  3. Store the token where the app keeps its secrets (an environment variable such as ACKPUSH_TOKEN), never in source code.
  4. Send your first message:
Send a message
curl -X POST https://ackpush.com/v1/messages \
  -H "Authorization: Bearer $ACKPUSH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Reconcile October rent for 4B?",
    "body": "$1,850 received, $1,900 due.",
    "actions": [
      {"key": "approve", "label": "Approve"},
      {"key": "deny", "label": "Deny"},
      {"key": "note", "label": "Reply", "kind": "reply"}
    ]
  }'
A message sent with curl arrives on the phone and is approved
Your app sends a message; you answer from the notification.

The response is {"id": "…", "state": "queued"}. Keep the id: it is how you get the answer. All fields are in the message reference.

Get the answer back

Option 1: webhook (best for servers)

  1. On the app’s page in Apps and tokens, enter your endpoint (it must start with https://) and choose Turn on webhook.
  2. Copy the signing secret (shown once) into your server’s secrets.
  3. AckPush POSTs JSON to your endpoint when a message is answered, snoozed, expired or failed. Reply with any 2xx status; failures are retried with backoff and listed under the app’s recent deliveries.
Webhook body
{
  "event": "message.answered",
  "occurred_at": "2026-10-04T19:14:07Z",
  "message": { "id": "8c1f…", "state": "answered", "title": "Reconcile October rent for 4B?", "metadata": {} },
  "response": { "action_key": "approve", "text": null, "channel": "app_action", "responded_at": "2026-10-04T19:14:07Z" }
}

Every request carries AckPush-Signature: t=<unix time>,v1=<HMAC-SHA256(secret, "t.body")>. Check it before trusting the body:

Verify the signature (Node.js)
import crypto from 'node:crypto';

// header: the AckPush-Signature value, e.g. "t=1791141247,v1=5f2c…"
// rawBody: the request body exactly as received (a string, not parsed JSON)
export function verifyAckPush(header, rawBody, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; // 5 minutes
  return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ''));
}

Option 2: wait for it (best for scripts and agents)

Call wait with the message id. It returns as soon as you answer, or after timeout seconds (up to 55) with the current state; call it again to keep waiting.

Wait for the answer
curl "https://ackpush.com/v1/messages/MESSAGE_ID/wait?timeout=55" \
  -H "Authorization: Bearer $ACKPUSH_TOKEN"

Example: act on each button

A webhook lets your server do something different for each answer. This example is a small Python receiver you deploy on Modal, which gives it a public https:// address, so nothing runs on your computer. It reacts to the button you pressed, and a page at the same address lists the last 20 webhooks with their JSON.

You answerThe receiver
Approve✅ Starts the deploy (pretend).
Deny⛔ Cancels the deploy (pretend).
Reply “ship it after lunch”💬 Saves your text.

The actions are placeholders: replace the body of react() with your own code, such as starting a job, creating a document or calling another API. The receiver also checks the signature, ignores a delivery it has already handled (the same webhook can arrive twice), and answers with 200 straight away.

app.py: what to do for each answer
def react(payload: dict) -> str:
    """Your logic goes here: what to do for each answer."""
    answer = payload.get("response") or {}
    title = (payload.get("message") or {}).get("title", "")
    if payload.get("event") != "message.answered":
        return f"{payload.get('event')}: nothing to do"
    if answer.get("action_key") == "approve":
        return f'✅ Approved, so the deploy for "{title}" started'
    if answer.get("action_key") == "deny":
        return f'⛔ Denied, so the deploy for "{title}" was cancelled'
    if answer.get("text"):
        return f'💬 Reply saved: "{answer["text"]}"'
    return f'Button "{answer.get("action_key")}" pressed'

Deploy it

  1. Install Modal, sign in, and download the example:
Install and download
pip install modal
python3 -m modal setup
curl -fsSL https://ackpush.com/examples/ackpush-webhook-modal.py -o app.py
  1. Create its secret with a placeholder, then deploy. Until you replace the placeholder in step 4, the receiver refuses every request. The deploy prints the address, ending in .modal.run.
First deploy
python3 -m modal secret create ackpush-webhook ACKPUSH_WEBHOOK_SECRET=pending
python3 -m modal deploy app.py
  1. In Apps and tokens, open your app, paste that address under Webhook and choose Turn on webhook. Copy the signing secret (shown once).
  2. Replace the placeholder with the signing secret (paste it when asked), then stop and deploy again so the new secret is used:
Add the signing secret
printf 'Signing secret: '; stty -echo; read -r S; stty echo; echo
python3 -m modal secret create --force ackpush-webhook ACKPUSH_WEBHOOK_SECRET="$S"; unset S
python3 -m modal app stop ackpush-webhook-demo   # running copies keep the old secret
python3 -m modal deploy app.py
  1. Open the address in a browser. Send a message with Approve, Deny and Reply buttons to that app (see Connect an app) and answer it on your phone: a card appears within a few seconds.
The results page is public to anyone with the address and shows message titles and replies. That is fine for a demo, but a real receiver should not publish what it receives. To stop it: python3 -m modal app stop ackpush-webhook-demo.

Claude Code: approve from your phone

When Claude Code needs permission to run a command or edit a file, the AckPush hook sends the request to your phone instead of waiting in the terminal.

Claude Code asks permission; the request is approved on the phone and the command runs
Claude Code asks; you approve from your phone; it carries on.
You answerClaude Code
ApproveRuns the tool.
DenyDoes not run it, and is told you said no.
Reply “use the staging database instead”Does not run it, and follows your reply as the new instruction.
No answer in 5 minutesShows the normal prompt in the terminal; the phone message is withdrawn.

Set it up

  1. In Apps and tokens, create an app named Claude Code and a token for it. Copy the token.
  2. Save the token where the hook looks for it:
Save the token
mkdir -p ~/.config/ackpush
pbpaste > ~/.config/ackpush/claude-code-token   # macOS: paste the token you copied
chmod 600 ~/.config/ackpush/claude-code-token
  1. Download the hook (a small Node.js script with no dependencies; it needs Node 18 or newer):
Download the hook
curl -fsSL https://ackpush.com/hook/ackpush-permission-hook.mjs \
  -o ~/.config/ackpush/ackpush-permission-hook.mjs
  1. Add the hook to Claude Code’s settings. Use ~/.claude/settings.json for every project, or .claude/settings.local.json inside one project to try it there first. If the file already has a "hooks" section, add PermissionRequest to it.
settings.json
{
  "hooks": {
    "PermissionRequest": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "node ~/.config/ackpush/ackpush-permission-hook.mjs",
            "timeout": 360
          }
        ]
      }
    ]
  }
}
  1. Start a new Claude Code session (settings are read when a session starts) and ask for something that needs permission, such as running your tests.
At your keyboard and do not want phone prompts? Start Claude Code with ACKPUSH_DISABLED=1 claude. To wait longer than 5 minutes, set ACKPUSH_WAIT_SECONDS (keep it below the hook’s timeout).

Claude Code: notify and ask

The AckPush MCP server lets Claude message you on its own: a heads-up when a long job finishes, or a question before it does something important.

Claude asks before deploying and follows a typed reply
Claude asks through AckPush; your reply becomes its instruction.

claude.ai, Claude Desktop and ChatGPT

Adding AckPush as a custom connector, allowing it on the AckPush page, then getting a notification
Add the connector, sign in to AckPush and choose Allow. No token to copy.
  1. Add a custom connector (in Claude: Settings → Connectors → Add custom connector; in ChatGPT: Settings → Connectors) with this URL:
MCP server URL
https://ackpush.com/mcp
  1. Choose Connect. AckPush opens: sign in with the same account as your phone, pick the app the messages should appear as (for example a new app named “Claude”), and choose Allow.
  2. Back in the chat, ask Claude to notify you or to ask you something (examples below).

Claude Code

  1. Add the server; Claude Code opens the same AckPush sign-in in your browser the first time:
Add the MCP server
claude mcp add --scope user --transport http ackpush https://ackpush.com/mcp
  1. Check it with claude mcp list (it should say Connected), then try one of the prompts below.
Prefer an API token (for example on a server)? Create one in Apps and tokens and pass it as a header instead:
With an API token
claude mcp add --scope user --transport http ackpush \
  https://ackpush.com/mcp \
  --header "Authorization: Bearer $(cat ~/.config/ackpush/claude-code-token)"

To disconnect a signed-in client, open its app in Apps and tokens and revoke its OAuth token (or delete the app).

Your own buttons

Ask for the choices you want, and they show as buttons. The answer comes back by name (for example The user chose “Hold”.), so Claude can act on it.

Claude asks with Approve and Hold buttons; Hold is tapped and Claude holds
Approve / Hold on the notification; Claude stops on Hold.
Approve / Hold, Yes / No and Accept / Decline (each with or without Reply) are buttons on every phone. Other choices are buttons on Android, and show in the app and the web inbox on iPhone.

Things to ask

  • “Run the full test suite and notify me on AckPush when it finishes.”
  • “Prepare the release, then ask me on AckPush before you push the tag.”
  • “Ask me on AckPush with Approve and Hold buttons before you send the rent reminders.”
  • “Remind me on AckPush tomorrow at 9 AM to review the PR.”
ToolWhat it does
notifyA notification with no answer needed; can be scheduled for later.
askA question with Approve / Deny / Reply, or up to 3 choices of its own (such as Approve / Hold); waits up to about 45 seconds for the answer. Approve / Hold, Yes / No and Accept / Decline (optionally with Reply) are buttons on the notification on every phone; other choices are buttons on Android and show in the app and the inbox.
wait_for_replyKeeps waiting for an answer that has not come yet.
get_messageChecks a message’s state and answer without waiting.

Message reference

POST https://ackpush.com/v1/messages with Authorization: Bearer <token>. Only title is required.

FieldTypeWhat it does
titletext, 1–200The headline on the lock screen.
bodytext, up to 4000Details; the notification shows the start.
actionslist, up to 3Each { key, label, kind? }. kind is button (default), reply (typed answer) or snooze (with snooze_seconds, 60 to 30 days). key: lowercase letters, digits, _.
priority-2 to 2Negative is quiet; 1 and 2 mark it important. Default 0.
urlhttp(s) URLA link shown with the message (“Open link”).
send_atISO timeDeliver later instead of now (a reminder).
expires_atISO timeDeadline to answer; after it the message expires and the webhook says so.
dedupe_keytextWhile a message with this key is open, sending it again returns that message instead of notifying twice.
thread_keytextGroups related messages.
metadataobjectAnything you want back in the webhook.

Also available with the same token: GET /v1/messages/{id} (state and answer), /wait, and POST /v1/messages/{id}/snooze, /reschedule, /cancel.

Troubleshooting

Messages do not reach my phone
Check that notifications are allowed for AckPush in the phone’s settings, then open the app once so it registers. Devices should show the phone with a recent “Last seen”. Send a test to check.
An answered message stays on my iPhone
iOS sometimes delays or skips the silent “clear” push (low battery, Low Power Mode, or the app was swiped away). Opening AckPush clears everything that was answered elsewhere.
The app returns 401
The token is missing, mistyped or revoked. Send it as Authorization: Bearer ack_app_…, or create a new one.
Sign-in on the web does not finish
Use the same Google account as on your phone. AckPush is invite-only, so other accounts are refused: request an invite.
Claude Code still asks in the terminal
Start a new session after changing settings. Check that ~/.config/ackpush/claude-code-token exists, that ACKPUSH_DISABLED is not set, and that node --version is 18 or newer. With no answer in 5 minutes, the terminal prompt is expected.
Webhook deliveries fail
The app’s page lists recent deliveries with the last error. The endpoint must be HTTPS, respond within a few seconds with a 2xx status, and check the signature with the current secret.