Write-up · Abdeen Labs

Hark

Let your scripts, services, and AI agents reach your iPhone through a server you host—whether to send a quick status update, track a long job on the Lock Screen, or pause and ask for a decision.

iOS ·

02 · iOSabdeen.dev/hark

When a script needs to ask you a question

A nightly backup can let you know it finished. A deploy script can stream its progress to your Lock Screen—or pause mid-run and ask if you want to push to production. Hark gives your scripts, cron jobs, and AI agents a clean way to reach your iPhone via standard notifications, interactive questions, or iOS Live Activities. Webhooks come with sensible defaults for titles and priority, so a basic monitoring script just has to send the message text.

On the backend, it’s a single Go binary backed by Postgres. That one binary runs the API, serves the web dashboard, logs history, and dispatches pushes through Apple Push Notification service (APNs). Once you sign in on the iOS app, your phone registers its device token and capabilities. Any unanswered questions also sit in the app’s inbox, so you won’t lose track of a prompt if you accidentally swipe away the notification.

Prompts can ask for an Approve/Deny decision, a Yes/No, or a free-text reply. Binary choices can show up as interactive buttons right inside a Live Activity on your Lock Screen, while text replies use standard notification actions or the app. If none of your devices support interactive Live Activities, Hark automatically drops back to a question notification. Ordinary Live Activities keep a job’s status and progress visible as it runs.

Fig. 1 · What a script can put on your Lock Screen An iPhone Lock Screen with three things from Hark. A question in a Live Activity asks to deploy api to production, with Deny and Approve buttons. A second Live Activity shows a nightly backup at 62 percent. A notification titled Monitoring, a title the webhook supplies, says the disk on db-2 is 91 percent full. Questions that need a text reply, or that reach a phone without interactive Live Activities, arrive as a notification instead.

Scripts can ask, but only you can answer

Hark isn’t a public service, and there’s no public sign-up; each self-hosted instance has one admin who creates accounts for themselves or their team. Every account gets its own isolated devices, webhook services, API tokens, and message history. You log in with a password, and the iOS app stashes its session token in the system Keychain.

Your scripts talk to Hark through a service webhook or a scoped API token, and MCP agents get the same permissions with a token supplied directly or obtained through Hark’s OAuth sign-in. Crucially, permissions are strictly one-way: a token can be allowed to send notifications, post questions, and update Live Activities, but it’s never allowed to answer a question. Managing credentials and registering devices require a signed-in owner session, and creating accounts requires the admin.

Because notification actions and Lock Screen buttons have to work without the main app’s session, each question push carries a response token tied to that question. When you tap “Approve,” Hark verifies that your phone is still an active device on the account and that the question hasn’t changed since the version shown on your phone. Stale prompts can’t answer an updated question, and a conflicting second answer is rejected.

Hosting and webhook callbacks

Running Hark just takes Postgres, a root secret, a public URL, and your Apple push credentials. All messages, prompts, replies, and delivery logs live in your database.

Hark stores only hashes of API and webhook tokens and strips webhook paths out of HTTP access logs. A notification can also carry a link, an avatar, or a Wallet pass URL (from Pocketful, say); the iPhone’s notification extension downloads avatars and passes directly from public HTTPS URLs.

If your script doesn’t want to poll for an answer, it can pass a public HTTPS callback URL and a bearer token when it posts a question through a webhook. Hark encrypts that callback token at rest, validates the destination host before connecting, and refuses to follow HTTP redirects—so your callback credential is never forwarded to another host.

Fig. 2 · What a sender hands Hark, and where each piece goes A webhook URL or API token stays on your server, which checks it against a stored digest. A callback URL and bearer token are kept with the token encrypted at rest; Hark checks the destination, posts the answer only to that public HTTPS receiver and refuses redirects to another host. An avatar or Wallet pass URL is passed on in the push, and the iPhone downloads it over HTTPS.

Delivery logs, timeouts, and retries

Hark logs the exact outcome for every device on your account, and it still records why a send failed if you have no active phones yet or forgot to configure APNs. If Apple reports a device token as permanently dead, Hark deactivates it until that phone registers again. Push notifications themselves aren’t retried on failure, with one exception: if Apple rejects an expired provider token and Hark can refresh it, Hark tries once more.

Scripts can either poll the API or hold open a short wait request until you answer, as long as the question hasn’t expired. Retrying with the same idempotency key returns the existing record instead of spamming your phone, while reusing that key with a different payload counts as a conflict. Once you answer a prompt, Hark tries to end its Live Activity—though if that final push fails or the update token is missing, the answered card might linger on your Lock Screen.

Webhook callbacks have their own retry queue, which persists in Postgres across server restarts and backs off between attempts. If your receiving server processes the answer but Hark can’t record the success, it might see a duplicate delivery on retry. Once all retries run out, Hark marks the callback failed, though your script can still fetch the answer by polling the API. Expired and canceled questions don’t generate callbacks.

Fig. 3 · One send can have different outcomes on each device A send targets each active device. APNs may accept a push, refuse it because of a provider or network failure, or report an invalid token. An expired provider token is refreshed and retried once; other failures are not retried. Invalid device tokens are deactivated until the phone registers again. This example has one of three pushes accepted.
Abdeen Labs

Try Hark or dig into the code.

All Labs products