Expedition. Free, virtual, Nov 3–6.

Technical tracks for practitioners, outcomes for leaders.

Snowflake for Developers/Developer Blog/Turning Telemetry into a Data Product Your Teams and Customers Actually Use

Turning Telemetry into a Data Product Your Teams and Customers Actually Use

Secure Data Sharing
Anusha Challa

If you are a SaaS provider, your telemetry data answers questions that your teams and your customers keep asking. The hard part is getting it to them with governance in place. Today most SaaS providers deliver telemetry through export APIs, dashboards, and files, and each of these approaches has limits. Internal teams wait in a queue for extracts from whichever team owns the pipeline. Your customers build and babysit pipelines against your export API, dealing with pagination, rate limits, retries, schema changes, and long backfills. Dashboards stop at your boundary. A customer can look at their usage but can't join it to data only they have, such as their HR roster, cost centers, asset registry, support tickets, or security incidents. And governance ends at delivery, because once a customer downloads an extract or file, you generally can't revoke that copy, it doesn't update when the source data changes, and your visibility into how it is used is limited.

This blog post shows how Snowflake Secure Data Sharing changes that. Because Snowflake separates compute from storage by design, consumers in the same region can query shared data in place without a separate copy in their account. You publish the data products, your consumers query them with their own compute, and you keep the governance controls on who sees what.

Unlike the other delivery methods, a data product built for sharing doesn't have to be raw data only. Alongside the data itself (whether standard tables or open-format lake tables), you can share governed metric definitions so everyone calculates KPIs the same way, agents that answer questions in plain English (sharing agents is in preview; the agent's semantic views and other tools must be shared too, in the same database, and only some tool types are supported), search services over documents and logs, secure user-defined functions, whose definitions are hidden from users who don't own them, and notebooks with prebuilt analysis (Declarative Sharing). Instead of handing over rows that every consumer has to model and interpret on their own, you deliver ready-to-use answers to the most common questions.

This post walks through building the telemetry data product with shared metric definitions and an agent on top, and publishing it for both your internal teams and your external customers.

Today, raw telemetry reaches internal teams through extract queues and customers through export APIs, dashboards, and files that leave your platform. With Snowflake Secure Data Sharing, telemetry is stored once and published as a data product with governed data, metric definitions, and agents, through an organizational listing for internal teams and a private listing for customers, while you keep governance.

1. The business problems telemetry can solve

Telemetry is the data software records automatically as people and other systems use it. The fastest way to judge the value of a telemetry data product is to list the decisions it would improve. Helio started by asking each team, and a handful of customers, what they would do differently if this data were ready to query tomorrow. The answers fell into two groups. The tables below are illustrative examples of decisions a telemetry data product may support; results depend on your implementation and the data available.

Value for your internal teams

TeamBusiness problem todayWhat changes with a telemetry data product
Product managementRoadmap debates run on anecdotes; nobody can say which features drive retentionFeature adoption by customer segment and plan, tied to renewal outcomes. Deprecation decisions backed by real usage.
Customer successChurn is discovered at renewal time, when it is too lateEarly warning: falling logins, abandoned workflows, rising errors, scored per account so CSMs can decide where to follow up
SupportEngineers ask customers for screenshots and steps to reproduceThe event sequence for the affected session, available to query while the ticket is open
EngineeringRollback decisions are judgment calls made under pressureCrash and latency trends by release cohort, so "roll back or not" has evidence
SalesExpansion opportunities are found by guessworkAccounts near plan limits, teams adding licenses quickly, trials that behave like the ones that converted
FinanceUsage-based invoices get disputed with no shared evidenceConsumption metering traceable to the same events the product emitted
Security and complianceAudit requests trigger manual log pullsAuthentication anomalies and access evidence across customers, queryable without a manual log pull

Value for your customers

Customer roleBusiness problem todayWhat changes with a telemetry data product
IT and procurementThey pay for 5,000 user licenses and don't know which licenses are actually usedUsage joined to their HR roster and cost centers to find unused licenses and inform renewal planning
Adoption and change managementThe product is rolled out; they need data to prove adoption and the benefits of that adoptionAdoption by department and office over time; which teams need a second round of training
IT operationsUsers complain the tool is slow; it is hard to tell whether the cause is the vendor or the networkLatency and error rates by office, device, and OS build, compared with their own network and asset data
SecurityIncident investigations stall waiting for the vendor to send logsAuthentication and activity events, queryable in their own account during an incident
Vendor managementSLA conversations rely on the vendor's own reportsTheir own copy of the events to review alongside other evidence in SLA discussions

The two tables show how internal and external teams can benefit from telemetry data. Internal teams ask "how is our product doing across all customers?" Customers ask "how is this product doing for us?" The questions differ, so this post shows how to package two data products from the same underlying telemetry, one for each audience.

2. Turning telemetry into a data product for internal and external consumers

In Snowflake, a data product is packaged using a listing, which wraps the governed data and objects with a title, description, usage terms, and target audience, and publishes it so consumers can discover and install it. There are three types of listings:

  • Organizational listing: visible only inside your Snowflake organization. Teams discover it in the Internal Marketplace and request access through an approval workflow. Use this for internal consumers.
  • Private listing: visible only to the specific consumer accounts you name. Use this for individual customers.
  • Public listing: visible to anyone on the Snowflake Marketplace. Use this to reach a broad audience or to monetize the product.

This post uses organizational listings for internal teams and private listings for customers. Helio packages two products from the same telemetry dataset:

Fleet Intelligence, an organizational listing for internal teams. "Fleet" here means all customers combined.

  • All-customer events with access scoped by account and role.
  • A semantic view with fleet-wide metric definitions. A semantic view defines business metrics, dimensions, and how tables relate once, so every SQL query and every agent computes "weekly active accounts" the same way. Here it covers weekly active accounts, crash rate per release, and feature adoption by plan.
  • A natural-language agent for business teams who don't write SQL.

Product Insights, a private listing for each customer.

  • That customer's telemetry data only, filtered by an entitlement join inside a secure view. The join decides which rows each customer can see, and marking the view secure hides its definition from consumers.
  • A semantic view with customer-scoped metrics (active users, license utilization, error rate by office).
  • A natural-language agent that answers each customer's questions.

3. Architecture

Delivering one dataset to many tenants is hard no matter what tool you use. Three problems show up every time. Each tenant should see only its own rows, data shouldn't land in regions where nobody needs it, and the data you move to another region should be limited to what consumers there need. The design below is one way to address each of those.

The data products are built from one telemetry table in the SaaS provider's (Helio's) primary Snowflake account, in AWS us-west-2. Secure views on top of that table define what each audience may see. Internal teams get an unfiltered fleet view that combines data for all customers. External customers get a customer view that returns only the rows belonging to whichever customer account is querying.

Consumers in the same region query these views in place with their own compute, without a separate copy in their account. Consumers in other regions or clouds are served by Cross-Cloud Auto-Fulfillment, which replicates the data underlying a data product into each region where it has consumers and refreshes it on the schedule you set. What gets replicated is decided by what the share references. With the default object-level mode, the objects granted to the share and every object they reference are transferred.

If the customer view sits directly on the full events table, every customer's telemetry is copied into every region that has consumers, whether or not that customer operates there, even though the view filters each customer's query results. The filter in the view controls what a consumer can query, not what gets copied. To avoid that, the telemetry is separated by region with dynamic tables. A dynamic table is a table defined by a query; Snowflake refreshes it according to the target lag and refresh mode you configure, and when the query supports incremental refresh, it processes changes rather than recomputing the whole result. Each remote region gets its own database, filtered to the customers whose entitlements name that region, published as its own listing. Validate the replicated data and the entitlement lifecycle (multi-region customers, reassignment, offboarding) for your deployment. In this design, the entitlement join filters what each consumer can query, while the regional tables and listing configuration determine which data is replicated to each region. Each regional listing can also have its own refresh schedule.

The rest of this section covers the options for getting telemetry into Snowflake, and how to share it internally and externally once it is there.

Cataloging telemetry in Snowflake

Everything downstream starts from a governed telemetry dataset, and there are several ways to make telemetry queryable in Snowflake. The right one depends on where the telemetry lives today. This post covers the two most common sources.

Telemetry from product services, devices, and event streams such as Kafka reaches the Helio primary account through Snowpipe, Snowpipe Streaming, the Kafka connector, Openflow, or a catalog-linked database over Iceberg tables.

If telemetry events arrive as a stream, Snowpipe Streaming writes rows straight into a table with low latency and no files in between. The Snowflake Connector for Kafka does the same from Apache Kafka® topics, and Openflow provides managed connectors for other sources. If services already write batches of JSON or Apache Parquet™ files to cloud storage, Snowpipe loads each file as it lands. Either way, the result is a standard Snowflake table that the rest of this architecture uses unchanged.

For telemetry that already sits in a data lake as Apache Iceberg™ tables managed by an external catalog, there is no need to copy it to Snowflake. Set up a catalog integration and create a catalog-linked database. Snowflake syncs with the external catalog, discovers its namespaces and tables, and registers them, while the data stays in the lake's storage. Snowflake can share those tables through listings. Cross-Cloud Auto-Fulfillment reads from your storage and replicates the tables into consumer regions as Snowflake-managed Iceberg tables.

Sharing telemetry as a product within the organization

In the Helio primary account in AWS us-west-2, telemetry events feed a fleet view for internal teams and a customer view for customers. Same-region consumers read the shares directly, and consumers in other regions receive them through cross-region auto-fulfillment.

The organizational listing is visible only inside the company's organization. Each team's account sees it in the Internal Marketplace; access is granted by account and role, and teams without access can request it. Teams in other regions are covered by auto-fulfillment for organizational listings, which creates a copy in a team's region the first time someone there requests the listing (Section 4, step 7).

Sharing telemetry as a product externally to customers

Telemetry events and private entitlements in the Helio primary account feed a home-region customer view plus regional datasets for AWS us-east-1 and Google Cloud us-central1. Each regional dataset is auto-fulfilled only to its own region, and entitlements are never shared.

Every customer receives the same private listing, and the customer view checks who is asking, so the view scopes each query to the rows entitled to the querying account. Test this for each account before use. Adding a customer means adding an entitlement row and a listing target, not building new objects. Customers can also capture changes (change data capture, or CDC) with a stream on the shared view (Section 4, steps 6 and 7).

4. How to set this up

The steps below assume that the telemetry table, the entitlements table, the two secure views, the semantic views, and the agents already exist. If you would like to follow along in a sandbox account, download the setup script; it creates all of those objects and loads fabricated telemetry for six customers (the five examples below plus CUST_001), which it maps to your own account so you can see the filter work. All identifiers are illustrative.

Step 0: Create a least-privilege provider role

An administrator runs the grants below once, and from then on a dedicated provider role creates and owns every database, view, share, and listing in the data product. Keeping ownership in one role also means the people who publish the product can't change anything else in the account.

-- Run once as ACCOUNTADMIN.
CREATE ROLE IF NOT EXISTS TELEMETRY_PROVIDER;
GRANT ROLE TELEMETRY_PROVIDER TO USER <data_product_owner>;

GRANT USAGE, OPERATE ON WAREHOUSE TELEMETRY_WH            TO ROLE TELEMETRY_PROVIDER;
GRANT CREATE DATABASE                 ON ACCOUNT           TO ROLE TELEMETRY_PROVIDER;
GRANT CREATE SHARE                    ON ACCOUNT           TO ROLE TELEMETRY_PROVIDER;
GRANT CREATE LISTING                  ON ACCOUNT           TO ROLE TELEMETRY_PROVIDER;
GRANT CREATE ORGANIZATION LISTING     ON ACCOUNT           TO ROLE TELEMETRY_PROVIDER;
GRANT MANAGE LISTING AUTO FULFILLMENT ON ACCOUNT           TO ROLE TELEMETRY_PROVIDER;
GRANT DATABASE ROLE SNOWFLAKE.SHARING_USAGE_VIEWER         TO ROLE TELEMETRY_PROVIDER;

Privilege requirements change over time, so check each one against the current references for listing privileges, data sharing privileges, and CREATE SEMANTIC VIEW, and grant only what your listing types need. What each grant is for:

  • Warehouse USAGE and OPERATE run the queries and dynamic table refreshes without giving the role ownership of the warehouse.

  • CREATE DATABASE lets the role create TELEMETRY_DB and the regional databases, so it owns every object inside them, including semantic views and agents.

  • CREATE SHARE, CREATE LISTING, and CREATE ORGANIZATION LISTING cover Steps 3 to 5. MANAGE LISTING AUTO FULFILLMENT is needed for listings that reach other regions.

  • SHARING_USAGE_VIEWER gives read access to the listing usage and cost views in Section 5.

If your telemetry comes from a catalog-linked database, the administrator also creates the catalog integration and external volume, then grants the role USAGE on that database and SELECT on its tables.

Step 1: Set up a secure view with entitlements for tenant isolation

The secure view is what separates one customer's rows from another's. Helio publishes a single view for all customers, rather than one view or one table per customer. That view decides, row by row, which data each customer may see. It needs two pieces:

  • The telemetry events table holds events for every customer, each tagged with a customer_id.
  • A private entitlements table says who may see what. Each row links one customer_id to the Snowflake account that is entitled to that customer's telemetry, plus an is_active flag. The table lives in a private schema and is never added to a share, so customers can't read or change it.

For the five example customers, the entitlements table looks like this:

SELECT * FROM TELEMETRY_DB.PRIVATE.ENTITLEMENTS
WHERE customer_id <> 'CUST_001' ORDER BY customer_id;

CUSTOMER_ID | CONSUMER_ACCOUNT            | CONSUMER_REGION  | IS_ACTIVE
------------+-----------------------------+------------------+----------
CUST_002    | CUSTOMER_A_ACCOUNT_LOCATOR  | AWS_US_WEST_2    | TRUE
CUST_003    | CUSTOMER_B_ACCOUNT_LOCATOR  | AWS_US_WEST_2    | TRUE
CUST_004    | CUSTOMER_C_ACCOUNT_LOCATOR  | AWS_US_EAST_1    | TRUE
CUST_005    | CUSTOMER_D_ACCOUNT_LOCATOR  | GCP_US_CENTRAL1  | FALSE
CUST_006    | CUSTOMER_E_ACCOUNT_LOCATOR  | GCP_US_CENTRAL1  | TRUE

CONSUMER_ACCOUNT holds the value that CURRENT_ACCOUNT() returns in each customer's account, which is its account locator. CONSUMER_REGION is used by the regional dynamic tables in step 2 to decide which region each customer's data is copied to. A customer with several Snowflake accounts gets one row per account.

The secure view joins the telemetry events table to the entitlements table using the key condition ent.consumer_account = CURRENT_ACCOUNT(). Inside a shared view, CURRENT_ACCOUNT() returns the account running the query, which is the customer's own account, not Helio's (Use secure objects to control data access). The account identity comes from Snowflake, so a customer can't pass in a different value.

Here is how the filter plays out for the same query against the same view:

  • Customer A queries it. CURRENT_ACCOUNT() returns CUSTOMER_A_ACCOUNT_LOCATOR. Only the CUST_002 row matches, so the join keeps only events where customer_id = 'CUST_002'.
  • An account with no active entitlement queries it, for example Customer D, whose IS_ACTIVE is FALSE. No active entitlement row matches, so the view returns zero rows.

Because the filtering happens inside the view, Helio maintains one definition for every customer. Onboarding a customer means inserting an entitlement row and adding the account as a listing target. In the home-region view, offboarding takes effect on the next query after is_active is set to false. Regional views read entitlements from a dynamic table, so there the change takes effect after the next dynamic table refresh, within its target lag, and in other regions after the following listing refresh. It is recommended to test the secure views for onboarding and offboarding new customers.

-- Provider account. The core of the customer view created by the setup script.
CREATE OR REPLACE SECURE VIEW TELEMETRY_DB.PUBLISH.V_CUSTOMER_EVENTS AS
SELECT e.event_id, e.event_timestamp, e.device_id, e.event_type,
       e.metric_name, e.metric_value,
       e.attributes:app_version::STRING AS app_version,
       e.attributes:office::STRING      AS office
FROM TELEMETRY_DB.RAW.EVENTS e
JOIN TELEMETRY_DB.PRIVATE.ENTITLEMENTS ent
  ON  ent.customer_id      = e.customer_id
  AND ent.consumer_account = CURRENT_ACCOUNT()
  AND ent.is_active;

Practical notes:

  • The join isolates customers; SECURE doesn't. Marking the view secure hides its definition and limits what the optimizer can expose. The CURRENT_ACCOUNT() join is what filters the rows, so treat it as the boundary and put it under change control.
  • Test both directions before every release. Set the SIMULATED_DATA_SHARING_CONSUMER session parameter to the value stored in consumer_account (the account locator that CURRENT_ACCOUNT() returns) to preview what that account would see. Confirm that each test account returns only its entitled rows and that an unentitled account returns none.
  • Review fields before sharing them. Semi-structured telemetry often contains IP addresses, usernames, and file paths. Expose a reviewed list of fields, as the view above does, rather than the raw attributes payload.
  • Filtering by account doesn't control where data is replicated. The CURRENT_ACCOUNT() join decides what a customer sees. The regional dynamic tables in step 2 decide which regions the data is copied to.

Step 2: Build a regional dataset for each remote region

For each region outside the provider's own, create a database for that region's product. The entitlements table carries a consumer_region column (a region name from SHOW REGIONS, such as AWS_US_EAST_1). One dynamic table keeps the reviewed columns for customers with consumer accounts in that region; a second keeps that region's entitlement rows, so the regional customer view can filter by account without referencing another database.

CREATE DATABASE TELEMETRY_AWS_US_EAST_1;
CREATE SCHEMA   TELEMETRY_AWS_US_EAST_1.PUBLISH;

-- Telemetry for customers whose consumer accounts are in AWS us-east-1.
CREATE OR REPLACE DYNAMIC TABLE TELEMETRY_AWS_US_EAST_1.PUBLISH.DT_EVENTS
  TARGET_LAG   = '1 hour'
  WAREHOUSE    = TELEMETRY_WH
  REFRESH_MODE = INCREMENTAL
AS
SELECT e.event_id, e.event_timestamp, e.customer_id, e.device_id, e.event_type,
       e.metric_name, e.metric_value,
       e.attributes:app_version::STRING AS app_version,
       e.attributes:office::STRING      AS office
FROM TELEMETRY_DB.RAW.EVENTS e
JOIN (SELECT DISTINCT customer_id
      FROM TELEMETRY_DB.PRIVATE.ENTITLEMENTS
      WHERE consumer_region = 'AWS_US_EAST_1' AND is_active) r
  ON r.customer_id = e.customer_id;

-- That region's entitlement rows. Not shared; used only by the view below.
CREATE OR REPLACE DYNAMIC TABLE TELEMETRY_AWS_US_EAST_1.PUBLISH.DT_ENTITLEMENTS
  TARGET_LAG   = '1 hour'
  WAREHOUSE    = TELEMETRY_WH
  REFRESH_MODE = INCREMENTAL
AS
SELECT customer_id, consumer_account
FROM TELEMETRY_DB.PRIVATE.ENTITLEMENTS
WHERE consumer_region = 'AWS_US_EAST_1' AND is_active;

CREATE OR REPLACE SECURE VIEW TELEMETRY_AWS_US_EAST_1.PUBLISH.V_CUSTOMER_EVENTS AS
SELECT d.event_id, d.event_timestamp, d.device_id, d.event_type,
       d.metric_name, d.metric_value, d.app_version, d.office
FROM TELEMETRY_AWS_US_EAST_1.PUBLISH.DT_EVENTS d
JOIN TELEMETRY_AWS_US_EAST_1.PUBLISH.DT_ENTITLEMENTS ent
  ON  ent.customer_id      = d.customer_id
  AND ent.consumer_account = CURRENT_ACCOUNT();

-- GCP us-central1: the same statements against its own database.
CREATE DATABASE TELEMETRY_GCP_US_CENTRAL1;
CREATE SCHEMA   TELEMETRY_GCP_US_CENTRAL1.PUBLISH;

-- Telemetry for customers whose consumer accounts are in GCP us-central1.
CREATE OR REPLACE DYNAMIC TABLE TELEMETRY_GCP_US_CENTRAL1.PUBLISH.DT_EVENTS
  TARGET_LAG   = '1 hour'
  WAREHOUSE    = TELEMETRY_WH
  REFRESH_MODE = INCREMENTAL
AS
SELECT e.event_id, e.event_timestamp, e.customer_id, e.device_id, e.event_type,
       e.metric_name, e.metric_value,
       e.attributes:app_version::STRING AS app_version,
       e.attributes:office::STRING      AS office
FROM TELEMETRY_DB.RAW.EVENTS e
JOIN (SELECT DISTINCT customer_id
      FROM TELEMETRY_DB.PRIVATE.ENTITLEMENTS
      WHERE consumer_region = 'GCP_US_CENTRAL1' AND is_active) r
  ON r.customer_id = e.customer_id;

-- That region's entitlement rows. Not shared; used only by the view below.
CREATE OR REPLACE DYNAMIC TABLE TELEMETRY_GCP_US_CENTRAL1.PUBLISH.DT_ENTITLEMENTS
  TARGET_LAG   = '1 hour'
  WAREHOUSE    = TELEMETRY_WH
  REFRESH_MODE = INCREMENTAL
AS
SELECT customer_id, consumer_account
FROM TELEMETRY_DB.PRIVATE.ENTITLEMENTS
WHERE consumer_region = 'GCP_US_CENTRAL1' AND is_active;

CREATE OR REPLACE SECURE VIEW TELEMETRY_GCP_US_CENTRAL1.PUBLISH.V_CUSTOMER_EVENTS AS
SELECT d.event_id, d.event_timestamp, d.device_id, d.event_type,
       d.metric_name, d.metric_value, d.app_version, d.office
FROM TELEMETRY_GCP_US_CENTRAL1.PUBLISH.DT_EVENTS d
JOIN TELEMETRY_GCP_US_CENTRAL1.PUBLISH.DT_ENTITLEMENTS ent
  ON  ent.customer_id      = d.customer_id
  AND ent.consumer_account = CURRENT_ACCOUNT();

Then create the regional semantic view and agent in each database the same way as in the home region, pointing at that region's view. Practical notes:

  • Freshness is the dynamic table's lag plus the listing's refresh. TARGET_LAG controls how far the regional table may trail the source; the listing refresh then carries it to the region. Set both with the customer's expectations in mind.
  • Prefer incremental refresh. With REFRESH_MODE = INCREMENTAL, Snowflake can process changes rather than recompute the full result when the query supports it, which can reduce refresh work. Check the refresh mode Snowflake reports for your dynamic tables. Because the regional view reads entitlements from a dynamic table, a deactivated entitlement is removed at the next refresh, within the target lag. Refresh compute runs on the provider's warehouse in the home region (dynamic table refresh modes).
  • Keep each regional product in one database. Auto-fulfillment doesn't support shared objects that reference a different database, which is why the regional view joins to a regional entitlement table instead of the central one.

Step 3: Put each product on a share

A share is the unit Snowflake replicates and grants, so its contents decide both what a consumer can use and what is copied to their region. Everything a consumer receives goes on one share per region, including the view, the semantic view, and the agent.

CREATE SHARE customer_share;
GRANT USAGE  ON DATABASE      TELEMETRY_DB                           TO SHARE customer_share;
GRANT USAGE  ON SCHEMA        TELEMETRY_DB.PUBLISH                   TO SHARE customer_share;
GRANT SELECT ON VIEW          TELEMETRY_DB.PUBLISH.V_CUSTOMER_EVENTS TO SHARE customer_share;
GRANT SELECT, REFERENCES ON SEMANTIC VIEW TELEMETRY_DB.PUBLISH.SV_CUSTOMER TO SHARE customer_share;
GRANT USAGE  ON AGENT         TELEMETRY_DB.PUBLISH.CUSTOMER_AGENT    TO SHARE customer_share;

-- Regional customer shares, one per regional database
CREATE SHARE customer_share_aws_us_east_1;
GRANT USAGE  ON DATABASE      TELEMETRY_AWS_US_EAST_1                           TO SHARE customer_share_aws_us_east_1;
GRANT USAGE  ON SCHEMA        TELEMETRY_AWS_US_EAST_1.PUBLISH                   TO SHARE customer_share_aws_us_east_1;
GRANT SELECT ON VIEW          TELEMETRY_AWS_US_EAST_1.PUBLISH.V_CUSTOMER_EVENTS TO SHARE customer_share_aws_us_east_1;
GRANT SELECT, REFERENCES ON SEMANTIC VIEW TELEMETRY_AWS_US_EAST_1.PUBLISH.SV_CUSTOMER TO SHARE customer_share_aws_us_east_1;
GRANT USAGE  ON AGENT         TELEMETRY_AWS_US_EAST_1.PUBLISH.CUSTOMER_AGENT    TO SHARE customer_share_aws_us_east_1;

CREATE SHARE customer_share_gcp_us_central1;
GRANT USAGE  ON DATABASE      TELEMETRY_GCP_US_CENTRAL1                           TO SHARE customer_share_gcp_us_central1;
GRANT USAGE  ON SCHEMA        TELEMETRY_GCP_US_CENTRAL1.PUBLISH                   TO SHARE customer_share_gcp_us_central1;
GRANT SELECT ON VIEW          TELEMETRY_GCP_US_CENTRAL1.PUBLISH.V_CUSTOMER_EVENTS TO SHARE customer_share_gcp_us_central1;
GRANT SELECT, REFERENCES ON SEMANTIC VIEW TELEMETRY_GCP_US_CENTRAL1.PUBLISH.SV_CUSTOMER TO SHARE customer_share_gcp_us_central1;
GRANT USAGE  ON AGENT         TELEMETRY_GCP_US_CENTRAL1.PUBLISH.CUSTOMER_AGENT    TO SHARE customer_share_gcp_us_central1;

-- Internal share for the organizational listing
CREATE SHARE fleet_share;
GRANT USAGE  ON DATABASE      TELEMETRY_DB                           TO SHARE fleet_share;
GRANT USAGE  ON SCHEMA        TELEMETRY_DB.PUBLISH                   TO SHARE fleet_share;
GRANT SELECT ON VIEW          TELEMETRY_DB.PUBLISH.V_FLEET_EVENTS    TO SHARE fleet_share;
GRANT SELECT, REFERENCES ON SEMANTIC VIEW TELEMETRY_DB.PUBLISH.SV_FLEET TO SHARE fleet_share;
GRANT USAGE  ON AGENT         TELEMETRY_DB.PUBLISH.FLEET_AGENT       TO SHARE fleet_share;

Grants on a share take effect for every consumer of its listing as soon as you run them, including after the listing is published. Review each GRANT ... TO SHARE before you run it, especially on a share that already has consumers.

Step 4: Publish the internal product as an organizational listing

Internal teams need discovery and an approval path more than they need contracts, which is what an organizational listing adds on top of the share.

CREATE ORGANIZATION LISTING FLEET_INTELLIGENCE
SHARE fleet_share AS
$$
title: "Fleet Intelligence"
description: |
  Product telemetry for every Helio customer in one governed data product.

  **What's inside**
  - `V_FLEET_EVENTS`: one row per event, with device, app version, office, and metric values.
  - `SV_FLEET`: governed definitions for error rate, crash rate, and average latency.
  - `FLEET_AGENT`: ask questions in plain English, answered from the semantic view.

  **Who it's for**
  Product engineering, customer success, finance, and regional teams who need
  fleet-wide adoption, reliability, and performance metrics without building a pipeline.

  **Freshness**
  Events land within an hour. Teams in other regions receive refreshes every 60 minutes.
resources:
  documentation: "https://docs.helio.example/data-products/fleet-intelligence"
listing_terms:
  type: "CUSTOM"
  link: "https://docs.helio.example/data-products/internal-use-policy"
data_dictionary:
  featured:
    database: "TELEMETRY_DB"
    objects:
      - name: "V_FLEET_EVENTS"
        schema: "PUBLISH"
        domain: "VIEW"
usage_examples:
  - title: "Error rate by customer, last 7 days"
    description: "Find the customers with the most errors this week."
    query: "SELECT customer_id, COUNT_IF(event_type = 'ERROR') / COUNT(*) AS error_rate_of_all_events FROM PUBLISH.V_FLEET_EVENTS WHERE event_timestamp >= DATEADD(day, -7, CURRENT_TIMESTAMP()) GROUP BY customer_id ORDER BY error_rate_of_all_events DESC;"
  - title: "Average latency by app version"
    description: "Compare performance across releases."
    query: "SELECT app_version, AVG(metric_value) AS avg_latency_ms FROM PUBLISH.V_FLEET_EVENTS WHERE event_type = 'LATENCY' GROUP BY app_version ORDER BY app_version;"
  - title: "Daily active devices"
    description: "Track adoption across the fleet."
    query: "SELECT DATE_TRUNC('day', event_timestamp) AS day, COUNT(DISTINCT device_id) AS active_devices FROM PUBLISH.V_FLEET_EVENTS GROUP BY day ORDER BY day;"
data_attributes:
  refresh_rate: HOURLY
  geography:
    geo_option: NOT_APPLICABLE
  time:
    granularity: EVENT_BASED
    time_range:
      time_frame: LAST
      units: DAYS
      value: 90
organization_profile: "INTERNAL"
organization_targets:
  discovery:
    - all_internal_accounts: true
  access:
    - account: "PRODUCT_ENG"
    - account: "CS_FINANCE"
    - account: "US_EAST_REGIONAL"
support_contact: "data-products@helio.example"
approver_contact: "data-products@helio.example"
request_approval_type: "REQUEST_AND_APPROVE_IN_SNOWFLAKE"
locations:
  access_regions:
    - name: "ALL"
auto_fulfillment:
  refresh_type: SUB_DATABASE
  refresh_schedule: 60 MINUTE
$$;

Every internal account can discover the listing; only the named accounts can use it, and others request access through the approval workflow. All links and emails referring to helio in these manifests are placeholders; replace them with your own documentation. Field details are in the organization listing manifest reference. Before teams in other regions can receive the product, global data sharing must be enabled for the provider account (Configure organizational listings for auto-fulfillment).

Step 5: Publish the external products as private listings

Each regional share gets its own private listing, so each one replicates only to the region whose customers it targets.

-- Home region: customers in AWS us-west-2, nothing replicated.
CREATE EXTERNAL LISTING PRODUCT_INSIGHTS
SHARE customer_share AS
$$
title: "Product Insights"
subtitle: "Your Helio telemetry, ready to query and join to your own data"
description: |
  Your organization's Helio product telemetry, delivered into your Snowflake account.
  You see only your own data, queried with your own compute.

  **What's inside**
  - `V_CUSTOMER_EVENTS`: one row per event from your devices, with app version, office, and metric values.
  - `SV_CUSTOMER`: governed definitions for error rate and average latency, the same ones Helio uses.
  - `CUSTOMER_AGENT`: ask questions about your usage and performance in plain English.

  **Common uses**
  Join telemetry to your HR, IT asset, or ticketing data, track adoption of new releases,
  and spot offices with degraded performance.
resources:
  documentation: "https://docs.helio.example/data-products/product-insights"
listing_terms:
  type: "OFFLINE"
targets:
  accounts: ["CUSTOMER_A_ORG.CUSTOMER_A", "CUSTOMER_B_ORG.CUSTOMER_B"]
categories:
  - BUSINESS
business_needs:
  - name: "Product Usage Analytics"
    description: "Measure adoption, reliability, and performance of Helio across your organization."
    type: CUSTOM
data_dictionary:
  featured:
    database: "TELEMETRY_DB"
    objects:
      - name: "V_CUSTOMER_EVENTS"
        schema: "PUBLISH"
        domain: "VIEW"
usage_examples:
  - title: "Error rate by office, last 7 days"
    description: "Find the offices with the most errors this week."
    query: "SELECT office, COUNT_IF(event_type = 'ERROR') / COUNT(*) AS error_rate_of_all_events FROM PUBLISH.V_CUSTOMER_EVENTS WHERE event_timestamp >= DATEADD(day, -7, CURRENT_TIMESTAMP()) GROUP BY office ORDER BY error_rate_of_all_events DESC;"
  - title: "Adoption of each app version"
    description: "See how many devices run each release."
    query: "SELECT app_version, COUNT(DISTINCT device_id) AS devices FROM PUBLISH.V_CUSTOMER_EVENTS GROUP BY app_version ORDER BY devices DESC;"
data_attributes:
  refresh_rate: HOURLY
  geography:
    geo_option: NOT_APPLICABLE
  time:
    granularity: EVENT_BASED
    time_range:
      time_frame: LAST
      units: DAYS
      value: 90
resharing:
  enabled: false
$$;

-- AWS us-east-1: customers whose entitlements name this region.
CREATE EXTERNAL LISTING PRODUCT_INSIGHTS_AWS_US_EAST_1
SHARE customer_share_aws_us_east_1 AS
$$
title: "Product Insights"
subtitle: "Your Helio telemetry, ready to query and join to your own data"
description: |
  Your organization's Helio product telemetry, delivered into your Snowflake account.
  You see only your own data, queried with your own compute.

  **What's inside**
  - `V_CUSTOMER_EVENTS`: one row per event from your devices, with app version, office, and metric values.
  - `SV_CUSTOMER`: governed definitions for error rate and average latency, the same ones Helio uses.
  - `CUSTOMER_AGENT`: ask questions about your usage and performance in plain English.

  **Common uses**
  Join telemetry to your HR, IT asset, or ticketing data, track adoption of new releases,
  and spot offices with degraded performance.
resources:
  documentation: "https://docs.helio.example/data-products/product-insights"
listing_terms:
  type: "OFFLINE"
targets:
  accounts: ["CUSTOMER_C_ORG.CUSTOMER_C"]
auto_fulfillment:
  refresh_type: SUB_DATABASE
  refresh_schedule: 60 MINUTE
categories:
  - BUSINESS
business_needs:
  - name: "Product Usage Analytics"
    description: "Measure adoption, reliability, and performance of Helio across your organization."
    type: CUSTOM
data_dictionary:
  featured:
    database: "TELEMETRY_AWS_US_EAST_1"
    objects:
      - name: "V_CUSTOMER_EVENTS"
        schema: "PUBLISH"
        domain: "VIEW"
usage_examples:
  - title: "Error rate by office, last 7 days"
    description: "Find the offices with the most errors this week."
    query: "SELECT office, COUNT_IF(event_type = 'ERROR') / COUNT(*) AS error_rate_of_all_events FROM PUBLISH.V_CUSTOMER_EVENTS WHERE event_timestamp >= DATEADD(day, -7, CURRENT_TIMESTAMP()) GROUP BY office ORDER BY error_rate_of_all_events DESC;"
  - title: "Adoption of each app version"
    description: "See how many devices run each release."
    query: "SELECT app_version, COUNT(DISTINCT device_id) AS devices FROM PUBLISH.V_CUSTOMER_EVENTS GROUP BY app_version ORDER BY devices DESC;"
data_attributes:
  refresh_rate: HOURLY
  geography:
    geo_option: NOT_APPLICABLE
  time:
    granularity: EVENT_BASED
    time_range:
      time_frame: LAST
      units: DAYS
      value: 90
resharing:
  enabled: false
$$;

-- Google Cloud us-central1: customers whose entitlements name this region.
CREATE EXTERNAL LISTING PRODUCT_INSIGHTS_GCP_US_CENTRAL1
SHARE customer_share_gcp_us_central1 AS
$$
title: "Product Insights"
subtitle: "Your Helio telemetry, ready to query and join to your own data"
description: |
  Your organization's Helio product telemetry, delivered into your Snowflake account.
  You see only your own data, queried with your own compute.

  **What's inside**
  - `V_CUSTOMER_EVENTS`: one row per event from your devices, with app version, office, and metric values.
  - `SV_CUSTOMER`: governed definitions for error rate and average latency, the same ones Helio uses.
  - `CUSTOMER_AGENT`: ask questions about your usage and performance in plain English.

  **Common uses**
  Join telemetry to your HR, IT asset, or ticketing data, track adoption of new releases,
  and spot offices with degraded performance.
resources:
  documentation: "https://docs.helio.example/data-products/product-insights"
listing_terms:
  type: "OFFLINE"
targets:
  accounts: ["CUSTOMER_D_ORG.CUSTOMER_D", "CUSTOMER_E_ORG.CUSTOMER_E"]
auto_fulfillment:
  refresh_type: SUB_DATABASE
  refresh_schedule: 60 MINUTE
categories:
  - BUSINESS
business_needs:
  - name: "Product Usage Analytics"
    description: "Measure adoption, reliability, and performance of Helio across your organization."
    type: CUSTOM
data_dictionary:
  featured:
    database: "TELEMETRY_GCP_US_CENTRAL1"
    objects:
      - name: "V_CUSTOMER_EVENTS"
        schema: "PUBLISH"
        domain: "VIEW"
usage_examples:
  - title: "Error rate by office, last 7 days"
    description: "Find the offices with the most errors this week."
    query: "SELECT office, COUNT_IF(event_type = 'ERROR') / COUNT(*) AS error_rate_of_all_events FROM PUBLISH.V_CUSTOMER_EVENTS WHERE event_timestamp >= DATEADD(day, -7, CURRENT_TIMESTAMP()) GROUP BY office ORDER BY error_rate_of_all_events DESC;"
  - title: "Adoption of each app version"
    description: "See how many devices run each release."
    query: "SELECT app_version, COUNT(DISTINCT device_id) AS devices FROM PUBLISH.V_CUSTOMER_EVENTS GROUP BY app_version ORDER BY devices DESC;"
data_attributes:
  refresh_rate: HOURLY
  geography:
    geo_option: NOT_APPLICABLE
  time:
    granularity: EVENT_BASED
    time_range:
      time_frame: LAST
      units: DAYS
      value: 90
resharing:
  enabled: false
$$;

With refresh_type: SUB_DATABASE, auto-fulfillment replicates the objects in the share and the objects they reference rather than the whole database (listing manifest reference). Customers see the same title and contents wherever they are; only the data behind it differs. Target accounts use the <OrgName>.<AccountName> format, and auto_fulfillment is required as soon as any target is outside the provider's region (listing manifest reference). The refresh schedule accepts an interval of at least 10 minutes or a cron expression; SYSTEM$TRIGGER_LISTING_REFRESH triggers a refresh on demand, which fits well after an upstream batch finishes.

To onboard a new customer later, insert an entitlement row with the account's region and add the account to that region's listing targets with ALTER LISTING. The views, shares, and listings stay the same.

What cross-region delivery costs. Same-region consumers query the provider's objects in place, with no data transfer. For other regions you pay for transfer at your source region's published rate, storage of the regional copy, and the compute that keeps it in sync, plus dynamic table refreshes at home (Auto-fulfillment costs). Regional datasets are designed to limit what is replicated to each region to the customers whose entitlements name it, so you move the regional subset rather than the full table. When an entitlement changes, the affected rows leave the regional dynamic table at its next refresh and leave the auto-fulfilled copy at the next listing refresh. Treat this as a design to validate for your deployment, not as a data residency assurance. Total cost still depends on transfer, storage, and refresh compute for your configuration and usage.

Usage is visible in the LISTING_AUTO_FULFILLMENT_REFRESH_DAILY view. Freshness follows the same split. Same-region consumers query the provider's objects directly, so they see data as current as those objects, and other regions see it as of the last completed listing refresh.

Step 6: Enable change tracking so your consumers know what changed

Consumers that load telemetry into their own pipelines usually want only new and changed rows. Streams provide this, but only the provider can enable change tracking on shared objects. The base tables need it for both the home-region view and the incremental dynamic tables, and each shared view needs it too:

ALTER TABLE TELEMETRY_DB.RAW.EVENTS                                SET CHANGE_TRACKING = TRUE;
ALTER TABLE TELEMETRY_DB.PRIVATE.ENTITLEMENTS                      SET CHANGE_TRACKING = TRUE;
ALTER VIEW  TELEMETRY_DB.PUBLISH.V_CUSTOMER_EVENTS                 SET CHANGE_TRACKING = TRUE;
-- Repeat ALTER VIEW for each regional V_CUSTOMER_EVENTS.

Include these points in the consumer documentation:

The following behaviors are what we observed when testing this design; confirm them against current documentation for your configuration.

  • If you need to change a shared view, use CREATE OR ALTER, not CREATE OR REPLACE. In our tests, CREATE OR ALTER SECURE VIEW added a column in place. The share grant and change tracking stayed, and consumer streams kept working and saw the new column. CREATE OR REPLACE drops and recreates the view, and in our tests existing consumer streams then failed with "Base table ... dropped, cannot read from stream". If you used CREATE OR REPLACE, your consumers can recover by recreating the stream with SHOW_INITIAL_ROWS = TRUE and loading with a MERGE on event_id, which skips rows they already have.
  • Consumers in other regions should use standard streams. Append-only streams cannot be created on an auto-fulfilled copy of the data.
  • Plan for retention. A stream on a shared object doesn't extend the provider's data retention, so a consumer stream becomes stale if it isn't read within that period (Streams on shared objects). Tell consumers your retention period, or set DATA_RETENTION_TIME_IN_DAYS on the source tables to cover how often they read.

Step 7: What the consumer does

A customer administrator opens Data sharing » External sharing, selects the listing, and chooses Get, which creates a read-only database. This doesn't need ACCOUNTADMIN: a role with CREATE DATABASE and IMPORT SHARE on the account can do it (Access and install listings), plus USAGE on a warehouse to query, and the SNOWFLAKE.CORTEX_USER and SNOWFLAKE.CORTEX_AGENT_USER database roles to use the agent.

A consumer in another region sees Request instead of Get the first time. Auto-fulfillment creates the regional copy only after that request, so the first install in a new region waits for the initial replication; in SQL, with a warehouse in use, CALL SYSTEM$REQUEST_LISTING_AND_WAIT('<listing_global_name>') requests the listing and returns once it can be installed. Later consumers in the same region install immediately.

Internal teams don't need to install anything. An organizational listing can be queried in place by its uniform listing locator, for example ORGDATACLOUD$INTERNAL$FLEET_INTELLIGENCE.PUBLISH.V_FLEET_EVENTS, as soon as their account has access. From then on, analysts can query the product alongside their own data:

-- Customer A's account. License utilization by cost center, joined to Customer A's own HR data.
SELECT h.cost_center,
       COUNT(DISTINCT h.employee_id)  AS licensed_users,
       COUNT(DISTINCT e.device_id)    AS active_devices_30d
FROM CUSTOMER_A_HR.PUBLIC.EMPLOYEES h
LEFT JOIN HELIO_INSIGHTS.PUBLISH.V_CUSTOMER_EVENTS e
       ON e.device_id = h.device_id
      AND e.event_timestamp >= DATEADD('day', -30, CURRENT_TIMESTAMP())
GROUP BY 1
ORDER BY licensed_users - active_devices_30d DESC;

-- Optional: process only new events with a stream in the customer's own database.
-- SHOW_INITIAL_ROWS = TRUE makes the first MERGE backfill existing rows.
CREATE STREAM CUSTOMER_A_CDC.TELEMETRY.S_EVENTS ON VIEW HELIO_INSIGHTS.PUBLISH.V_CUSTOMER_EVENTS
  SHOW_INITIAL_ROWS = TRUE;

MERGE INTO CUSTOMER_A_CDC.TELEMETRY.EVENTS_LOCAL t
USING (SELECT * FROM CUSTOMER_A_CDC.TELEMETRY.S_EVENTS WHERE METADATA$ACTION = 'INSERT') s
ON t.event_id = s.event_id
WHEN NOT MATCHED THEN INSERT (event_id, event_timestamp, metric_value)
                      VALUES (s.event_id, s.event_timestamp, s.metric_value);

Reading a stream doesn't consume it; only a DML statement such as the MERGE advances its position. Without SHOW_INITIAL_ROWS, the stream starts empty and returns only changes made after it was created. Business users can ask the agent instead of writing SQL. A shared agent runs on the consumer's default warehouse, which they can change under AI & ML » Agents.

5. Monitoring how the data product is used

A data product needs its own usage telemetry. The provider wants to know which teams and customers are getting value, which parts of the product they use, and what delivery costs, so it can invest in what works and reach out to consumers who have gone quiet. Snowflake records this for listings without any extra instrumentation (Monitor listing use).

The LISTING_ACCESS_HISTORY view in SNOWFLAKE.DATA_SHARING_USAGE has one row per consumer query, with the consumer account, region, and the shared objects and columns accessed. Its documented columns describe access to shared objects and don't include the consumer's query text or the consumer's own tables, so you can see which shared objects are used. Check the view's column reference before describing provider visibility to your consumers. For example, active customers and the objects they use each month:

SELECT DATE_TRUNC('month', lah.query_date)              AS month,
       lah.consumer_account_name,
       obj.value:"objectName"::STRING                   AS object_name,
       COUNT(DISTINCT lah.query_token)                  AS queries
FROM SNOWFLAKE.DATA_SHARING_USAGE.LISTING_ACCESS_HISTORY lah,
     LATERAL FLATTEN(input => lah.share_objects_accessed) obj
WHERE lah.query_date >= DATEADD('month', -3, CURRENT_DATE())
GROUP BY 1, 2, 3
ORDER BY 1, 4 DESC;

The usage views can lag by up to two days and keep a year of history, so they suit weekly product reviews and customer-success follow-up rather than real-time alerting.

The result closes the loop with Section 1: the same data that tells customer success which customers are adopting the product can now tell the data-product team which customers are adopting the data product.

Where to start

Start inside the company. Publish the organizational listing to the two or three teams already asking for extracts, often customer success and product. There is no contract, and cross-region cost depends on how many teams are in other regions, and you will find gaps in your metric definitions with colleagues rather than customers.

Then run a customer pilot: one design-partner customer in your own region, plus a test account that should see nothing, to prove the separation works. Add a second region next, with its own dynamic table and listing, so refresh timing and cost become real numbers and you can validate what data is replicated there. Offer change processing once the schema stops moving.

References

Updated Nov 5, 2026

This content is provided as is, and is not maintained on an ongoing basis. It may be out of date with current Snowflake instances