Skip to content
  • There are no suggestions because the search field is empty.

Setting Up a Snowflake Warehouse Export with Reactiv

Integrating Snowflake with Reactiv lets you continuously export your Reactiv mobile-app events into your own Snowflake account for warehousing and analytics. Events are delivered exactly-once into a table in your database, where they are yours to query, join, and retain for as long as you like.

Prerequisites

Before you begin, make sure you have:

  • A Snowflake account on a region/cloud where high-performance Snowpipe Streaming is available (generally available on AWS; Azure/GCP regions may lag).
  • Admin access in Snowflake — the setup script requires both the USERADMIN and SECURITYADMIN roles (or ACCOUNTADMIN, which includes both).
  • An account policy that allows TYPE = SERVICE users. Reactiv authenticates with a key-pair-only service user (no password, no MFA).
  • A terminal with openssl to generate the RSA key pair (macOS and Linux include it; on Windows use WSL or Git Bash).
  • Access to your Reactiv dashboard with permission to manage integrations.

⚠️ If your region doesn't support high-performance Snowpipe Streaming, or your account doesn't allow TYPE = SERVICE users, stop here and contact Reactiv support. There is no supported fallback — don't substitute a password-authenticated user or broader permissions to work around a failed check.


How the Snowflake Export Works

You grant a dedicated Reactiv service role least-privilege access to one database and one schema in your account. Reactiv handles everything else:

Responsibility

Who does it

Create the destination database, schema, and warehouse

You (or reuse existing ones)

Create the REACTIV_SVC service user + REACTIV_INGEST_ROLE role

You (one script, run once)

Generate the RSA key pair and register the public key

You

Create the destination table REACTIV_EVENTS

Reactiv (automatically, when you enable the integration)

Evolve the table as new event fields are added

Reactiv (additive ALTER TABLE ADD COLUMN only — columns are never dropped or retyped)

Stream events into the table

Reactiv (serverless Snowpipe Streaming — ingestion does not consume your warehouse)

The service role owns the table it creates, which is what keeps the permission set so small: ownership implies the ability to insert rows and add columns, so no table-level grants are ever needed. Your warehouse is only used for the occasional table create/alter/check — never for ingestion.


Step-by-Step Guide

Step 1: Choose (or create) the destination database, schema, and warehouse

Reactiv writes to one database + one schema, using one virtual warehouse for its occasional DDL. You can reuse existing objects, but we recommend a dedicated schema that will hold only Reactiv data — it keeps the integration's footprint cleanly bounded.

If you're creating them fresh, run something like this as your own admin role:

USE ROLE SYSADMIN;
CREATE DATABASE IF NOT EXISTS REACTIV_ANALYTICS;
CREATE SCHEMA IF NOT EXISTS REACTIV_ANALYTICS.EVENTS;
CREATE WAREHOUSE IF NOT EXISTS REACTIV_WH
WAREHOUSE_SIZE = XSMALL
AUTO_SUSPEND = 60
AUTO_RESUME = TRUE
INITIALLY_SUSPENDED = TRUE;

⚠️ The warehouse must have AUTO_RESUME = TRUE. The Reactiv role receives only USAGE on it, so it cannot resume a suspended warehouse on its own — without auto-resume, enabling the integration fails.

💡 Note your exact identifier casing. Snowflake upper-cases unquoted names. If a database or schema was created with quoted, mixed-case names (e.g. "myDb"), you must use the exact same quoting everywhere in Step 3 and in the dashboard form. When in doubt, use the names exactly as SHOW DATABASES / SHOW SCHEMAS report them.

Step 2: Generate an RSA key pair

Reactiv authenticates to your account with RSA key-pair authentication (2048-bit minimum). You generate the pair, register the public key in Snowflake (Step 3), and paste the private key into the Reactiv dashboard form (Step 5).


# Option A — unencrypted private key:
openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -nocrypt -out reactiv_snowflake_key.p8

# Option B — encrypted private key (you'll be prompted to set a passphrase;
# enter the same passphrase in the dashboard form later):
openssl genrsa 2048 | openssl pkcs8 -topk8 -v2 aes256 -inform PEM -out reactiv_snowflake_key.p8

# Then derive the public key (both options). For Option B, openssl prompts for the
# passphrase you just set — that prompt is expected, not an error:
openssl rsa -in reactiv_snowflake_key.p8 -pubout -out reactiv_snowflake_key.pub

Snowflake's RSA_PUBLIC_KEY property takes the base64 body only — no -----BEGIN PUBLIC KEY----- / -----END PUBLIC KEY----- lines and no line breaks. This command prints the value ready to paste into Step 3:

grep -v "PUBLIC KEY" reactiv_snowflake_key.pub | tr -d '\n'

🔒 Treat the private key file (reactiv_snowflake_key.p8) as a secret. You will paste it into the Reactiv dashboard once; Reactiv stores it encrypted, uses it only to sign authentication tokens, and never displays or logs it. Don't commit it to source control or share it anywhere else.

Step 3: Run the least-privilege setup script in Snowflake

This script is run once, by an operator holding both USERADMIN and SECURITYADMIN (or ACCOUNTADMIN). It creates the Reactiv role and service user, registers your public key, and grants exactly the access the export needs — nothing more.

Pre-flight check — confirm none of the objects already exist (for example from an earlier, partial setup). If any of these return a row, stop and contact Reactiv support rather than re-running blindly:

SHOW TERSE OBJECTS LIKE 'REACTIV_EVENTS' IN SCHEMA <DB>.<SCHEMA>;
SHOW ROLES LIKE 'REACTIV_INGEST_ROLE';
SHOW USERS LIKE 'REACTIV_SVC';

Then run the script, substituting only the four placeholders (see the reference table below) — copy the rest verbatim:


-- Role + key-pair service user
USE ROLE USERADMIN;
CREATE ROLE IF NOT EXISTS REACTIV_INGEST_ROLE;
CREATE USER IF NOT EXISTS REACTIV_SVC
DEFAULT_ROLE = REACTIV_INGEST_ROLE
DEFAULT_WAREHOUSE = <WAREHOUSE>
RSA_PUBLIC_KEY = '<RSA_PUBLIC_KEY>'
TYPE = SERVICE; -- key-pair only; no password/MFA
GRANT ROLE REACTIV_INGEST_ROLE TO USER REACTIV_SVC;

-- Least-privilege object grants, scoped to ONE database + ONE schema
USE ROLE SECURITYADMIN;
GRANT USAGE ON DATABASE <DB> TO ROLE REACTIV_INGEST_ROLE;
GRANT USAGE ON SCHEMA <DB>.<SCHEMA> TO ROLE REACTIV_INGEST_ROLE;
GRANT CREATE TABLE ON SCHEMA <DB>.<SCHEMA> TO ROLE REACTIV_INGEST_ROLE; -- create + own REACTIV_EVENTS
GRANT CREATE PIPE ON SCHEMA <DB>.<SCHEMA> TO ROLE REACTIV_INGEST_ROLE; -- streaming pipe (auto-created at ingest)
GRANT USAGE ON WAREHOUSE <WAREHOUSE> TO ROLE REACTIV_INGEST_ROLE; -- for the enable-time connection check + schema-reflection query; DDL and ingestion don't use it

Placeholder

Substitute with

<DB>

The database from Step 1, in its exact stored casing (double-quote if it was created case-sensitive).

<SCHEMA>

The schema from Step 1 (within <DB>). Same casing/quoting rule.

<WAREHOUSE>

The warehouse from Step 1 (must have AUTO_RESUME = TRUE).

<RSA_PUBLIC_KEY>

The single-line base64 public key body from Step 2 (no BEGIN/END lines, no line breaks).


⚠️ The Reactiv role needs no table-level grants — and none should be added to it. The Reactiv role creates and owns REACTIV_EVENTS, which implies insert/select/alter on it. Do not grant OWNERSHIP on the schema or database, CREATE SCHEMA, any FUTURE GRANTS, OPERATE/MONITOR on pipes, account-level privileges, or anything beyond USAGE on the warehouse to REACTIV_INGEST_ROLE. If a review process suggests adding one of these, that's a signal to check with Reactiv — not to broaden the grant. (Granting your own analyst roles SELECT on REACTIV_EVENTS is a separate, expected step — see Grant your team read access below.)

⚠️ Re-running the script is not a key-update path. CREATE USER IF NOT EXISTS silently does nothing if REACTIV_SVC already exists — it will not apply a new RSA_PUBLIC_KEY. To change the key on an existing user, see Rotating your key pair below.

Step 4: Verify the setup

Catch typos and skipped grants now — not at first ingest:

SHOW GRANTS TO ROLE REACTIV_INGEST_ROLE; -- expect: USAGE (database, schema, warehouse), CREATE TABLE, CREATE PIPE
SHOW GRANTS OF ROLE REACTIV_INGEST_ROLE; -- expect: role granted to user REACTIV_SVC
DESC USER REACTIV_SVC; -- confirm TYPE = SERVICE and DEFAULT_ROLE / DEFAULT_WAREHOUSE are set

In the DESC USER output, confirm RSA_PUBLIC_KEY_FP matches the fingerprint of the key you generated. Compute your local fingerprint with:

openssl rsa -pubin -in reactiv_snowflake_key.pub -outform DER | openssl dgst -sha256 -binary | openssl enc -base64

The output should match the value after SHA256: in RSA_PUBLIC_KEY_FP.

Step 5: Connect the integration in your Reactiv dashboard

In your Reactiv dashboard, go to Integrations, select Snowflake, and fill in the configuration form:

Field

Required

What to enter

Example

Account / Host

Yes

Your Snowflake account identifier, preferably in organization-account form. See Where Do I Find My Credentials?

myorg-myacct

User

Yes

The service user created in Step 3.

REACTIV_SVC

Role

Yes

The role created in Step 3.

REACTIV_INGEST_ROLE

Database

Yes

The database from Step 1 — exact stored casing.

REACTIV_ANALYTICS

Schema

Yes

The schema from Step 1 — exact stored casing.

EVENTS

Warehouse

Yes

The warehouse from Step 1.

REACTIV_WH

Target Table

No

The table events land in. Leave as the default unless you have a naming requirement.

REACTIV_EVENTS

Private Key

Yes

The full contents of reactiv_snowflake_key.p8 from Step 2, including the -----BEGIN/END----- lines. Write-only — never displayed after saving.

(paste the PEM)

Private Key Passphrase

No

Only if you generated an encrypted key (Option B in Step 2). Write-only — never displayed after saving.

(your passphrase)

When you save and enable the integration, Reactiv:

  1. Verifies the connection — it authenticates as REACTIV_SVC and checks that the role can reach the database, schema, and warehouse.
  2. Creates the destination table — REACTIV_EVENTS (or your custom table name) is created in your schema, owned by the Reactiv role.
  3. Starts streaming — events begin flowing shortly after enablement.

If any check fails, the integration stays disabled and the dashboard shows an actionable error (for example, a missing privilege) — fix the cause in Snowflake and enable again.

Step 6 (Optional): Restrict the service user to Reactiv's network

The private key is the primary control, but as defense-in-depth you can pin REACTIV_SVC to the network Reactiv streams from. Reactiv's warehouse export runs on AWS Fargate in us-east-1 and egresses from AWS's public EC2 address space, so the allowlist below is AWS's published us-east-1 EC2 IP ranges.

-- Account-level action. SECURITYADMIN holds CREATE NETWORK POLICY and administers users
-- by default, so it suffices — you don't need full ACCOUNTADMIN (either works).
USE ROLE SECURITYADMIN;
CREATE NETWORK POLICY IF NOT EXISTS REACTIV_INGEST_POLICY
ALLOWED_IP_LIST = (
'34.196.244.77'
);
ALTER USER REACTIV_SVC SET NETWORK_POLICY = REACTIV_INGEST_POLICY;

This user-level policy scopes only REACTIV_SVC — it does not affect your account-level network policy or any other user.


Where Do I Find My Credentials?

Account / Host

Use the organization-account form of your account identifier — your organization name and account name joined by a hyphen (e.g. myorg-myacct). Any of these three ways gets you the value:

  • Run this in a Snowflake worksheet (easiest — copy the result verbatim):
SELECT CURRENT_ORGANIZATION_NAME() || '-' || CURRENT_ACCOUNT_NAME();
  • In Snowsight: open the account selector in the bottom-left corner of the navigation menu, hover over the active account, and choose the copy account identifier action (the copy icon). Snowsight copies it as MYORG.MYACCT — replace the period with a hyphen: MYORG-MYACCT.
  • From your login URL: if you sign in at https://myorg-myacct.snowflakecomputing.com, the account identifier is the part before .snowflakecomputing.com — here, myorg-myacct.

Enter only the identifier — no https://, no .snowflakecomputing.com suffix. A legacy account locator with a region suffix (e.g. xy12345.us-east-1) is also accepted, but prefer the organization-account form.

User and Role

These come from the Step 3 script: REACTIV_SVC and REACTIV_INGEST_ROLE (unless you changed the names — then use exactly what you created).

Database, Schema, and Warehouse

The objects you chose or created in Step 1. Confirm their exact stored names with SHOW DATABASES;, SHOW SCHEMAS IN DATABASE <DB>;, and SHOW WAREHOUSES;.

Private Key and Passphrase

The reactiv_snowflake_key.p8 file you generated in Step 2, pasted in full. The passphrase applies only if you chose the encrypted option.


What Data Lands in Snowflake

Every Reactiv event is delivered as one row in REACTIV_EVENTS:


Column

Type

Description

event_id

STRING

Stable, unique event ID — safe to deduplicate and join on.

event_name

STRING

The Reactiv event name (e.g. product viewed, checkout started).

app_uuid

STRING

Your Reactiv application's unique ID.

occurred_at

TIMESTAMP_NTZ

When the event happened on the device.

received_at

TIMESTAMP_NTZ

When Reactiv ingested the event.

customer_gid

STRING

The customer's global ID, when known.

device_id

STRING

The device identifier.

properties

VARIANT

All remaining event properties, as queryable semi-structured JSON.

The table is clustered by event date and name (CLUSTER BY (TO_DATE(occurred_at), event_name)), so time- and event-scoped queries stay fast and cheap. For example:

SELECT event_name, COUNT(*) AS events
FROM REACTIV_ANALYTICS.EVENTS.REACTIV_EVENTS
WHERE occurred_at >= DATEADD('day', -7, CURRENT_TIMESTAMP())
GROUP BY event_name
ORDER BY events DESC;

📊 Grant your team read access. REACTIV_EVENTS is created and owned by REACTIV_INGEST_ROLE, which is granted only to the REACTIV_SVC service user — so your own analyst roles can't run the query above until you grant them read access. After the integration is enabled (the table exists only once provisioning has created it), run as SECURITYADMIN (which can grant on any object):

USE ROLE SECURITYADMIN;
GRANT SELECT ON TABLE <DB>.<SCHEMA>.REACTIV_EVENTS TO ROLE <YOUR_ANALYST_ROLE>;

A single table-level SELECT grant is durable: it automatically covers the new columns that additive schema evolution adds, so you never need to re-grant. Granting SELECT to your own roles is expected and is separate from broadening the Reactiv ingest role (which the Step 3 note warns against). If you prefer role-hierarchy inheritance instead, grant REACTIV_INGEST_ROLE to SYSADMIN so the standard admin hierarchy inherits access.

As Reactiv adds new event fields over time, new columns appear automatically. Migration is strictly additive — existing columns are never dropped, renamed, or retyped, so your downstream models won't break.


Additional Notes & Troubleshooting

Delivery guarantees. Events are delivered exactly-once — retries on Reactiv's side never produce duplicate rows.

Costs on your side are minimal. Ingestion uses serverless high-performance Snowpipe Streaming (billed by Snowflake at a small per-GB rate) and does not run your warehouse. Your warehouse is only touched for the connection check and schema-reflection query at enable time (and during schema evolution); the table create/alter DDL itself is metadata-only and, like ingestion, does not run the warehouse.

"The Snowflake role lacks the required privileges." Re-run the verification queries in Step 4. The most common causes are a grant that landed on the wrong object because of identifier casing, or a skipped statement in the Step 3 script.

Enable fails and the warehouse is suspended. Confirm the warehouse has AUTO_RESUME = TRUE (SHOW WAREHOUSES; → auto_resume column). The Reactiv role cannot resume a suspended warehouse itself.

"Invalid public key" when running Step 3. The RSA_PUBLIC_KEY value must be the base64 body only — no -----BEGIN/END PUBLIC KEY----- lines and no line breaks. Re-run the grep … | tr -d '\n' command from Step 2 and paste that output.

A REACTIV_EVENTS table already exists in the schema. If a table with that name exists but is not owned by REACTIV_INGEST_ROLE (for example, you created it yourself), automatic schema migration will fail. Don't pre-create the table — the supported path is letting Reactiv create and own it. Contact Reactiv support if you hit this.

Rotating your key pair. Snowflake supports a second key slot for zero-downtime rotation:

  1. Generate a new key pair (Step 2).
  2. Register the new public key in the free slot: ALTER USER REACTIV_SVC SET RSA_PUBLIC_KEY_2 = '<NEW_PUBLIC_KEY>';
  3. Update the Private Key (and passphrase, if any) in the Reactiv dashboard form.
  4. Once delivery is confirmed healthy, clear the retired slot: ALTER USER REACTIV_SVC UNSET RSA_PUBLIC_KEY;

Always unset the decommissioned key — leaving both slots populated keeps the old key valid.

Disabling the integration never deletes your data. Disabling (or uninstalling) stops delivery but issues no DDL against your account — the REACTIV_EVENTS table and every row in it stay yours, on your retention terms.

Need help? Contact Reactiv support and include the dashboard error message (it never contains your key material) plus the output of the Step 4 verification queries.