> ## Documentation Index
> Fetch the complete documentation index at: https://docs.noxus.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Inbound Connectivity

> What must be able to reach your deployment — users, channel webhooks, and OAuth callbacks

Inbound traffic falls into two groups with very different exposure needs:

1. **Your users** — browsers and API clients. Can stay entirely on a private
   network/VPN.
2. **External SaaS webhooks** — third-party services pushing events to you.
   Require a publicly reachable endpoint.

## Services and ports

Self-hosted Noxus puts a reverse proxy (nginx on VM, an ingress controller on
Kubernetes) in front of three HTTP services. Only the proxy needs to be exposed.

| Service                 | Internal port              | Public hostname (typical) | Who connects               |
| ----------------------- | -------------------------- | ------------------------- | -------------------------- |
| Frontend (Next.js)      | `8080` (`3000` on VM)      | `app.example.com`         | Users' browsers            |
| Backend API (FastAPI)   | `8080` (`8100` on VM)      | `api.example.com`         | Browsers, API clients, SSE |
| Relays (FastAPI)        | `8080` (`5003` on VM)      | `relay.example.com`       | External SaaS webhooks     |
| Workers                 | `8080` (internal)          | —                         | Never exposed              |
| Plugin server / sandbox | `8500` / `8080` (internal) | —                         | Never exposed              |

<Note>
  On Kubernetes these are three ingress hosts (main, `api.`, `relay.`) all
  routing to internal port `8080`. On the VM, nginx terminates TLS on `80`/`443`
  and proxies to the per-service ports. Only `443` (and `80` for redirect) needs
  to be open at the edge.
</Note>

## Inbound from your users (UI, API, streaming)

These power the product itself and are **always required**, but they only need
to be reachable by your users — a LAN or VPN address is fine.

* **Frontend + Backend API** over HTTPS.
* **Server-Sent Events (SSE)** — long-lived `GET`/`POST` responses with
  `text/event-stream`, used for live run progress (`/v1/runs/{run_id}/events`)
  and agent replies (`/v1/conversations/{conversation_id}/stream` and
  `/events`). Make sure your proxy does **not buffer** these responses and
  allows long-lived connections (disable response buffering; set a generous
  read timeout — streams can run for minutes).
* **WebSockets** — used by optional interactive features (sandbox shell,
  playbook recording). If your proxy needs explicit WebSocket upgrade rules,
  add them; these features simply won't work without them, but core usage is
  unaffected.

<Warning>
  A common self-hosting bug is an idle-timeout or buffering proxy that cuts SSE
  streams. If runs "hang" in the UI but complete server-side, check your proxy's
  buffering and timeout settings on the API host first.
</Warning>

## Inbound from external SaaS (channel webhooks)

Agent and trigger **channels** differ in whether the external service *pushes*
events to you (needs public inbound) or whether Noxus *pulls* them (outbound
only). This is the single biggest restricted-networking consideration.

| Channel                     | Mechanism                                                   | Needs public inbound?    | Notes                                                                     |
| --------------------------- | ----------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------- |
| **WhatsApp** (Cloud API)    | Meta POSTs to your webhook                                  | **Yes**                  | No polling alternative — webhook is mandatory                             |
| **Microsoft Teams**         | Teams POSTs change notifications                            | **Yes**                  | No polling fallback                                                       |
| **Telegram**                | `setWebhook` registers your URL; Telegram POSTs updates     | **Yes**                  | A `getUpdates` polling fallback is **not** implemented                    |
| **Generic webhook trigger** | External system POSTs to `/webhook/{group_id}/{trigger_id}` | **Yes**                  | The whole point is to receive external calls                              |
| **Google Chat**             | Webhook **or** Pub/Sub                                      | **Only in webhook mode** | Switch to **Pub/Sub mode** to make it outbound-only                       |
| **Slack**                   | **Socket Mode** (outbound WS) or Events webhook             | **No, with Socket Mode** | Socket Mode opens an outbound WebSocket to Slack, so no inbound is needed |
| **Gmail**                   | Relay polls the Gmail API                                   | **No**                   | Outbound polling loop                                                     |
| **Outlook / email**         | Relay polls the provider                                    | **No**                   | Outbound polling loop                                                     |
| **Knowledge base sync**     | Worker polls the source                                     | **No**                   | Periodic outbound polling                                                 |

<Tip>
  In a private/VPN-only deployment you can still use **Slack (Socket Mode)**,
  **Gmail/Outlook**, **Google Chat in Pub/Sub mode**, and **KB sync** — they
  never need an inbound path. The push channels (WhatsApp, Teams, Telegram,
  generic webhooks) require exposing the **relay host** to the internet; expose
  only `relay.example.com` and keep the app itself private if you want to
  minimise surface area.
</Tip>

### Securing the relay endpoint

If you must expose the relay for push channels, restrict it: each provider signs
or carries a secret on its webhook (Slack signing secret, Telegram secret token,
WhatsApp/Meta app secret, per-trigger tokens for generic webhooks). Keep the
relay host on its own subdomain so you can apply WAF/rate-limit rules
independently of the app.

## OAuth callbacks

When a user connects an integration (Google, Microsoft, Slack, GitHub…), the
provider sends the user's **browser** back to:

```
{BACKEND_URL}/integrations/oauth/callback
```

This is a **302 redirect in the user's browser**, *not* a server-to-server call
from the provider. So the callback URL only needs to be reachable by your
**users' browsers** — the same audience as the rest of the app. The subsequent
token exchange (backend → the provider's token endpoint) is **outbound** (see
[Outbound](/deployment/networking/outbound#integrations-oauth)).

Implications:

* If your users reach the app over a VPN, an internal `BACKEND_URL` works for
  OAuth — no public inbound required.
* `BACKEND_URL` must exactly match the redirect URI registered in each OAuth
  app, and the frontend `redirect_uri` must share the configured `FRONTEND_URL`
  origin (the backend validates this).
* If you can't expose an OAuth callback at all, fall back to **manual
  credential entry** (static API tokens) for providers that support it.
