# NangoHQ/nango

> Integration platform with a 1,000-provider YAML auth catalog, credential-injecting proxy, hosted TypeScript functions and MCP endpoints.

- Category: [API layer & connectors](https://llms-technical-reviews.com/connectors/)
- Repository: https://github.com/NangoHQ/nango (reviewed at commit `8da00015e150d812680151d82c4a8c1650f9687e`, 2026-10-06)
- Stars: 12539 · Language: TypeScript · License: Elastic-2.0
- Canonical page: https://llms-technical-reviews.com/p/nango/

## Overview

Nango is integration infrastructure for SaaS products and AI agents. It does three jobs. First, it owns the auth dance with third-party APIs: OAuth, API keys, basic auth, client credentials, JWT apps and more than a dozen other modes, all driven from one provider catalog. Second, it runs a credential-injecting proxy. Third, it hosts *functions*, TypeScript actions and syncs that you write against Nango's SDK and that run on Nango's infrastructure. Your backend or your agent then calls the Nango API, the Node client, or one of Nango's MCP endpoints, and never handles a refresh token itself.

The provider catalog is data, not code. `packages/providers/providers.yaml` is a 29,500-line file with roughly 1,000 entries. Each entry names the auth mode, the authorize and token URLs, the proxy base URL with `${connectionConfig.*}` templating, rate-limit headers, a pagination recipe, and optional hook scripts for webhooks and post-connection steps. Adding an API usually means adding YAML. Integration *logic* (which endpoints to sync, what an action does) is user-authored TypeScript that the CLI compiles and deploys.

The repository is a large Node/TypeScript monorepo with over 40 packages. It is licensed under the Elastic License 2.0, which is source-available but not OSI open source. It contains the whole cloud product, but the self-hosted Docker image is a deliberately reduced edition. That distinction matters more than anything else on this page (see *Running it*).

## Architecture

```mermaid
flowchart LR
  APP["Your backend / agent"] --> SRV["server (Express API, MCP, Connect UI)"]
  SRV --> PRX["ProxyService"]
  PRX --> API["Third-party API"]
  SRV --> ORC["orchestrator + scheduler (Postgres queue)"]
  JOBS["jobs (task processor)"] -->|"dequeue"| ORC
  JOBS --> FLEET["fleet: runner nodes"]
  FLEET --> RUN["runner: node:vm exec"]
  RUN -->|"nango.get / proxy"| SRV
  RUN --> PER["persist"]
  PER --> REC["records (Postgres)"]
  SRV --> DB["Postgres + Redis"]
```

| Component | Path | Role |
|---|---|---|
| API server | `packages/server` | Public/private REST API, OAuth callbacks, proxy, webhooks ingress, three MCP servers |
| Shared services | `packages/shared` | Connections, credential refresh, encryption, orchestrator client |
| Provider catalog | `packages/providers` | `providers.yaml` and `providers.scopes.yaml`, alias-resolving loader |
| Orchestrator | `packages/orchestrator`, `packages/scheduler` | Task/schedule service over Postgres (`FOR UPDATE SKIP LOCKED`) |
| Jobs | `packages/jobs` | Dequeues tasks and dispatches them to a runtime (runner fleet or Lambda) |
| Fleet | `packages/fleet` | Manages runner nodes on local processes, Render, Kubernetes or Lambda |
| Runner | `packages/runner`, `packages/runner-sdk` | Executes deployed function code; the `nango` object scripts call |
| Persist / records | `packages/persist`, `packages/records` | Writes sync records and logs; serves the record cache |
| CLI and clients | `packages/cli`, `packages/node-client`, `packages/connect-ui` | Author and deploy functions; call the API; embedded auth UI |

## How a request flows

Take an agent or backend calling `nango.triggerAction("hubspot", "user-42", "create-contact", input)`:

1. **API entry.** The public route resolves the environment and calls `executeAction`. That function loads the connection and the provider config, resolves the deployed function, refuses disabled ones, opens an activity-log context, and calls the orchestrator client ([action.service.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/services/action.service.ts#L42-L130)).
2. **Enqueue.** `triggerAction` builds a task named after the environment, connection and action. Async actions go into a group with `maxConcurrency: 1`, so they run one at a time per environment. Sync actions are scheduled and awaited ([orchestrator.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/shared/lib/clients/orchestrator.ts#L244-L330)). `executeAction` gives the task 30 seconds to start and 15 minutes to finish, then long-polls the task output ([client.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/orchestrator/lib/clients/client.ts#L244-L320)).
3. **Schedule.** The orchestrator stores tasks and schedules in Postgres. Due recurring syncs are picked with `FOR UPDATE SKIP LOCKED`, so several orchestrator instances can share the table ([scheduling.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/scheduler/lib/daemons/scheduling/scheduling.ts#L9-L26)).
4. **Dequeue.** In the `jobs` service, `OrchestratorProcessor` long-polls `dequeue` into a `PQueue` sized to its concurrency and reports failed tasks back ([processor.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/orchestrator/lib/clients/processor.ts#L61-L104)). `handler` branches on task type: sync, abort, action, function, webhook or on-event ([handler.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/jobs/lib/processor/handler.ts#L17-L52)).
5. **Pick a runtime.** `startScript` fetches the compiled script from local disk or remote storage and asks `getRuntimeAdapter` for a runner or Lambda adapter ([start.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/jobs/lib/execution/operations/start.ts#L13-L83), [runtimes.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/jobs/lib/runtime/runtimes.ts#L46-L57)). In production each team gets its own runner id, and if that runner cannot start the call falls back to a shared default runner ([runner.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/jobs/lib/runner/runner.ts#L39-L52)).
6. **Execute.** The runner wraps the CommonJS bundle and runs it in a `node:vm` context. `console` is a no-op, string and WASM code generation are off, and `require` is limited to `url`, `crypto`, `zod`, `botbuilder`, `soap` and `unzipper` ([exec.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/runner/lib/exec.ts#L106-L152)).
7. **Call the API.** Inside the script, `nango.get/post/proxy` goes back to the server's `ProxyService`. That service checks base-URL overrides and plan capping, loads the integration and connection, refreshes credentials if needed, and sends the request with retries ([proxy.service.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/services/proxy.service.ts#L136-L260)). For syncs, `batchSave` sends records to `persist`.
8. **Return.** The runner reports the output, the task becomes `SUCCEEDED`, and the waiting API call returns the action result.

## Key components

### Provider catalog

`loadProvidersYaml` reads the YAML once and resolves `alias:` entries by shallow-merging the parent with overrides. This is how sandbox and regional variants avoid copying whole entries ([index.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/providers/lib/index.ts#L40-L67)). A typical entry, HubSpot, declares `OAUTH2`, a `portalId` connection field, three hook scripts (post-connection, pre-deletion, webhook routing), a rate-limit header and a cursor pagination recipe ([providers.yaml](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/providers/providers.yaml#L12126-L12153)). The proxy and `nango.paginate()` read these fields, so most per-API behaviour is configuration.

### Credentials and refresh

Connection credentials are encrypted with AES-256-GCM in `EncryptionManager.encryptConnection`. If no encryption key is configured, `shouldEncrypt()` is false and credentials are stored as plain JSON ([encryption.manager.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/shared/lib/utils/encryption.manager.ts#L18-L55)). Refresh is guarded twice. Within one process, an in-flight map shares a single refresh promise. Across processes, a Redis lock is taken per connection with a 10-second TTL. The code's own comment notes that this lock is not safe across several Redis instances ([refresh.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/shared/lib/services/connections/credentials/refresh.ts#L444-L475)). Failed refreshes enter a cooldown, and connections are eventually marked `refresh_exhausted`.

### MCP surfaces

The server exposes three MCP endpoints:

- `/mcp` serves one connection's deployed actions as tools, selected with `connection-id` and `provider-config-key` headers.
- `/session/:sessionId/mcp` is for agent sessions.
- A separate management router exposes `/mcp` with tools for provisioning integrations, functions and syncs ([routes.public.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/routes.public.ts#L427-L430), [L478-L482](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/routes.public.ts#L478-L482)).

The agent-session server is the agent-oriented one. It registers four meta-tools: `nango_tool_search`, `nango_execute`, `nango_proxy` and `nango_create_connection`. It also registers every tool the session's toolset allows, but it pages `tools/list` at 50 entries. Searchable-but-unlisted tools stay callable by name or through `nango_execute` ([sessionServer.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/controllers/agent/mcp/sessionServer.ts#L15-L34), [L60-L154](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/controllers/agent/mcp/sessionServer.ts#L60-L154)).

### Webhooks

Incoming provider webhooks hit `routeWebhook`. It looks up the provider's `webhook_routing_script` in a table of dozens of per-provider handler files, lets the handler map the payload to connections and to syncs that have `onWebhook` logic, and then forwards a signed copy to your own webhook URL if your plan allows it ([webhook.manager.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/webhook/webhook.manager.ts#L25-L120)).

## Extending it

- **New API.** Add a `providers.yaml` entry (auth mode, URLs, proxy config, optional pagination and hook script names). There is no per-provider TypeScript unless the API needs a webhook router or a post-connection hook.
- **Functions.** `nango init`, `nango dev`, `nango dryrun` and `nango deploy` (in `packages/cli`) compile TypeScript syncs and actions written against `runner-sdk` and push them to an environment. "Zero-YAML" functions export a typed object instead of a `nango.yaml` declaration. Syncs use `batchSave`, `lastSyncDate` and `saveCheckpoint` for incremental runs.
- **Consumption.** The Node client covers connections, `proxy`, `triggerAction`/`triggerActionAsync`, `listRecords` with cursor pagination, and connect sessions. Agents can use the MCP endpoints instead.

## Running it

- **Nango Cloud** is the full product.
- **Self-hosted Docker** (`docker-compose.yaml`) runs `nango-server`, Postgres and Redis, with Elasticsearch optional for logs. It mounts `providers.yaml` into the container. The image deletes `packages/jobs`, `packages/runner` and `packages/persist` at build time ([Dockerfile.self_hosted](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/Dockerfile.self_hosted#L1-L12)). A Docker run counts as `isHosted`, and `flagHasScripts` is true only for local, enterprise, cloud and test ([detection.ts](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/utils/lib/environment/detection.ts#L8-L30)). The free self-hosted edition therefore gives you auth, the Connect UI and the proxy, but not syncs, actions or functions. Running those yourself requires the enterprise mode (`NANGO_ENTERPRISE`).
- **Local development** runs every service from the monorepo with `RUNNER_TYPE=LOCAL`, which spawns runner child processes.

## Strengths and caveats

- **Strength: breadth as data.** About 1,000 providers live in one reviewed YAML file. Pagination and rate-limit hints sit beside auth, so the proxy and the SDK get per-API behaviour for free.
- **Strength: production-grade auth plumbing.** Per-connection refresh locking, refresh backoff and exhaustion tracking, encrypted credentials, and a white-label Connect UI.
- **Strength: real job system.** A Postgres-backed scheduler with heartbeats, timeouts and retries, per-team runner fleets on Kubernetes, Render or Lambda, and durable records with cursor reads.
- **Caveat: self-host is auth + proxy only.** The open image strips the function runtime, so the "1,000 APIs for your agent" story needs Cloud or an enterprise deployment.
- **Caveat: `node:vm` is not a security boundary.** The runner context receives host-realm `Buffer`, `Error` and `setTimeout`. Isolation comes from running each team on separate runner nodes in production. Outside production, every team shares the `default` runner, and production falls back to it when a team's runner fails.
- **Caveat: plaintext fallback.** If you forget `NANGO_ENCRYPTION_KEY`, credentials are stored unencrypted.
- **Caveat: licence and size.** Elastic License 2.0 forbids offering Nango as a managed service. The codebase is large: dozens of packages, several datastores (Postgres, Redis, optional ClickHouse and Elasticsearch) and cloud-specific services in the same tree.

*Sources: code at 8da0001, OpenDeepWiki wiki (36 pages), verified Q&A.*

## How NangoHQ/nango answers the API layer & connectors questions

### How is third-party authentication implemented? (answered)

Nango supports 15 distinct `auth_mode` values, the most numerous being API_KEY (344 providers), OAUTH2 (304), OAUTH2_CC (106), BASIC (100), and TWO_STEP (68). The providers.yaml at the package root defines each provider's auth mode, credential schema, and token URLs — Zendesk Sell shows a typical OAUTH2 entry with `authorization_url`, `token_url`, `refresh_params`, and `proxy` config. **OAuth2 flows** follow the standard authorization-code grant; the server's OAuth controller in `oauth.controller.ts` handles the callback, stores tokens, and initiates refresh. **API keys and Basic auth** use dedicated handlers: `postApiKey.ts` validates an `apiKey` credential against the provider's schema while `postBasic.ts` handles username/password pairs. **Credential storage** encrypts artifacts via `node:crypto` HKDF + JWE AES-256-GCM in `crypto.ts`. The Data Encryption Key comes from the `DekRegistry` in `env.ts`. **Token refresh** runs through `refreshOrTestCredentials` invoked by the proxy service before every request, with a 60-second memoized TTL. **Multi-tenant connected accounts** are scoped by `connection_id` + `providerConfigKey`, resolved via `connectionService.getConnection()`. The **Connect UI** in `connect-ui/` provides a white-label embedded auth flow launched through `connectSession.service.ts` with session tokens.

> **Editor's note.** Correction: `oauth-server/lib/crypto.ts` (HKDF + JWE) encrypts the MCP OAuth server's own artifacts, not connections. Connection credentials are encrypted with AES-256-GCM by `EncryptionManager.encryptConnection` using NANGO_ENCRYPTION_KEY, and are stored in plaintext if no key is configured.

Citations: [packages/providers/providers.yaml:29015-29030](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/providers/providers.yaml#L29015-L29030) · [packages/oauth-server/lib/crypto.ts:17-56](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/oauth-server/lib/crypto.ts#L17-L56) · [packages/server/lib/services/proxy.service.ts:34-36](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/services/proxy.service.ts#L34-L36) · [packages/server/lib/services/proxy.service.ts:227-251](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/services/proxy.service.ts#L227-L251) · [packages/server/lib/controllers/auth/postApiKey.ts:54-80](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/controllers/auth/postApiKey.ts#L54-L80)

### How is an integration / connector defined? (answered)

Integrations are defined in **`packages/providers/providers.yaml`**, a 29,523-line YAML file declaring 1,034 providers. Each entry includes `display_name`, `categories`, `auth_mode`, OAuth endpoints (`authorization_url`, `token_url`, `refresh_params`), `proxy` configuration (`base_url` with `${connectionConfig.*}` templating, `headers`, `verification`), credential schemas, and connection config schemas. The YAML is loaded at runtime by `packages/providers/lib/index.ts` via `js-yaml`, with alias-based inheritance for provider variants. A separate `providers.scopes.yaml` defines OAuth scopes per provider. **Code vs config**: provider templates are static YAML; users create *integrations* ("provider configs") in a given environment, binding custom credentials. The `IntegrationService` class in `integration.service.ts` handles get/list/create/update/delete with rich error types. The `buildIntegrationConfig` helper transforms creation requests into `DBCreateIntegration` objects. **Scripts** (syncs/actions) are user-written TypeScript that can be bundled into nango-yaml format. The **nango-yaml** package's `parser.ts` parses v1/v2 format sync/action definitions. **Versioning** of deployed scripts is tracked via `DBFunctionConfigVersion` in the database. There is no OpenAPI codegen; integrations ship as curated YAML templates.


Citations: [packages/providers/providers.yaml:1-35](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/providers/providers.yaml#L1-L35) · [packages/providers/lib/index.ts:40-67](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/providers/lib/index.ts#L40-L67) · [packages/server/lib/services/integration.service.ts:152-250](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/services/integration.service.ts#L152-L250) · [packages/server/lib/controllers/v1/integrations/buildIntegrationConfig.ts:35-60](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/controllers/v1/integrations/buildIntegrationConfig.ts#L35-L60) · [packages/nango-yaml/lib/parser.ts:13-80](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/nango-yaml/lib/parser.ts#L13-L80)

### How are integrations exposed to LLM agents? (answered)

Integrations are exposed to LLM agents via **MCP (Model Context Protocol)** servers using `@modelcontextprotocol/server` and `@modelcontextprotocol/node`. The **Agent Session MCP server** (`sessionServer.ts`) registers every tool from the session's compiled toolset. Four meta-tools drive agent interaction: `nango_tool_search` (fuzzy search via Fuse.js — `agentSessionToolSearch.service.ts`), `nango_execute` (`execute/execute.ts`) to run any tool by name, `nango_proxy` for direct credential-injected API calls, and `nango_create_connection` to start auth flows. The **Management MCP server** (`managementServer.ts`) exposes 18 tools (list integrations, deploy functions, trigger syncs, proxy requests, etc.) for provisioning, authenticated by API key or OAuth2. Both servers support **OAuth2 authentication** via the OAuth server in `oauth-server/lib/provider.ts` using `oidc-provider` with PKCE required, rotating refresh tokens (`rotateRefreshToken`), and `resourceIndicators`. Tool `tools/list` paginates at 50 tools per page (`sessionServer.ts:142-151`). Tool search uses Fuse.js fuzzy matching across action name, description, integration, and provider names with stopword removal. The toolset policy (`agentSessionToolset.service.ts`) controls which integrations and actions are available per session, with `*` allowing all, explicit allow/deny lists, and pinned vs searchable tools.


Citations: [packages/server/lib/controllers/agent/mcp/sessionServer.ts:60-154](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/controllers/agent/mcp/sessionServer.ts#L60-L154) · [packages/server/lib/controllers/agent/mcp/execute/execute.ts:23-100](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/controllers/agent/mcp/execute/execute.ts#L23-L100) · [packages/server/lib/services/agentSessionToolSearch.service.ts:25-100](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/services/agentSessionToolSearch.service.ts#L25-L100) · [packages/server/lib/controllers/mcp/management.ts:16-62](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/controllers/mcp/management.ts#L16-L62) · [packages/server/lib/controllers/mcp/managementServer.ts:50-72](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/controllers/mcp/managementServer.ts#L50-L72)

### How is a tool call executed? (answered)

Tool calls execute through **`executeAction`** in `action.service.ts` which resolves the connection and function, then delegates to the orchestrator. The runner (`packages/runner/lib/exec.ts`) executes TypeScript in a **sandboxed `node:vm` context** — `require()` is restricted to only `url`, `crypto`, `zod`, `botbuilder`, `soap`, `unzipper`; `console` is a no-op proxy; `codeGeneration` disables `strings` and `wasm`. No container isolation is used. The **proxy** (`proxy.service.ts:136-370`) is the direct-execution path: it resolves the integration, loads/refreshes credentials, builds an `InternalProxyConfiguration` from the provider's `proxy.base_url` and `headers` template (interpolating `${apiKey}`, `${connectionConfig.*}`, etc.), then makes the HTTP request via axios. **Rate limiting** uses `ratelimit.middleware.ts` and a separate webhook ingress rate limiter. **Plan-level capping** is checked before every proxy request and webhook forward. **Retries** are configured per-request via the `retries` header, with `maxWaitMs` for backoff in the `ProxyRequest` class. **Error mapping** distinguishes upstream errors (returned as `upstream_error` with the provider's status) from infrastructure errors (`ProxyServiceError` with typed codes like `base_url_override_disabled`, `connection_not_found`, `credentials_refresh_failed`, `plan_limit`).

> **Editor's note.** Correction: beyond `node:vm`, functions run on separate runner nodes managed by the fleet package (local child processes, Render, Kubernetes or Lambda), with one runner per team in production and a shared default runner otherwise.

Citations: [packages/server/lib/services/proxy.service.ts:136-200](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/services/proxy.service.ts#L136-L200) · [packages/server/lib/services/proxy.service.ts:300-400](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/services/proxy.service.ts#L300-L400) · [packages/runner/lib/exec.ts:106-145](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/runner/lib/exec.ts#L106-L145) · [packages/server/lib/services/action.service.ts:42-80](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/services/action.service.ts#L42-L80) · [packages/server/lib/controllers/proxy/allProxy.ts:23-37](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/controllers/proxy/allProxy.ts#L23-L37)

### How are data sync, webhooks and triggers implemented? (answered)

Data syncs are built on the **Scheduler** package at `packages/scheduler/lib/scheduler.ts`, a cron-like task engine with states (CREATED, STARTED, SUCCEEDED, FAILED, EXPIRED, CANCELLED) and three daemons: `SchedulingDaemon` (evaluates schedules), `ExpiringDaemon` (expires stale tasks), `CleaningDaemon` (cleanup). **Scheduled syncs** are triggered via `postSyncStart.ts` (unpause a recurring sync) and `postTrigger.ts` (one-shot trigger with modes `incremental`, `full_refresh`, or `full_refresh_and_clear_cache`). Both call `syncManager.runSyncCommand`. **Incremental cursors** are implemented in `packages/records/lib/cursor.ts` as base64-encoded `last_modified_at||id` strings. The `records` package provides sync state tracking with upsert semantics. **Webhook ingestion** uses per-provider routing scripts in `packages/server/lib/webhook/` — 40+ scripts (HubSpot, Shopify, Salesforce, GitHub, etc.) that parse webhooks, verify signatures (`signature.ts`), and map to the provider's format. The `webhook.manager.ts:25-120` `routeWebhook` function looks up `webhook_routing_script` from providers.yaml, dispatches to the matching handler, then fans out via `dispatchWebhookExecutions`. Large webhook fan-outs use the **dispatch queue** (`dispatch-queue/publisher.ts`) backed by AWS SQS with batching (max 10 entries, 1MB).


Citations: [packages/scheduler/lib/scheduler.ts:22-80](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/scheduler/lib/scheduler.ts#L22-L80) · [packages/server/lib/controllers/sync/postTrigger.ts:13-60](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/controllers/sync/postTrigger.ts#L13-L60) · [packages/server/lib/webhook/dispatch.ts:59-80](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/webhook/dispatch.ts#L59-L80) · [packages/server/lib/webhook/webhook.manager.ts:25-120](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/webhook/webhook.manager.ts#L25-L120)

### How is it self-hosted and what is open vs proprietary? (answered)

Nango is licensed under the **Elastic License 2.0 (ELv2)** (`LICENSE:1-10`), which permits use, copy, distribution, and modification but prohibits providing the software as a hosted/managed service to third parties or removing license-key functionality. The **entire monorepo is open-source** — all packages (server, webapp, runners, oauth-server, providers, records, scheduler, sandbox) are in this repository. **Proprietary components** like WorkOS-based managed auth are feature-flagged and gated behind the cloud hosted service. **Self-hosting** uses Docker: `Dockerfile.self_hosted` starts from the base image and removes `jobs`, `runner`, `persist` packages (production runs them as separate services). The `docker-compose.yaml` requires **PostgreSQL** and **Redis**; **Elasticsearch** is optional for log storage. Dashboard authentication in self-hosted mode uses basic auth or local username/password (`clients/auth.client.ts:43-96`) via `NANGO_DASHBOARD_USERNAME`/`NANGO_DASHBOARD_PASSWORD` env vars, configurable with `FLAG_AUTH_ENABLED` and `AUTH_ALLOW_SIGNUP`. The server entrypoint is `packages/server/entrypoint.sh`. The providers.yaml must be volume-mounted. **What requires the cloud**: license key validation, managed auth (WorkOS/SSO/MFA), billing, and the hosted runner fleet — the self-hosted Dockerfile strips runner/jobs packages, implying sync/action execution runs in-process.

> **Editor's note.** Correction: the self-hosted image removes jobs/runner/persist because Docker mode disables scripts (`flagHasScripts` is false unless local, enterprise, cloud or test). Free self-hosting provides auth, Connect UI and proxy only; syncs and actions need Cloud or NANGO_ENTERPRISE.

Citations: [LICENSE:1-15](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/LICENSE#L1-L15) · [Dockerfile.self_hosted:1-12](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/Dockerfile.self_hosted#L1-L12) · [docker-compose.yaml:1-80](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/docker-compose.yaml#L1-L80) · [packages/server/lib/clients/auth.client.ts:43-96](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/clients/auth.client.ts#L43-L96) · [packages/server/lib/controllers/v1/account/signup.ts:90-93](https://github.com/NangoHQ/nango/blob/8da00015e150d812680151d82c4a8c1650f9687e/packages/server/lib/controllers/v1/account/signup.ts#L90-L93)
