control-plane.popsink.com, or your own.
The interesting question for a security review is not the diagram, it is the
wire: what actually crosses between them. This page answers it field by
field.
Who owns what
Control plane
Organizations, users, environment memberships, deployment registry,
licensing, usage and billing. It is the source of truth for identity —
accounts and roles are defined here, never in the data plane.
Data plane
Pipelines, connectors, datamodels, subscriptions, the broker, the schema
registry and every connector worker pod. It is the only place your
records exist — the control plane never sees them.
Direction of travel
The data plane always initiates. The control plane never calls in.
The only apparent exception is the browser: the control plane issues HTTP 302
redirects that send the user’s browser to your
ingressUrl (login return,
connector OAuth callbacks). That is a redirect the browser follows, not a
connection the control plane opens.
The channels
Five distinct exchanges, all authenticated with the deployment JWT:
Only one data-plane replica performs the periodic ones — a PostgreSQL advisory
lock elects the sender, so scaling
replicaCount does not multiply heartbeats.
Authentication
ThedeploymentJwtToken from the onboarding wizard is sent verbatim as
Authorization: Bearer <jwt> on every call above. It is an HS256 token whose
claims are just the deployment’s identity:
403 if the deploymentId in the path does not match — a stolen token
cannot be pointed at someone else’s deployment.
The heartbeat
The heartbeat is the main channel. It is a status report and the trigger for config reconciliation, in a single round trip every ten minutes.What the data plane sends
worker_images reports which connector images your workers actually run, so the
control plane can tell you a worker is behind the chart:
worker_images, data_plane_version and
supported_env_roles alike: an omitting heartbeat never clears what the control
plane already stored, so a transient read failure cannot look like a regression.
supported_env_roles is a capability declaration. A deployment that has never
reported it is assumed to understand only admin and user — which is why
assigning the reader role to someone on an older data plane silently grants
write access instead. Upgrade the data plane before relying on reader.entity_snapshot is a flat inventory of your pipelines, stored against the
deployment and used by the Snowflake marketplace integration. Per object it
carries identifiers, the user-chosen name and a state — and nothing else:
Note what is absent: no connector configuration, no host, no credentials, no
schema, no column list, no transform code.
What the control plane answers
Licensing
The control plane computes the verdict; the data plane enforces it. The control plane never blocks anything itself. The verdict is cached in the data-plane database so every replica shares it, and it defaults tofalse — an older or misbehaving control plane can never imply
“licensed”. Enforcement is deliberately forgiving:
When it blocks, the data plane answers
403 with {"code": "license_inactive"}.
The grace period is LICENSE_GRACE_PERIOD_SECONDS, and enforcement ships dark
unless LICENSE_ENFORCEMENT_ENABLED is on.
The practical reading for an operator: a control plane you cannot reach does not
take your pipelines down. Heartbeats fail, the deployment shows
offline, config
stops reconciling and new SSO logins fail — but data keeps moving, and the
cached license verdict holds for a week.Deployment status
The control plane derives your deployment’s status entirely from heartbeat arrival. There is no probe.
A self-hosted deployment starts
offline: the control plane provisions
nothing and can only wait for you to run helm install. That is why the
self-hosted wizard sits on Awaiting connections… until the first heartbeat
lands, then flips the deployment to Live.
Heartbeats go out every 10 minutes. The control plane sweeps every
5 minutes and marks a deployment
offline when the last heartbeat is older
than 13 minutes — so a single missed heartbeat is tolerated, two are not.helm upgrade, allow up to a
minute for the status to return to Live, and expect a brief offline window
if a rollout takes longer than 13 minutes.
Because the heartbeat reports data_plane_version, the control plane can compare
what you run against the chart version it publishes as supported — which is why
the install wizard never hardcodes a chart version and you should not pin one
either. See Upgrading a fleet.
Config sync
Environments, members and users flow control plane → data plane, never the other way. The control plane owns them; the data plane holds a replica. Reconciliation is hash-based rather than push-based:1
Both sides hash the same structure
The data plane rebuilds its local environment/member config and hashes it.
The control plane hashes its own. The two builders are deliberately
identical — same field names, same role vocabulary — so the hashes converge.
2
The heartbeat compares them
The data plane sends its
config_hash; the response carries the control
plane’s and config_sync_required.3
On drift, the data plane pulls
GET /deployment/{deploymentId}/config returns the full environment list
with members and nested user records. The data plane applies it and deletes
environments the control plane no longer lists.4
Persistent drift backs off
If the same hash pair repeats, the re-sync is skipped for a few ticks rather
than looping every cycle. Any change to either hash resumes it immediately.
Usage metrics
Every 30 seconds the data plane pushes aggregated CDC counters, which is what the control plane’s Usage tab and billing are computed from:from_ts / to_ts are epoch milliseconds, and events are counts per
operation code — read (snapshot), create, update, delete — emitted sparsely,
so a key absent means zero. Row contents are not part of the payload, and there
is no field that could carry one. The id is generated by the data plane and
reused as the primary key on ingest, so a retried batch is idempotent rather
than double-counted.
This is the sole input to the control plane’s Usage tab and to billing; there is
no second metering channel.
There is no infrastructure telemetry in this channel: no CPU, memory or disk
metrics, no log shipping, no stack traces, no per-request tracing. If you want
OpenTelemetry, it is a separate opt-in export to a collector you configure
via
OTEL_EXPORTER_OTLP_ENDPOINT — it does not go to the control plane.Identity and login
In self-hosted mode the control plane is the identity provider. This is a design choice worth understanding before an install, because it means user login depends on control-plane reachability.
These three calls are the only ones that carry a user email outbound
(
navigation, generate-switch-token, PATCH /deployment-users). Emails flow
predominantly inbound, because the control plane is where accounts live.
Do not confuse the two tokens involved:
The user access token is what the control plane hands the browser after SSO; the
data plane verifies it and exchanges it for its own session. It carries the
user’s id, email, target
deployment_id and whether they are an org admin.
Connector OAuth brokering
For OAuth-based connectors (HubSpot, Google Ads, Salesforce…), the control plane holds the providerclient_secret and the registered redirect_uri. Your data
plane never registers an app with the provider and never holds the provider
secret.
The cost of that arrangement is explicit: the connector’s OAuth refresh token
transits the control plane on every refresh, because the control plane is the
only party that can exchange it. The refresh token originated at the control
plane in the first place — it is handed to the data plane at grant time, and
posted back to /oauth/{provider}/refresh to obtain a short-lived access token.
What never leaves the data plane
Verified against every outbound payload builder:
Two caveats we would rather state than have you discover in an audit:
If either is unacceptable for your data classification, name your tables,
schemas and pipelines accordingly, or run in standalone mode.
Standalone: no control plane at all
Every exchange on this page is gated ondeploymentMode: SELF_HOSTED. With
deploymentMode: STANDALONE, the data plane makes zero calls to any control
plane — the heartbeat and metrics tasks return immediately at startup, and no
deploymentJwtToken is required. This is the mode behind air-gapped
installations.
The trade-offs are real and worth naming:
Air-gapped deployments
How offline, signed update bundles work.
Network requirements checklist
If the deployment stays
offline, the cause is almost always one of the first
two rows plus a wrong deploymentId / deploymentJwtToken — the full
symptom-to-cause table is on the
partners page.
Further reading
Deployments and environments
How to map deployments onto your regions and networks.
Install a deployment
Create the deployment in the control plane, then install the chart.
Deployment options
SaaS, BYOC, on-prem and air-gapped compared.
Security & compliance
Encryption, audit logging, certifications.