# 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.

HTML: https://notify.belweave.ai/docs/connect

## 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.
