Docs

Everything you need to get messages onto your Even Realities G2: pair it, send a ping, wire up your tools.

Quickstart

  1. Install EyePush from Even Hub (Even Realities app, then Even Hub) and open it on your glasses. It shows a pairing code.
  2. Go to eyepush.app/pair and enter the code.
  3. Open the dashboard, create a source, and copy its key. Keys are 20 lowercase alphanumeric characters. Then send a ping:
curl -X POST https://eyepush.app/h/YOUR_KEY \
  -H "Content-Type: application/json" \
  -d '{"title":"Hello","body":"It works."}'

A source is one named inbound channel, such as “GitHub”, “Home” or “Claude”. Each has its own webhook URL, email address and MCP URL, and you can revoke one without breaking the others. Treat the key like a password: anyone who has it can send you pings.

Widget on the G2

The EyePush widget shows a live feed on the lens. Open it from the glasses after installing it from Even Hub. Each ping shows up to 2,000 characters; emoji and characters outside the G2 font are dropped by the glasses.

GestureAction
TapJump to the latest ping, or to a question waiting for an answer
Scroll (temple touchpad)Browse history
Double-tapExit the widget
Tap on a questionAnswer it: scroll to switch between the two answers, tap to send, double-tap to back out

The R1 ring’s tap and swipe work the same way.

The status line reads like LIVE · 1/12 · 09:41: feed state, position in history, and the time.

Phone notifications

EyePush also sends web push notifications to your phone. The G2 mirrors phone notifications onto the lens through its built-in notification mirroring, so pings reach you even when the widget is closed.

  1. Open eyepush.app/app on your phone and tap Enable notifications.
    iPhone: tap Share, then Add to Home Screen first, and open EyePush from the Home Screen icon. This needs iOS 16.4 or later.
  2. Send a test notification from the dashboard.
  3. In the Even Realities app, go to Settings, then Notifications, and enable EyePush (or your browser).

Notifications only show on the glasses for apps that have sent at least one notification. That is why the test in step 2 comes before step 3.

Add "silent": true to a ping to keep it on the widget only, with no phone notification.

Webhook

POST https://eyepush.app/h/YOUR_KEY

{
  "title": "Deploy finished",
  "body": "api-server v2.4.1 is live",
  "silent": false
}
FieldNotes
bodyThe message. Aliases: text, message, content.
titleOptional heading. Alias: subject.
silenttrue sends to the widget only, with no phone notification.

Accepted formats

  • JSON, as above.
  • Plain text: the whole request body becomes the message body.
  • Form-encoded fields with the same names.
  • Slack incoming-webhook payloads (text, blocks, attachments) and Discord webhook payloads (content, embeds), as-is. Any tool that can post to a Slack or Discord webhook can post to EyePush: swap the URL.
  • GitHub webhooks (content type application/json) are formatted automatically for push, pull_request, issues, release and workflow_run events.
  • Anything else: unrecognized JSON is shown as key: value lines.

Responses

HTTP/1.1 202 Accepted
{"id":"…","widgets":1,"phones":1}

widgets and phones count the paired glasses and phones the ping was delivered to. See Errors for the rest.

GET requests

For tools that can only open a URL (IFTTT, iOS Shortcuts, bookmarks), the same endpoint accepts a query string.

https://eyepush.app/h/YOUR_KEY?title=Hi&body=Hello

URL-encode the values. Add &silent=true for widget-only.

REST API

Prefer a header over a key in the URL? Use the authenticated endpoint. The OpenAPI 3.1 spec is at eyepush.app/openapi.json.

curl -X POST https://eyepush.app/api/v1/messages \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Hello","body":"From the API","silent":false}'

Email

Send to YOUR_KEY@eyepush.app. The subject becomes the title and the plain-text body becomes the body. Quoted replies and signatures are trimmed, and attachments are ignored. It’s a good fit for forwarding rules and tools that can only send email.

Slack

  1. Webhook URL: paste your EyePush URL anywhere a tool asks for a Slack incoming-webhook URL.
  2. Slash command and shortcut: in the dashboard, open Sources, then Slack, for a one-click Slack app manifest. It adds a /glasses <message> slash command and a “Send to glasses” message shortcut to your workspace. In Slack: Create app, From manifest, paste, Install.

Claude, ChatGPT and MCP

EyePush runs a remote MCP server at https://eyepush.app/mcp/YOUR_KEY (Streamable HTTP). There is no OAuth: the key in the URL is the secret.

ToolArgumentsWhat it does
send_to_glassesbody, optional title and silentShows a message.
ask_on_glassesquestion, optional details, yes, no and expires_in_minutesAsks a yes/no question and waits up to 45 seconds for the answer.
check_answeridKeeps waiting on a question that wasn’t answered in time.

Tell your agent when to use them, for example: “When the tests pass, ask me on my glasses before you push to main.”

Claude Code

claude mcp add --transport http eyepush https://eyepush.app/mcp/YOUR_KEY

Claude (web and desktop)

Settings, then Connectors, then Add custom connector, and paste the URL.

ChatGPT

Enable developer mode, then add a custom connector (MCP) with the URL and no authentication.

Custom GPTs

Configure, then Actions, then Import from URL: https://eyepush.app/openapi.json. Set Authentication to API key, Auth type to Bearer, and paste your key.

Any other agent framework or automation can simply POST to the webhook.

Yes/no questions

Add ask to a ping and it becomes a question. You answer on the lens, with a tap on the glasses or the R1 ring, and the sender gets the answer back. Use it as an approval step: deploy or not, merge or not, spend or not.

curl -X POST https://eyepush.app/h/YOUR_KEY \
  -H "Content-Type: application/json" \
  -d '{"ask":"Run the migration on prod?","body":"3 tables, about 40s of downtime","yes":"Approve","no":"Deny","wait":60}'
FieldNotes
askThe question, or true to ask the title or body.
yes, noLabels for the two answers, up to 20 characters each. Default Yes and No.
expiresInSeconds the question stays open, from 10 to 604,800 (a week). Default 3,600.
waitSeconds to hold the request open for the answer, up to 60. Default 0.
callbackUrlAn https:// URL that receives the outcome. See Callbacks.

The fields work as form fields and query parameters too: https://eyepush.app/h/YOUR_KEY?ask=Deploy%3F&yes=Ship&no=Hold. With the REST API, POST {"question": "…"} to /api/v1/questions.

Getting the answer

The response describes the question. With wait, it returns as soon as you answer:

HTTP/1.1 202 Accepted
{
  "id": "0mvsnkqx9yhfef4",
  "status": "answered",
  "answer": "yes",
  "choice": "Approve",
  "title": "Run the migration on prod?",
  "body": "3 tables, about 40s of downtime",
  "askedAt": 1793224514637,
  "answeredAt": 1793224517529,
  "expiresAt": 1793228114635,
  "widgets": 1,
  "phones": 1,
  "pollUrl": "https://eyepush.app/h/YOUR_KEY/questions/0mvsnkqx9yhfef4"
}

status is open, answered or expired. answer is yes, no or null, and choice is the label that was picked. Times are milliseconds since the epoch. While a question is open, GET its pollUrl, adding ?wait=60 to wait for a change. The first answer is final.

Answering

  • On the lens. A new question takes over the lens. Tap to answer, scroll to switch between the two answers, and tap again to send. Double-tap backs out without answering.
  • On the phone. The widget’s screen in the Even Realities app and the dashboard both show the two answers as buttons. On Android and desktop, the push notification has them too.

Callbacks

With callbackUrl, EyePush POSTs the same JSON there when the question is answered or expires, with "event" set to "question.answered" or "question.expired". If your server is down or returns a 5xx, it tries twice more. The X-EyePush-Signature header is sha256= followed by the hex HMAC-SHA256 of the raw body, keyed with the source key the question was asked with. Check it before acting on a yes:

import { createHmac, timingSafeEqual } from "node:crypto";

function fromEyePush(rawBody, signatureHeader, sourceKey) {
  const expected = Buffer.from("sha256=" + createHmac("sha256", sourceKey).update(rawBody).digest("hex"));
  const given = Buffer.from(signatureHeader ?? "");
  return given.length === expected.length && timingSafeEqual(given, expected);
}

Treat expired, timeouts and errors as “no”. Anyone with the source key can ask questions and read their answers, so keep keys used for approvals in a secret store.

Copy-paste recipes

curl

Plain text works too: the whole body becomes the message.

curl -d "Backup finished" https://eyepush.app/h/YOUR_KEY

GitHub Actions

Store your full webhook URL as a repo secret named EYEPUSH_URL.

- name: Ping my glasses
  if: always()
  run: |
    curl -fsS -X POST "${{ secrets.EYEPUSH_URL }}" \
      -H "Content-Type: application/json" \
      -d "{\"title\":\"${{ github.workflow }}: ${{ job.status }}\",\"body\":\"${{ github.repository }}@${{ github.ref_name }}\"}"

Or skip the step entirely: add your EyePush URL as a repository webhook (content type application/json) and push, pull request, issue, release and workflow run events are formatted for you.

Approval gate (shell or CI)

Asks, then keeps waiting until the question is answered or expires. The script fails unless the answer is yes, so put it before the step it guards. It needs curl and jq, which GitHub-hosted runners have.

r=$(curl -fsS "$EYEPUSH_URL" -H "Content-Type: application/json" \
  -d '{"ask":"Deploy api to prod?","yes":"Deploy","no":"Hold","expiresIn":900,"wait":60}')
poll=$(echo "$r" | jq -r .pollUrl)
while [ "$(echo "$r" | jq -r .status)" = open ]; do
  r=$(curl -fsS "$poll?wait=60")
done
test "$(echo "$r" | jq -r .answer)" = yes

Home Assistant

Add to configuration.yaml, then call rest_command.eyepush from an automation.

rest_command:
  eyepush:
    url: "https://eyepush.app/h/YOUR_KEY"
    method: POST
    content_type: "application/json"
    payload: '{"title": "{{ title }}", "body": "{{ body }}"}'

# in an automation action:
- service: rest_command.eyepush
  data:
    title: "Front door"
    body: "Opened at {{ now().strftime('%H:%M') }}"

Zapier

Add the Webhooks by Zapier action, event POST. URL: your EyePush URL. Payload Type: json. Data: map title and body from earlier steps. Or paste a Payload JSON like:

{"title":"New lead","body":"Ada Lovelace signed up"}

Make

Add the HTTP, Make a request module. URL: your EyePush URL. Method: POST. Body type: Raw, content type JSON.

{"title":"Scenario finished","body":"12 rows processed"}

n8n

Add an HTTP Request node. Method: POST. URL: your EyePush URL. Send Body: on, Body Content Type: JSON, then specify:

{"title":"Workflow done","body":"{{ $json.summary }}"}

IFTTT

Use the Webhooks, Make a web request action. Method: GET (or POST), content type application/json. Put the values in the query string:

https://eyepush.app/h/YOUR_KEY?title={{ExtraData}}&body={{OccurredAt}}

iOS Shortcuts

Add a Get Contents of URL action. URL: your EyePush URL. Method: POST. Request Body: JSON, with text fields title and body. For a quick version, use GET with a query string instead.

Python

import requests

requests.post(
    "https://eyepush.app/h/YOUR_KEY",
    json={"title": "Job done", "body": "Trained 3 epochs", "silent": False},
    timeout=10,
)

JavaScript

await fetch("https://eyepush.app/h/YOUR_KEY", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ title: "Build done", body: "All 214 tests passed" }),
});

Limits

FreePro
Sources225
Pings per day1005,000
HistoryLast 50 messagesLast 1,000 messages
URL watchers-Up to 10, polled every 5 minutes
QuestionsCount as pings. Open from 10 seconds to a week; kept past the history limit until answered or expired.

Daily counters reset at 00:00 UTC. Request bodies are limited to 64 KB, and the widget shows up to 2,000 characters per ping.

URL watchers (Pro) poll any URL or JSON API every 5 minutes and ping you when the value changes.

Delivery is best-effort. Do not use EyePush for emergency, medical or safety-critical alerts.

Errors

StatusMeaning
202Accepted. Body: {"id":"…","widgets":1,"phones":1}, plus the question fields for a question.
400Invalid question: no text, or a callbackUrl that isn’t https://.
402The source is paused because the account is over its plan’s source limit.
404Unknown key or question. Check it, or it may have been revoked.
409Answering a question that was already answered or has expired.
413Request body over 64 KB.
429Daily limit reached. Resets at 00:00 UTC.

Stuck? Email hello@eyepush.app.