Google Cloud Storage
Datafly Signal writes batches of first-party events as objects into a GCS bucket — ready for query with BigQuery external tables, Dataproc, or any analytics framework that reads GCS.
Why route Google Cloud Storage through Datafly Signal
- The data lands in infrastructure you own. Signal writes to your Google Cloud Storage directly from your own deployment. Datafly operates no intermediary and never holds a copy of your event data.
- 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 eight verticals. General (the default: page, identify and the key cross-vertical events), Retail, Insurance, Travel, Media & Streaming, Publishing, B2B / SaaS and Automotive, each writing one named field per documented event property. Email, phone and names are written only as SHA-256 digests unless you deliberately change the installed blueprint.
- One consent decision governs every destination. Google Cloud Storage is gated by the same consent state as the rest of your stack, so a withdrawal applies everywhere at once.
Prerequisites
Before configuring Google Cloud Storage in Signal, you need a GCP project with a GCS bucket and a service account with the Storage Object Creator role.
Create a GCP Account and Project
- Sign up at cloud.google.com if you don’t already have an account.
- Create a new project or select an existing one in the GCP Console.
- Note the Project ID.
Enable the Cloud Storage API
- Go to APIs & Services > Library.
- Search for Cloud Storage JSON API.
- Click Enable (it may already be enabled by default).
Create a GCS Bucket
- Go to the Cloud Storage console.
- Click Create bucket.
- Enter a Bucket name (e.g.
datafly-events-production). Names must be globally unique. - Choose a Location type:
- Region — lowest latency, single region (recommended for Signal).
- Multi-region — higher availability, replicated across regions.
- Choose a Storage class: Standard (recommended for frequently accessed data), Nearline, Coldline, or Archive.
- Leave Public access prevention enforced (recommended).
- Click Create.
Choose a region close to your Signal infrastructure to minimise latency and egress costs. Standard storage class is recommended for event data that will be queried regularly.
Create a Service Account
- Go to IAM & Admin > Service Accounts > Create Service Account.
- Enter a name (e.g.
datafly-signal-gcs). - Grant the Storage Object Creator role (
roles/storage.objectCreator). - Click Done.
Generate a Service Account Key
- Click on the service account.
- Go to Keys > Add Key > Create new key > JSON.
- The key file will download. Store it securely.
Store the JSON key file securely. Do not commit it to version control. The entire JSON content is what you will paste into the Signal configuration.
Configuration
| Field | Type | Required | Description |
|---|---|---|---|
bucket_name | string | Yes | The Cloud Storage bucket. Also accepts bucket. |
project_id | string | Yes | The Google Cloud project that owns the bucket. |
service_account_json | secret | Yes | JSON key of a service account with roles/storage.objectCreator on the bucket. |
prefix | string | No | Folder prepended to every object name (for example events). |
file_format | select | Yes | json (newline-delimited, default), csv or parquet. |
parquet_compression | select | No | snappy (default), zstd, gzip or none. |
schema_mode | select | No | canonical (default, 16 fixed columns) or blueprint (typed columns). |
schema_columns | textarea | No | Declared columns for blueprint mode, one per line as name[:type[:source]]. |
batch_size | number | No | Events per file; above 1 consolidates events into one file per partition-hour. Typical: 1000. |
batch_max_wait_ms | number | No | Flush a batch after this long even if it is not full. Default 60000. |
Signal Setup
Quick Setup
- Navigate to Integrations in the sidebar.
- Open the Integration Library tab.
- Find Google Cloud Storage or filter by Cloud Storage.
- 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_cloud_storage/install \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Google Cloud Storage",
"variant": "default",
"config": {
"bucket_name": "datafly-events-production",
"project_id": "datafly-analytics",
"service_account_json": "{\"type\": \"service_account\", ...}",
"prefix": "events/"
},
"delivery_mode": "server_side"
}'What Signal writes
By default each event is one object (set batch_size to consolidate events into one file per partition-hour). Object names use Hive-style partitions of the event time in UTC:
gs://<bucket>/<prefix>/yyyy=2026/mm=09/dd=23/hh=09/<pipeline_id>/<message_id>.ndjsonThe extension and content type follow file_format: .ndjson (application/x-ndjson), .csv (text/csv) or .parquet (application/vnd.apache.parquet).
JSON (default)
One line per event: the Signal event as JSON, the same document the streaming destinations send.
| Field | Contents |
|---|---|
event, type | Event name (purchase, page, …) and type (track, page, identify, …). |
anonymous_id, user_id, device_id | Visitor identifiers (user_id and device_id only when set). |
message_id, event_id | Signal’s message ID (use it to deduplicate) and the client event ID. |
timestamp, sent_at, received_at | RFC 3339 UTC times: event, client send, Signal receipt. |
org_id, pipeline_id, vendor_type | Where the event came from and which destination type wrote it. |
context | The collected context as sent: ip, user_agent, locale, page, campaign, consent, geo, device, library, and any partner attributes. Written in full; the blueprint does not filter it (#1414). |
click_ids, collected_ids | Ad click IDs and ID-provider values, when present. |
properties | Only the fields the blueprint maps (below). |
Only what the blueprint maps is written. The properties object holds the fields your installed blueprint maps and nothing else (the row contract): the global block under @root (copies of the envelope fields, UTM parameters, the analytics and marketing consent flags, and email_sha256, phone_sha256, first_name_sha256, last_name_sha256, name_sha256), and the event section’s fields at the top level (transaction_id, value, currency, items, …). Identify traits and client properties the blueprint does not map are never written. An event with no section in the blueprint still arrives, with the @root block only: add a section for any other event whose properties you need.
CSV and Parquet, canonical schema
A header row and one row per event in the 16 fixed columns the SQL warehouses use, all text (timestamps ISO-8601 in UTC): message_id, event_type, event_name, anonymous_id, user_id, ip_address, user_agent, page_url, page_path, page_referrer, org_id, pipeline_id, event_timestamp, received_at, properties_json (the mapped properties object above) and context_json. A purchase (digests shortened):
message_id,event_type,event_name,anonymous_id,user_id,ip_address,user_agent,page_url,page_path,page_referrer,org_id,pipeline_id,event_timestamp,received_at,properties_json,context_json
msg-s1-amazon_s3-2,track,purchase,anon-s1-7f3a,crm-4711,203.0.113.9,Mozilla/5.0 (Macintosh),https://shop.example/checkout,/checkout,,org-s1,pl-s1,2026-09-23T09:30:02.000Z,2026-09-23T09:30:02.000Z,"{""@root"":{""anonymous_id"":""anon-s1-7f3a"",""email_sha256"":""86e0b9e5..."", ...},""currency"":""GBP"",""items"":[...],""transaction_id"":""SIG-ORDER-1001"",""value"":84.5}","{""ip"":""203.0.113.9"",""page"":{...},""campaign"":{...}}"Blueprint schema mode
With schema_mode set to blueprint, each file has one typed column per field instead (File formats and schema). Inferred columns are message_id, event_name and event_timestamp plus the event section’s top-level fields. The global block’s fields (identifiers, digests, UTM, consent) sit under @root and are not inferred as columns; declare them with an @root. source:
message_id
event_name
event_timestamp:timestamp
user_id:string:@root.user_id
email_sha256:string:@root.email_sha256
transaction_id
value:double
currencywhich writes, for the same purchase:
message_id,event_name,event_timestamp,user_id,email_sha256,transaction_id,value,currency
msg-s1-google_cloud_storage-2,purchase,2026-09-23T09:30:02.000Z,crm-4711,86e0b9e56c17cc4d12387e1949b85053fbe73bc3ce5a1188713a9d300cc6133d,SIG-ORDER-1001,84.5,GBPThese files were produced by Signal’s Google Cloud Storage writer against fake-gcs-server 1.52 (JSON; the CSV and blueprint-mode shapes are shared with Amazon S3 and were proved there) on 2026-09-23 (batch S1 review), not written from memory.
Stored event context
Alongside the fields your blueprint maps (where email, phone, names and address are stored as normalised SHA-256 by default), each file 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) |
In each NDJSON line these are the context object, with anonymous_id, user_id, device_id, message_id, event_id, click_ids and collected_ids beside the mapped properties. In CSV and Parquet with the canonical schema they are the ip_address, user_agent, page_url, page_path and page_referrer columns and, in full, the context_json column. In blueprint schema mode only the columns you declare are written.
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
The integration’s consent category in Signal decides which events reach Google Cloud Storage; events that fail it are never written. Every row also carries the visitor’s consent state (context.consent / context_json, and consent_analytics / consent_marketing in @root) so downstream queries can apply their own rules.
Testing
- Enable the integration in Signal and trigger a test event on your website.
- Open the Cloud Storage console and navigate to your bucket.
- Browse to the prefix path and verify that event files are appearing.
- Click on a file to download and inspect the event data.
- In Signal, check the Live Events view to confirm delivery status shows as successful.
Troubleshooting
| Problem | Solution |
|---|---|
| Events not appearing in the bucket | Verify the bucket name, project ID, and prefix are correct. |
Permission denied (403) | The service account lacks Storage Object Creator role on the bucket. Add it in IAM & Admin > IAM. |
Bucket not found (404) | The bucket does not exist. Verify the bucket name (globally unique, case-sensitive). |
| Invalid service account JSON | Ensure you pasted the complete JSON key file content, including all fields. |
| Files appearing but empty | Check the batch settings. Events are buffered before flushing to files. |
| Bucket location mismatch | The bucket location does not affect connectivity but may impact latency. Choose a location close to your Signal deployment. |
| Uniform bucket-level access errors | If the bucket uses uniform bucket-level access, ensure the service account has the role at the bucket level via IAM, not legacy ACLs. |
Visit Google Cloud Storage documentation for full API reference, lifecycle management, and access control guides.