Skip to documentation
Notify Docs

Reach an internal service

Expose a private HTTP service through Notify Connector and the OpenTunnel relay. The public hostname reaches the service; a verified Notify pass or signed callback still determines who may use it.

Relay and connector lifecycle

The relay lives at https://api.clawchest.com; tunnel addresses are https://<id>.clawchest.com and named routes https://<route>.<id>.clawchest.com. Tunnel creation uses a Notify agent token (ntf_…), introspected at /api/relay/introspect. A public tunnel address provides reachability, not visitor authorization.

  • @belweave/notify-connector uses OpenTunnel 0.5.1, declares public routes, registers web apps, and sends heartbeats. Notify retains its Cloudflare relay and AWS backend.
  • After five minutes without a heartbeat, the connector is marked offline. Its configured oncallGroupId can receive a page.
  • A callback target containing connectorId, route, and path follows the connector’s live address at delivery time.

Expose a local port

Run the web app and the Notify CLI on your computer. Keep both processes running, then open the registered app from Apps in Notify on your phone. This example connects a local warehouse dashboard on port 3000:

bash
notify auth login
notify connect 3000 --name warehouse
# https://warehouse.<id>.clawchest.com

OpenTunnel 0.5.1 adds optional PROXY protocol v1/v2 support. Use notify connect 3000 --name warehouse --proxy-protocol v2 only when that upstream listener explicitly accepts PROXY protocol before HTTP, or before TLS for an HTTPS upstream. Ordinary web servers should omit the flag. In the SDK, a route can be { target: "127.0.0.1:3000", proxyProtocol: "v2" }; plain string routes keep their existing behavior. The authenticated gateway remains mandatory and forwards the preamble only after authorization. Visitor addresses come from the relay connection, not caller-supplied HTTP forwarding headers.

The connector adopts the 0.5.1 client’s reconnect and transient-failure handling. Certificate and tunnel state remain in memory for the running connector; restarting does not restore a persisted tunnel identity or certificate.

The named origin is registered as a Notify web app. The connector's mandatory gateway validates a one-time Notify pass, then rechecks current access for every page, asset, API request and WebSocket. The exact origin prevents a sibling route from reusing its pass. The updated notify connect and connectors rotate-secret save the callback signing secret in a mode-0600 file and print only its path, including with NOTIFY_TOKEN; the environment token itself is not saved. The normal locations are ~/Library/Application Support/notify/connectors on macOS and ~/.config/notify/connectors on Linux, or beside NOTIFY_CONFIG when set. Older CLI releases printed the secret to stderr; update before running in an agent log. Default logins include connector read/write scopes; narrowed or older logins need connectors:write. Rotate with notify connectors rotate-secret <id>.

Return an answer to a private receiver

To return an approval to a private receiver, register its listening port as a connector route and save that route plus the receiver path on the interaction. Notify signs the answer and sends it through the fixed /.notify/callback gateway ingress. The gateway authorizes the saved target, current workspace access and durable replay receipt before forwarding; callbacks cannot bypass sign-in on ordinary app routes.

bash
# On the laptop: a tiny receiver on port 8780, then
# notify connect 8780 --name cb

# From CI or an agent:
notify send ask "Approve the maintenance window?" --approval --stdin <<'JSON'
{
  "callback": {
    "target": {
      "connectorId": "conn_example_warehouse",
      "route": "cb",
      "path": "/approve"
    }
  }
}
JSON

Verify Notify-Signature: v1=<hex> as HMAC-SHA256 of {timestamp}.{rawBody}, using Notify-Timestamp and the connector’s callbackSecret. A classic URL/token callback instead uses the callback token as its HMAC key and also sends a bearer Authorization header. Accept timestamps only within five minutes and reject future values.

json
{
  "type": "notification.response",
  "interactionId": "int_example_pickup",
  "actionDigest": "<the stored interaction actionDigest>",
  "eventId": "evt_...",
  "kind": "approval",
  "status": "approved",
  "action": "approve",
  "text": null,
  "respondedAt": "2026-10-09T12:00:00.000Z"
}
The SDK exports verifyCallbackSignature for this format. Verify before acting and deduplicate by interactionId because retries can repeat answers. Legacy service receivers may also use eventId; agent-created asks can have a null event ID. Map a verified canonical action to a known operation; do not execute text from the callback as a shell command.

SDK middleware for a private site

The connector registers a hosted-app origin and places its mandatory gateway in front of the private dashboard. The gateway validates ES256, exact issuer and origin audience, expiry, app/workspace claims, durable one-time replay consumption and current access. It covers static assets and WebSocket upgrades as well as application requests. Your server still authorizes access to its own data using the gateway’s verified identity, and its listener must not be publicly reachable around that gateway.

For direct integrations, the SDK also supplies honoNotifyPass, expressNotifyPass, and fastifyNotifyPass. Configure issuer https://notify.belweave.ai, the exact audience, and the required atomic durable consumeJti store; the connector README includes a database example. Missing or unavailable replay storage denies access. These raw-pass helpers are separate from the gateway’s short-lived sessions and continuous revocation checks. verifyNotifyPass alone verifies cryptography, not replay or current membership.