Skip to Content

Architecture

The Native App is a hub-and-spoke deployment. The hub is a small Zeotap cloud control plane that handles browser auth and reverse-proxies UI traffic. The spokes are the SPCS services that run inside each customer’s Snowflake account. Each spoke is fully self-contained — it owns its metadata store, its event buffer, and the application-owned warehouse it executes SQL against — and never reaches across to another customer’s install.

SPCS service inventory

Zeotap runs four long-running SPCS services and a varying number of ephemeral job services inside your account.

ServiceRoleRuntime
zeotap_control_planeSame Zeotap API used by the cloud product. Serves UI requests forwarded by the cloud reverse-proxy, and accepts events on its /v1/* endpoints (track, identify, page, screen, group, batch) for the in-account event hot path.Long-running
zeotap_postgresMetadata store for workspaces, sources, syncs, audiences, identity graphs, orchestrations, and run history. Persists to an SPCS block volume.Long-running
zeotap_redisStreaming buffer for the event hot path. Decouples ingest from the forwarder so SDKs aren’t blocked on destination latency.Long-running
zeotap_event_forwarderBatch-consumes the streaming buffer and runs two lanes per batch: forwarding rules to destinations, and warehouse delivery to per-event-type tables (tracks, identifies, pages, …).Long-running
zeotap_job_*Ephemeral SPCS job services for sync runs, loaders, computed attribute computations, identity resolution, orchestration execution, and AI agent sessions.One-shot

Service-to-service connections

Service-to-service connections inside the SPCS compute pool

The control plane is the only service that talks to all the others. It reads and writes Postgres for metadata, publishes to Redis when it accepts incoming events on its /v1/* endpoints, and dispatches sync, loader, identity, computed attribute, orchestration, and agent work as one-shot SPCS job services. The forwarder consumes from Redis and writes both to destinations (through the bound EAIs) and to your Snowflake databases via ZEOTAP_ADMIN_WH, the warehouse the application provisions at install time.

Job services don’t have a long-running connection to the rest of the graph. When the control plane creates one, it passes the work payload through SPCS service arguments. The job posts run-state updates back to the control plane over HTTP (authenticated by a run-scoped JWT), and opens its own connections to ZEOTAP_ADMIN_WH for SQL execution and to whatever EAIs the work requires. When the job finishes, SPCS removes the service.

The application-owned warehouse

ZEOTAP_ADMIN_WH is created and owned by the application itself. The application requests CREATE WAREHOUSE in its manifest privileges; PROVISION_SERVICES issues CREATE WAREHOUSE IF NOT EXISTS ZEOTAP_ADMIN_WH WAREHOUSE_SIZE = XSMALL AUTO_SUSPEND = 60 AUTO_RESUME = TRUE at install time, and every SPCS service spec sets QUERY_WAREHOUSE = ZEOTAP_ADMIN_WH directly.

You can resize the warehouse from the setup app’s Warehouse settings card or by calling <APP_NAME>.APP_DATA.SET_WAREHOUSE_SIZE('<size>'). Valid sizes range from XSMALL through X6LARGE; the proc validates the value before issuing ALTER WAREHOUSE. Because the warehouse is application-owned, there is no consumer-side GRANT USAGE or GRANT OPERATE step and no customer_warehouse reference to bind.

The proxy chain

Browser-to-SPCS traffic takes four hops, each enforcing a different authentication contract.

Browser to SPCS proxy chain
  1. Browser → cloud. The user signs into composable.zeotap.com with email or Google. The UI sends an HTTPS request with a Firebase ID token in the Authorization header.
  2. Cloud → SPCS public ingress. The cloud control plane validates the Firebase token, looks up which Native App install the user’s workspace is bound to, fetches the encrypted keypair credential for that install, and re-signs the request with a JWT bearer based on the ${BRAND}_PROXY_USER keypair. It POSTs the original request body to the install’s SPCS public ingress URL.
  3. SPCS public ingress → control-plane container. Snowflake’s SPCS gateway terminates TLS, validates the JWT against the proxy user’s registered RSA public key, and forwards the request to the zeotap_control_plane service over the internal SPCS network.
  4. Control plane → Postgres / warehouse. The control plane processes the request locally — read the metadata, run a sync, query the bound database — and writes the response back through the same chain in reverse.

The cloud never persists Firebase tokens; it mints a JWT per request and discards it. The keypair private key never leaves the cloud’s encrypted credential store. The customer’s data never round-trips through the cloud — only API request bodies and response bodies pass through, and the cloud doesn’t store them.

Event ingestion data flow

Events take a separate path optimised for high throughput and at-least-once delivery.

  1. SDK to cloud ingest. Web, mobile, and server SDKs POST to events.zeotap.com/v1/track regardless of whether the workspace runs cloud or Native App mode. The cloud ingest validates the write key.
  2. Cloud ingest to in-account control plane. When the write key is bound to a Native App install, the cloud forwards the validated payload to that install’s zeotap_control_plane service through the same proxy chain used for UI calls — the /v1/* event routes share the same public ingress as the API.
  3. Control plane to Redis stream. The control plane authenticates the forwarded write key against the install’s local key store, enriches the payload with workspace metadata, appends it to a Redis stream, and returns 200 to the cloud, which returns 200 to the SDK. End-to-end latency from the SDK perspective is the proxy round-trip plus a Redis XADD.
  4. Forwarder fan-out. The forwarder polls the Redis stream and runs two lanes per batch:
    • Forwarding rules — real-time delivery to destinations through the bound EAIs. Server-side CAPI batchers, generic webhooks, and per-platform adapters all run in this lane.
    • Warehouse delivery — buffered batched INSERTs to CDP_EVENTS.RAW_EVENTS in the database your destination targets. Same buffer policy (size and time triggers) as cloud-mode warehouse delivery, but the write happens through the in-process Snowflake driver session — no Avro, no GCS staging, no COPY INTO.
  5. At-least-once with messageId dedup. SDKs already de-dup their own retries by messageId. Custom server-side integrations should set a stable messageId per event so the forwarder’s idempotent destination writes can de-dup retries downstream.

Schemas Zeotap creates

In each Snowflake database you configure as a source or destination:

SchemaContains
CDP_EVENTSRAW_EVENTS table holding all events the forwarder writes.
CDP_PLANNERPlan tables for sync orchestration (a/b slot rotation for diff-based CDC).
CDP_AUDITAudit tables for sync runs and pipeline observability.

Inside the application’s own footprint (<APP_NAME>.APP_DATA):

ObjectContains
APP_VERSIONThe currently installed application version.
CLOUD_REGISTRATIONThe cloud-issued install ID, claim URL, and registration status. Written by the SPCS control-plane service when the cloud handshake completes.
CLOUD_CREDENTIALSThe keypair private key minted by GENERATE_PROXY_KEYPAIR, plus the proxy user/role names and the cloud-side register_secret.
REFERENCE_BINDINGSThe EAI references bound by the application — populated by REGISTER_REFERENCE_CALLBACK when Snowsight reports a Grant event from the Configure tab.
REGISTRATION_REQUESTSingle-row queue the setup app writes to when you click Register with Zeotap. The SPCS control-plane service watches this table and performs the cloud handshake through its zeotap_sf_api_access EAI binding.

The application schemas are inside the app sandbox — only the application’s own procedures can read them, and they’re invisible to your other Snowflake roles.

Why the SPCS control plane drives cloud registration

Cloud registration runs as a goroutine inside the zeotap_control_plane SPCS service rather than as a stored procedure. The reason is structural: Snowflake’s marketplace review accepts EAIs provisioned only through the Permissions SDK consent flow, and that flow binds USAGE through manifest references rather than directly on the integration object. Stored procedures with EXTERNAL_ACCESS_INTEGRATIONS = (<literal EAI name>) fail at call time under that model, but SPCS service specs support EXTERNAL_ACCESS_INTEGRATIONS = (REFERENCE('zeotap_sf_api_access')). Routing the registration handshake through the SPCS service is the only marketplace-compliant path for the outbound call.

The setup app’s Register with Zeotap button calls APP_DATA.REQUEST_REGISTRATION(<your-email>), which writes a single row to APP_DATA.REGISTRATION_REQUEST. The control-plane service polls that table, signs the body with the keypair from APP_DATA.CLOUD_CREDENTIALS, POSTs to the cloud’s /api/v1/native-apps/register endpoint through the EAI reference, and writes the cloud-issued claim URL into APP_DATA.CLOUD_REGISTRATION. The setup app polls both tables and surfaces the URL as soon as it appears — typically within three to six seconds.

The flow is idempotent: re-running REQUEST_REGISTRATION is safe, and the cloud’s re-registration branch updates ingress_url, auth_credential, and status in place using the stored register_secret for signing. Useful after rotating the keypair or recovering from a transient failure.

Why this shape

The split between long-running services and ephemeral job services is what keeps the steady-state SPCS bill predictable. The four long-running services use a fixed compute pool node; sync, loader, identity, and computed attribute work — which is bursty and concurrent — fans out to job services that exit when the run completes. The block volume on Postgres is the only persistent storage; everything else is rehydrated from Postgres on service restart.

The reverse-proxy chain rather than a direct browser-to-SPCS connection is what lets Zeotap ship the same UI to cloud and Native App customers. The browser doesn’t know whether the workspace it’s looking at runs in the cloud or in an SPCS install — both look like normal composable.zeotap.com API calls.

The application-owned warehouse keeps the install self-contained. Earlier versions bound a consumer-supplied customer_warehouse reference, which required the consumer to pre-create a warehouse, bind it in the application’s Set up tile before provisioning could proceed, and run a separate GRANT USAGE, OPERATE ON WAREHOUSE ... TO APPLICATION block. Owning the warehouse internally removes those steps and lets the application self-manage size (SET_WAREHOUSE_SIZE), auto-suspend, and lifecycle. The consumer still pays for the credits the warehouse consumes — billed against their Snowflake account — but does not have to provision or grant anything to make it run.

Last updated on