Skip to main content

Why teams build this

Security and GRC leaders are increasingly asked to show that the trust center is doing commercial work. The question from leadership is usually some version of “which accounts are reviewing us, and what are they reading?” Answering it in a spreadsheet gets stale within a week. Answering it in Hex lets you join trust center activity to the CRM and pipeline data you already have there, so document engagement sits next to deal stage and account owner.

What one row looks like

The dataset this guide produces has one row per interaction: One request returns the whole trust center, so you do not need to know your document list in advance or loop over it. No cell is ever null. Rows carry unattributed and an empty email when a visit has not been tied to a company or a person, and two boolean columns let you filter to the level of attribution you want. See how much of the activity is identified. event_type currently returns one of DOCUMENT_VIEWED, DOCUMENT_DOWNLOADED, or LINK_DOCUMENT_OPENED. Treat it as a passthrough column so new activity types flow into your dashboard without a code change.

Before you start

Bind the key to a service account. A scheduled Hex refresh then keeps working when the person who created the key changes roles or leaves the company. See service accounts.

Step 1: Create an API key

1

Create the service account first

Go to Settings → Service accounts and create one named something like Hex reporting. Set its role to Admin, because the endpoint this guide uses is admin only and the role selector starts on User.Do this before opening API settings. The Owner dropdown on the key dialog lists the service accounts that already exist, so it offers only Yourself until one is there to pick. Full walkthrough: service accounts.
2

Open API settings

Go to Settings → API. This page is visible to admins.
3

Create the key

Click Create API key. Give it a Name such as Hex trust center reporting, set Owner to your service account, and pick an Expiration.
4

Copy it once

The key is shown a single time and starts with wolfia-api-. Copy it before closing the dialog.

Step 2: Store the key as a Hex secret

Open the Variables sidebar in your Hex project. In the Secrets section, click +Add, choose Create project secret, and name it WOLFIA_API_KEY. Hex stores secrets in an encrypted vault and renders them as [SECRET VALUE] if anything tries to display them, so the key stays out of your notebook source and out of any published app. A secret is referenced in a Python cell by its bare name, which is why the sample below uses WOLFIA_API_KEY directly with no lookup around it.
Use a secret for the API key. Hex environment variables are read through os.environ and are stored without encryption or redaction, which makes them unsuitable for a credential.

Step 3: Build the interactions dataframe

One endpoint returns every document interaction across your whole trust center, so this cell is a single loop rather than a walk over each document in turn. Each request returns up to 100 interactions plus a next_cursor. Keep passing that cursor back while has_more is true, and stop when it turns false. A trust center with a few hundred interactions is a handful of requests.
Assign the result to a named dataframe and Hex will make it available to downstream SQL and chart cells.
Let has_more end your loop. With the default ordering the last page reports has_more as false and returns no cursor, so code that treats a missing cursor as an error will fail on the final page. With order=received the last page still ends the loop the same way, and any non-empty page’s cursor is safe to save.

How much of the activity is identified

A trust center is a public page. Someone can read your SOC 2 before they ever tell you who they are, so every interaction falls into one of three levels of identification. The sample code labels all three so your dashboard has no blank cells, and it sets two boolean columns so you can filter deliberately. Two things follow from how this works, and both are useful:
  • An email never appears without a domain. Individual identification comes from an access request, and an access request always belongs to an account. So account_domain is always at least as complete as user_email, and it is the right column to group by for account level reporting.
  • Unidentified visits are real engagement. Dropping them understates how much your trust center is being read, which is usually the opposite of the point. Keep them in the totals and filter only when you specifically need account level attribution:
The proportion of each level varies by trust center, by how much traffic arrives from public search, and by how much of your document set sits behind an access request.

Choosing your join keys

Use document_title as the document key. For a document that is still on your trust center, the title is the one you gave it, read live at the time of the request. Renaming a document therefore updates its whole history at once, and re-uploading a refreshed SOC 2 under the same title keeps that history in one place. document_id changes on a re-upload, which makes it a poor primary key for a saved dashboard even though it is useful for spot checks. One caveat: grouping is by exact title, so SOC 2 Type 2 Report and Acme SOC 2 Type II Report count as two documents. If you have used both spellings, map the variants to one canonical name in your dashboard. Check is_deleted before you trust a title. When it is false, the title came from your trust center. When it is true the document id no longer resolves to anything on your trust center, and the title falls back to what the visitor’s browser reported at the time of the click, which may be an older name or missing entirely. Removed documents are a small share of most trust centers, but the flag is what tells you which rows to treat carefully. Use account_domain as the customer key. It is the stable identifier for a visiting company and joins cleanly to the website or domain field on your CRM accounts.

Keeping the dataset fresh

A visitor can browse before they finish verifying their identity. When they do verify, their earlier activity is attributed to them and to their account, so a row that was anonymous an hour ago can gain an account_domain and a user_email.Set each scheduled run to re-pull the last 30 days and let event_id de-duplicate. Rows that gained attribution get corrected, and nothing is double counted.
event_id is unique and stable per interaction. It is the right key for an upsert into a warehouse table and for drop_duplicates in a notebook.
Run the full historical range once and write it to a table. After that, schedule a run over a trailing window only, using start_date and end_date rather than a saved cursor.
The default ordering follows when interactions happened, and the reported time comes from the visitor’s browser, so a late arriving interaction can land behind a position you already passed. Default cursors therefore live inside one walk: finish each walk in one run.For scheduled incremental runs, pass order=received instead. It sorts by when Wolfia recorded each interaction, so its cursor is durable: persist it between runs and resume exactly where the last run stopped, late arrivals included. It covers interactions recorded from the point the ordering became available, holds back the newest fifteen minutes, and does not accept date windows. An empty page means you are caught up; keep your saved cursor. Run the historical backfill once with the default ordering, then switch your schedule to order=received.
Interactions with documents you have taken down stay in the feed, marked with is_deleted as true. A trend line does not develop a hole when you retire a report.

Limits to design around

Troubleshooting

Confirm the header is X-API-Key and the value begins with wolfia-api-. A key that has been deactivated, deleted, or has passed its expiration date also returns 401. Check its status on Settings → API.
The interactions endpoint requires the Admin role. If the key is bound to a service account, raise that service account to Admin or issue the key under an admin owner.
The cursor was altered, truncated, or built by hand. Pass back the next_cursor value exactly as it was returned, and start a fresh walk with no cursor if you need to recover.
You have exceeded the hourly or burst rate limit. Increase REQUEST_PAUSE_SECONDS, and split a large backfill across several runs.
Check the date window first. start_date and end_date are microseconds, and passing seconds or milliseconds produces a window that matches nothing. Then confirm your trust center was live during the period you asked for.
This is expected on a public trust center rather than a fault in the extract. A visitor can read a public document without identifying themselves, and a row gains an email only once someone verifies their identity, so re-pulling a trailing window fills in more of them over time.Group by account_domain for account level reporting, or filter on has_known_person when you specifically need named individuals. The three levels of identification are explained in how much of the activity is identified.
Same cause, one level up. The visit was not tied to any company, usually because the reader arrived on a public page and never requested access. The sample code labels these unattributed so nothing is silently dropped from your totals. Filter on has_known_account to exclude them from account level views.

Adapting this to other tools

Nothing here is specific to Hex beyond where the credential lives. The same requests work from any scheduler or notebook that can hold a secret and make HTTPS calls, including Databricks, Snowflake external functions, Airflow, and dbt Python models. Replace the bare WOLFIA_API_KEY reference with your platform’s own secret lookup, which on most of them is os.environ["WOLFIA_API_KEY"], and keep the rest of the code as it is.

Next steps

Trust analytics API

The full endpoint reference, including revenue attribution, active accounts, and access requests.

API overview

Authentication, rate limits, status codes, and every endpoint area.

Service accounts

Bind API keys to a non-human identity so scheduled jobs survive team changes.

Trust Center

Set up documents, branding, and access controls on the trust center itself.