Get Moshi
push

Push notifications and webhooks

Enable push, copy your API token, test notifications, send custom webhook alerts, and fan out agent pushes across devices on the same license.

updated 1 week ago8 min readpage 29 / 37

Moshi supports push notifications from two sources: custom webhooks and agent events from moshi-hook.

Pushed where you'll see itAgent pushes surface on the lock screen, Dynamic Island, and Apple Watch — and you can allow or deny right from the banner.

Enable push

Open Settings -> Push Notifications and turn notifications on. iOS may ask for system permission. If permission is denied, open iOS Settings and enable notifications for Moshi.

After registration succeeds, Moshi shows an API token in the settings screen.

Pause and resume

The notifications switch can pause Moshi delivery without deleting your token. Turn it back on when you want events to resume.

Silence agent pushes while your Mac is unlocked

On macOS, moshi-hook can keep inbox events flowing while making visible agent pushes silent whenever the local console is unlocked:

moshi-hook set suppress-push-while-unlocked on
brew services restart moshi-hook

This is off by default because an unlocked Mac does not necessarily mean somebody is looking at it. If the lock-state check fails, the notification remains enabled so an approval cannot disappear silently. See Hook settings for details and the separate nested-agent control.

Test notifications

Use the test action in the notification settings screen to send a normal notification or an image notification. Test notifications are unavailable on the iOS simulator.

Webhook API

Use the copied token with Moshi's webhook endpoint:

custom webhook
$curl -X POST https://api.getmoshi.app/api/webhook \
$ -H "Content-Type: application/json" \
$ -d \'{"token":"YOUR_API_TOKEN","title":"Done","message":"Build finished"}'

This is useful from shell scripts, CI jobs, cron tasks, and agent prompts.

Live Activity push example

The custom /api/webhook endpoint sends a normal notification; it does not start or update a Live Activity. Live Activity pushes come from structured agent events sent by moshi-hook through the host-scoped endpoint.

moshi-hook creates these requests automatically after you pair and install it. The low-level request below shows the contract for a host-resident integration. HOST_SECRET is the host credential created during pairing, not the API token displayed in Push Notifications settings:

curl -X POST https://api.getmoshi.app/api/v1/hosts/HOST_ID/events \
  -H "Authorization: Bearer HOST_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "source": "claude",
    "eventType": "agent_turn_complete",
    "sessionId": "session-123",
    "category": "task_complete",
    "title": "Refactor complete",
    "message": "Tests pass and the branch is ready",
    "eventId": "event-456",
    "projectName": "moshi/app-server",
    "liveActivity": {
      "action": "auto"
    }
  }'

On iOS, this single request updates the inbox through Expo Push and starts or updates the Live Activity through Apple's dedicated APNs Live Activity channel. task_complete updates the Live Activity without adding a separate banner. Use approval_required or error when the event also needs a visible iOS notification. Send session_ended when the agent session actually closes; Moshi ends the Live Activity and lets the final state remain for roughly ten minutes.

Every agent-event category except session_ended can start a fresh Live Activity when none is running. If ActivityKit has already supplied an update token, the same categories update the existing activity instead. See Live Activity for settings and lifecycle details.

liveActivity.action is optional. Omit it or use auto for the category-based behavior above. A host integration can use upsert to update or start regardless of category, end to terminate the current activity, or none to preserve normal push and inbox delivery without touching Live Activity. The response includes both the requested action and a result such as started, updated, ended, not_requested, unavailable, or failed.

Moshi exposes public deep links for active tmux and Herdr session cards. The links are constructible — Moshi does not mint an opaque token. Moshi 3.9.3 and later uses this explicit grammar:

moshi://tmux?session=<URL-encoded tmux session name>&window=<window index>
moshi://herdr?workspace=<workspace id>
moshi://herdr?session=<URL-encoded named Herdr server session>&workspace=<workspace id>

For example, a tmux session named my project becomes moshi://tmux?session=my%20project. In JavaScript, encode each query value with encodeURIComponent.

These links only resume cards that are already active or minimized in Moshi. They do not discover a remote multiplexer session or create a connection from a saved connection. If no active card matches, Moshi shows an error instead of opening a new connection.

For tmux, session is the tmux session name. The optional window is a single-digit window index from 0 through 9; Moshi resumes the matching active card and sends the configured tmux prefix followed by that digit. A window does not affect card selection, and Moshi cannot detect a missing window before resuming.

For Herdr, session is the server session reported by herdr session list, not the workspace label shown on the active card. It is optional and defaults to Herdr's default session, matching the UI rule that omits default from workspace labels. Use session=<name> only when targeting a named, non-default server session; session=default is also accepted explicitly.

The optional Herdr workspace is the stable workspace ID. Moshi first looks for an active card whose server session and workspace both match, preferring its live workspace ID over its open-time ID. If none is already on that workspace, Moshi resumes the most recently minimized active card for the server session and asks the existing Herdr focus flow to switch it to the requested workspace. It never opens a new session or creates a connection from a saved connection.

URL-encode workspace IDs containing reserved characters, such as workspace=w%2Fapi. Without workspace, Moshi resumes the most recently minimized active card for the selected server session.

The older name=<session> parameter remains a compatibility alias for both multiplexers; session wins when both are present. The alias still means the tmux session or Herdr server session, never a visible Herdr workspace label.

The public grammar does not accept hostId, a tmux pane, or a Herdr tab or pane. If matching sessions are active on multiple hosts, Moshi uses the workspace match described above or the most recently minimized match, so use session and workspace IDs that avoid ambiguity. The underlying moshi://terminal/... route and its query parameters are internal and should not be used as an integration API.

Open a session from a webhook notification

POST /api/webhook does not have a top-level deepLink field. Put the Moshi link in the existing URL action instead:

open a tmux session on tap
$curl -X POST https://api.getmoshi.app/api/webhook \
$ -H "Content-Type: application/json" \
$ -d \'{"token":"YOUR_API_TOKEN","title":"Agent needs input","message":"Open my-project","data":{"type":"url","url":"moshi://tmux?session=my-project"}}'

Tapping the notification opens the matching active session card. For Herdr's default session, use a URL such as moshi://herdr?workspace=w-api; Moshi selects an active card already on that workspace or switches the most recent active default-session card to it.

Unified push

By default, /api/webhook sends to the device that owns the token. To reach every opted-in device joined to the same Moshi license, include unified: true on the webhook payload.

unified webhook push
$curl -X POST https://api.getmoshi.app/api/webhook \
$ -H "Content-Type: application/json" \
$ -d \'{"token":"YOUR_API_TOKEN","title":"Done","message":"Build finished","unified":true}'

With unified: true, Moshi looks up the license joined by the token's device and fans the push out to every device on that license whose unified-push setting is on. Devices that only restored the store purchase but have not joined the Moshi license are not included.

Events sent by moshi-hook use unified push automatically when the host is attached to a license. The hook cannot choose one specific target device; delivery goes to all opted-in devices on that license. To stop receiving shared host pushes on one device, turn unified push off for that device in Settings -> Your License.

Agent-event push limits

moshi-hook limits agent-event fan-out before contacting Moshi's service: free hosts can send up to 10 events per 60 seconds, while hosts attached to Moshi Pro can send up to 60 events per 60 seconds. Expo Push notifications and Live Activity updates use one shared counter, so an event consumes one slot whether it updates either channel or both. Fan-out to multiple devices still counts as one event.

The hook enforces the limit locally first, avoiding requests for events that are already over quota. The server applies the same limit as a safety backstop. For Pro, the server-side quota is shared across hosts attached to the same Moshi license.

Agent notifications

For coding agents, prefer moshi-hook over hand-written webhook prompts. Hooks can distinguish approvals, turn completion, session starts, and tool activity without relying on the agent remembering to call curl.

Images

Send a rich image notification by passing a public HTTPS image URL as data.type: "image":

curl -X POST https://api.getmoshi.app/api/webhook \
  -H "Content-Type: application/json" \
  -d '{
    "token": "YOUR_API_TOKEN",
    "title": "Screenshot ready",
    "message": "The latest build finished",
    "data": {
      "type": "image",
      "url": "https://example.com/screenshot.png"
    }
  }'

The image URL must be reachable without authentication when the notification is delivered. Moshi passes it to Expo as a rich-content attachment and also includes it in the notification data.

If the image is local, upload it to Moshi first with the same API token:

curl -X POST https://api.getmoshi.app/api/v1/images/upload \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -F "file=@/path/to/screenshot.png"

The upload response includes an eight-character code:

{
  "id": "abcde",
  "code": "abcde1xy",
  "expires_at": "2026-07-23T12:00:00.000Z"
}

Build the public URL as https://i.getmoshi.app/<code>, then send it through the webhook:

curl -X POST https://api.getmoshi.app/api/webhook \
  -H "Content-Type: application/json" \
  -d '{
    "token": "YOUR_API_TOKEN",
    "title": "Screenshot ready",
    "message": "Tap to view the result",
    "data": {
      "type": "image",
      "url": "https://i.getmoshi.app/abcde1xy"
    }
  }'

Moshi-hosted uploads are limited to 10 MB and 10 successful uploads per hour. Their public links expire after one day. The app's notification settings also include an image test action for checking rich-notification delivery on a physical device.

Token handling

Treat the API token like a notification secret. Anyone with the token can send notifications to your device. If a token leaks, disable and re-register notifications from the app.