Google BigQuery
Datafly Signal writes events directly into a BigQuery table using the BigQuery Storage Write API (batched appends on the default stream; the legacy streaming insert API is available as a fallback), so you can analyse first-party behaviour at warehouse scale.
Why route Google BigQuery through Datafly Signal
- The data lands in infrastructure you own. Signal writes to your Google BigQuery directly from your own deployment. Datafly operates no intermediary and never holds a copy of your event data.
- Personal data is hashed on the way in. Email, phone (shaped to E.164 first), first and last name, full name and postcode are normalised (trim and lowercase) and hashed with SHA-256 inside your own deployment, so the table holds
email_sha256,phone_sha256and the other digests rather than raw personal data. A raw column is an opt-in edit to the installed blueprint. - Client IP and user agent are recorded as event context. Both come from the original visitor request rather than from your server, so geography and device analysis done in the warehouse reflects the actual visitor.
- Presets for Retail, Insurance, Travel, Media & Streaming, Publishing, B2B / SaaS, Automotive and General. Each vertical preset names one field per property in the matching event spec, with canonical names (
transaction_id,value,items), legacy aliases (order_id,revenue,products) read automatically, and numeric properties typed as numbers. Every other event still lands with its properties. - One consent decision governs every destination. Google BigQuery is gated by the same consent state as the rest of your stack, so a withdrawal applies everywhere at once.
Prerequisites
Before configuring Google BigQuery in Signal, you need a GCP project with BigQuery enabled, a dataset and table, and a service account with the BigQuery Data Editor role.
Create a GCP Account and Project
- Sign up at cloud.google.com if you don’t already have an account.
- Go to the GCP Console and click Select a project > New Project.
- Enter a Project name (e.g.
datafly-analytics). - Click Create.
- Note the Project ID (shown below the project name).
Enable the BigQuery API
- In the GCP Console, go to APIs & Services > Library.
- Search for BigQuery API.
- Click Enable (it may already be enabled by default).
Create a Dataset
- Go to the BigQuery console.
- In the Explorer panel, click the three dots next to your project and select Create dataset.
- Enter a Dataset ID (e.g.
datafly_events). - Select a Data location (e.g.
US,EU). This cannot be changed after creation. - Click Create dataset.
Create a Table
- In the BigQuery console, click the three dots next to your dataset and select Create table.
- Choose Empty table as the source.
- Enter a Table name (e.g.
events). - Define the schema manually or run the following SQL:
CREATE TABLE `your-project.datafly_events.events` (
message_id STRING,
event_type STRING,
event_name STRING,
anonymous_id STRING,
user_id STRING,
timestamp TIMESTAMP,
received_at TIMESTAMP,
ip STRING,
user_agent STRING,
page_url STRING,
page_path STRING,
page_referrer STRING,
org_id STRING,
pipeline_id STRING,
properties_json JSON
)
PARTITION BY DATE(timestamp)
CLUSTER BY event_type, event_name;Signal writes exactly these column names; a column the table lacks is skipped and a table column Signal does not write stays NULL. Partitioning by timestamp and clustering by event_type and event_name keeps scan costs predictable. properties_json may also be STRING.
Create a Service Account
- Go to IAM & Admin > Service Accounts.
- Click Create Service Account.
- Enter a Name (e.g.
datafly-signal-bigquery). - Click Create and Continue.
- Grant the BigQuery Data Editor role (
roles/bigquery.dataEditor). - Click Done.
Generate a Service Account Key
- Click on the service account you just created.
- Go to the Keys tab.
- Click Add Key > Create new key.
- Select JSON format.
- Click Create. The key file will download automatically.
Store the JSON key file securely. It provides write access to your BigQuery dataset. Do not commit it to version control. The key content is what you will paste into the Signal configuration.
Configuration
| Field | Type | Required | Description |
|---|---|---|---|
project_id | string | Yes | The Google Cloud project ID that contains the target BigQuery dataset. |
dataset_id | string | Yes | The BigQuery dataset ID where the table resides. |
table_id | string | Yes | The BigQuery table ID to insert rows into. The table must already exist with a compatible schema. |
service_account_json | secret | Yes | The full JSON key file content for the GCP service account with BigQuery Data Editor permissions. |
location | select | Yes | The geographic location of the BigQuery dataset (e.g. US, EU, us-east1). |
Signal Setup
Quick Setup
- Navigate to Integrations in the sidebar.
- Open the Integration Library tab.
- Find Google BigQuery or filter by Warehouse.
- Click Install, select a variant if available, and fill in the required fields.
- Click Install Integration to create the integration with a ready-to-use default blueprint.
API Setup
curl -X POST http://localhost:8084/v1/admin/integration-catalog/google_bigquery/install \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Google BigQuery",
"variant": "default",
"config": {
"project_id": "datafly-analytics",
"dataset_id": "datafly_events",
"table_id": "events",
"service_account_json": "{\"type\": \"service_account\", \"project_id\": \"datafly-analytics\", ...}",
"location": "US"
},
"delivery_mode": "server_side"
}'Schema
Signal writes one row per event: a fixed envelope plus properties_json. Define the table schema to match. With the Storage Write API (the default) a column the table does not have is skipped, and a column the row does not fill is NULL.
| Column | BigQuery type | Notes |
|---|---|---|
message_id | STRING | Unique per event; also the insert ID BigQuery deduplicates on. |
event_type | STRING | track, page, identify, screen or group. |
event_name | STRING | The event name as collected. |
anonymous_id | STRING | First-party visitor identifier. |
user_id | STRING | Logged-in user identifier (nullable). |
timestamp | TIMESTAMP | Client event time. Use as the partition column. |
received_at | TIMESTAMP | Time Signal received the event. |
ip | STRING | Client IP address. |
user_agent | STRING | Client user agent. |
page_url | STRING | Page URL. |
page_path | STRING | Page path. |
page_referrer | STRING | Page referrer. |
org_id | STRING | Signal organisation identifier. |
pipeline_id | STRING | Signal pipeline identifier. |
properties_json | JSON or STRING | The fields your blueprint maps for this event. See What properties_json contains. |
Partition by DATE(timestamp) and cluster by event_type, event_name to keep scan costs predictable.
What properties_json contains
properties_json holds only what the integration’s blueprint maps for the event, and nothing else:
- A property or identify trait the blueprint does not map is never written. That includes the traits a visitor gave in an earlier
identify()call, which Signal attaches to later events so a blueprint can read them. - Personal data is written as the blueprint writes it. The catalogue blueprints hash email and phone with
normalised_sha256(trimmed, lower-cased, SHA-256), soemail_sha256holds a digest. A field you map without a hashing transform is stored raw: that is a choice you make in the blueprint. - Fields mapped in the blueprint’s global section sit under an
@rootobject, for exampleJSON_VALUE(properties_json, '$."@root".email_sha256'). Fields mapped for a single event sit at the top level.
To store a trait, map it in the blueprint (hashed unless you have a reason to keep it raw).
A BigQuery integration that already existed before this rule keeps writing every property it wrote before, including unmapped properties and pass-through events, with personal data removed (the attached identify traits, unmapped identify traits, context.traits and raw email, phone, name, address and similar fields the blueprint does not map). Set strict_output_contract to true in its connection settings to switch it to only what the blueprint maps. See Integrations installed before this change.
Where mapped fields land
The table layout is fixed: the envelope columns above plus properties_json. The blueprint shapes properties_json: event fields from the preset (for example transaction_id, value, insurance_type, policy_term_months) sit at its top level, and the preset’s envelope block (identity digests, UTM, consent flags) sits under @root:
SELECT
timestamp,
JSON_VALUE(properties_json, '$.transaction_id') AS policy_number,
CAST(JSON_VALUE(properties_json, '$.value') AS NUMERIC) AS premium,
JSON_VALUE(properties_json, '$."@root".email_sha256') AS email_sha256
FROM `your-project.datafly_events.events`
WHERE event_name = 'purchase';Signal does not write mapped fields to their own typed columns yet. Create views over properties_json for the columns you query often.
Stored event context
Alongside the fields your blueprint maps (where email, phone, names and address are stored as normalised SHA-256 by default), each row carries the event’s collected context exactly as Signal received it. The blueprint does not filter or hash the context: this is deliberate (decision 2026-09-23), so the context is complete for attribution, fraud and debugging queries, and it is listed here so you can decide what to keep.
| Stored | What it contains |
|---|---|
| Visitor request | IP address, user agent, Accept-Language, locale and timezone |
| Page | URL including its query string, path, title and referrer |
| Campaign | UTM source, medium, campaign, term and content |
| Consent | The visitor’s consent state |
| Location | GeoIP country, region and city, and the network (ASN) |
| Device | Screen size; for app events the OS and version, manufacturer, model, advertising ID and IDFV when the app collected them |
| Vendor IDs | Cookie and click identifiers in vendor_ids (for example the _fbp browser ID, gclid, ttclid, the GA client ID) |
| Session and library | Session ID, number and engagement time; the collector’s name and version |
| Partner attributes | Profile attributes from partner enrichment (partner_attrs) |
They are written to the ip_address, user_agent, page_url, page_path and page_referrer columns and, in full, to the context_json column, beside anonymous_id and user_id. Click and collected IDs outside the context are stored only where the blueprint maps them.
The IP address, device and advertising IDs, and cookie and click identifiers are personal data under UK and EU GDPR. To keep any of them out of storage, strip, mask or hash the field for every integration with the organisation data layer, apply URL redaction to page URLs, or remove it in the installed blueprint; and set the storage’s retention to match your privacy notice. See Personal data by integration.
Consent
BigQuery is a first-party destination you own. The default blueprint forwards all events; if you need consent filtering, apply it inside the pipeline transform or via downstream views that gate on the preset’s consent flags, JSON_VALUE(properties_json, '$."@root".consent_analytics') and consent_marketing (the BigQuery row has no context column).
Testing
- Enable the integration in Signal and trigger a test event on your website.
- Open the BigQuery console and navigate to your dataset and table.
- Click Preview to see recently inserted rows, or run a query:
SELECT * FROM `your-project.datafly_events.events`
ORDER BY timestamp DESC
LIMIT 10;- Verify that event rows are appearing with correct data.
- In Signal, check the Live Events view to confirm delivery status shows as successful.
Troubleshooting
| Problem | Solution |
|---|---|
| Events not appearing in BigQuery | Verify the project ID, dataset ID, table ID, and location are correct. |
Permission denied (403) | The service account lacks the BigQuery Data Editor role. Go to IAM & Admin > IAM and add the role. |
Not found: Table | The table does not exist. Verify the dataset and table IDs. Ensure the table has been created. |
Invalid JSON key | The service account JSON is malformed. Ensure you pasted the complete JSON key file content. |
| Columns empty | The table’s column names must match the Schema above (message_id, event_name, properties_json, …). Check them with SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'events';. |
| Location mismatch | The location config must match the dataset’s actual location. Check the dataset details in BigQuery console. |
| Preview shows no rows | Rows written through the Storage Write API default stream are queryable at once but can take a few minutes to show in the table Preview tab. Query the table instead. |
| Quota exceeded | Check the Storage Write API quotas and request an increase if needed. |
Visit Google BigQuery documentation for full API reference, SQL syntax, and cost optimisation guides.