Azure Cosmos DB
Datafly Signal writes first-party events as documents into Azure Cosmos DB (Core SQL API) for globally distributed, multi-model NoSQL storage with millisecond latency.
Why route Azure Cosmos DB through Datafly Signal
- The data lands in infrastructure you own. Signal writes to your Azure Cosmos DB 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, last and full name and postcode are normalised (trimmed and lower-cased) and hashed with SHA-256 inside your own deployment, so the container holds
email_sha256,phone_sha256and the other digests rather than the raw values. Storing a raw value is a deliberate edit to the installed blueprint. - Only what the blueprint maps is stored. The properties Signal writes are exactly the fields the blueprint maps. Identify traits and client properties the blueprint does not map are never written, so a stray email in an event property cannot land in Cosmos DB in plain text.
- Presets for Retail, Insurance, Travel, Media & Streaming, 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, numbers typed as numbers and currency codes upper-cased. - 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.
- One consent decision governs every destination. Azure Cosmos DB is gated by the same consent state as the rest of your stack, so a withdrawal applies everywhere at once.
Prerequisites
Before configuring Azure Cosmos DB in Signal, you need an Azure account with a Cosmos DB account (Core SQL API), a database, and a container.
Create an Azure Account
If you don’t already have one, sign up at azure.microsoft.com.
Create a Cosmos DB Account
- In the Azure portal, search for Azure Cosmos DB and click Create.
- Select Azure Cosmos DB for NoSQL (Core SQL API).
- Enter an Account Name (e.g.
datafly-events). - Select a Location close to your Signal deployment.
- Choose a Capacity mode:
- Serverless — pay per request, ideal for variable workloads.
- Provisioned throughput — reserve RU/s for predictable workloads.
- Click Review + Create > Create.
- Once deployed, note the URI (endpoint) from the account overview page.
Create a Database and Container
- Open your Cosmos DB account in the Azure portal.
- Go to Data Explorer in the left sidebar.
- Click New Database.
- Enter a Database ID (e.g.
analytics). - Click OK.
- Click New Container within the database.
- Enter a Container ID (e.g.
events). - Set the Partition key to
/anonymous_id. Signal writes every item with the visitor’sanonymous_idas its partition key value (themessage_idwhen there is none), so the container must be partitioned on/anonymous_id; any other path rejects the write. - Click OK.
/anonymous_id has high cardinality and keeps one visitor’s events in one logical partition, so per-visitor queries stay in a single partition. Set partition_key_path to the container’s partition key path: Signal sends the document’s own value at that path, so the two always agree.
Get the Access Key
- In your Cosmos DB account, go to Settings > Keys.
- Copy the PRIMARY KEY (or Secondary Key as a backup).
- Note the URI if you haven’t already.
Store the access key securely. It provides full read/write access to the account. For production, consider using Azure RBAC with a managed identity instead of keys.
Configuration
| Field | Type | Required | Description |
|---|---|---|---|
endpoint | string | Yes | The Cosmos DB account endpoint URL (e.g. https://my-account.documents.azure.com:443/). |
database | string | Yes | The Cosmos DB database ID. Also accepts database_id. |
container | string | Yes | The Cosmos DB container ID. Also accepts container_id. |
partition_key_path | string | No | The container’s partition key path: /anonymous_id (the default), /id, /message_id, /user_id, /event_type, /event_name, /org_id or /pipeline_id. Any other path, or a nested or hierarchical one, is refused when the integration starts. |
account_key | secret | Yes | The Cosmos DB account primary or secondary access key. Also accepts access_key. |
Signal Setup
Quick Setup
- Navigate to Integrations in the sidebar.
- Open the Integration Library tab.
- Find Azure Cosmos DB or filter by Database.
- 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/azure_cosmosdb/install \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Azure Cosmos DB",
"variant": "default",
"config": {
"endpoint": "https://datafly-events.documents.azure.com:443/",
"database": "analytics",
"container": "events",
"partition_key_path": "/anonymous_id",
"account_key": "YOUR_PRIMARY_KEY"
},
"delivery_mode": "server_side"
}'Schema
Each event becomes one item created with CreateItem; a redelivered event (same id) returns a conflict, which Signal treats as already delivered:
{
"id": "<message_id>",
"event_type": "track",
"event_name": "purchase",
"anonymous_id": "anon-7f3c",
"user_id": "user-1042",
"timestamp": "2026-09-23T10:00:00Z",
"received_at": "2026-09-23T10:00:00Z",
"org_id": "...",
"pipeline_id": "...",
"properties": {
"@root": { "email_sha256": "86e0b9e5...", "event_name": "purchase", "utm_source": "google" },
"transaction_id": "T-1001",
"value": 59.98,
"currency": "GBP",
"items": [{ "item_id": "SKU-1", "item_name": "Shoe", "price": 29.99, "quantity": 2 }]
},
"context": { "ip": "203.0.113.9", "user_agent": "Mozilla/5.0", "page": { "url": "https://shop.example/checkout" } },
"collected_ids": null,
"click_ids": null
}user_id is an empty string for an anonymous visitor. Items cannot exceed 2 MB.
What the properties hold
The blueprint decides what properties contains, and only mapped fields are written (identify traits and unmapped client properties never are):
- The event’s own fields from the preset sit at the top level, for example
transaction_id,value,currency,items,insurance_typeorpolicy_term_months. - The preset’s global block sits under
@root: copies of the envelope (event_name,event_type,user_id,anonymous_id,event_id,message_id,event_timestamp,page_url,page_referrer,ip_address,user_agent,locale,timezone,source_id),utm_source,utm_medium,utm_campaign, the consent flagsconsent_analyticsandconsent_marketing, and the hashed identityemail_sha256,phone_sha256,first_name_sha256,last_name_sha256,name_sha256. identifyaddsname_sha256andpostal_code_sha256at the top level.- An event the preset does not list stores its envelope and the
@rootblock only.
For example:
SELECT c.timestamp, c.properties.transaction_id, c.properties["value"], c.properties["@root"].email_sha256
FROM c
WHERE c.event_name = 'purchase'Mapped fields stay inside properties; they are not lifted to top-level fields. What else a row carries outside the blueprint (the whole event context) is under review in #1414.
Stored event context
Alongside the fields your blueprint maps (where email, phone, names and address are stored as normalised SHA-256 by default), each document 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 stored as the document’s context field (Bigtable: the context column), using the event context’s JSON field names, 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
Cosmos DB is a first-party destination under your control, gated by the same consent state as every other destination. Each stored event also records the visitor’s consent at collection time: the preset writes consent_analytics and consent_marketing under @root in the properties (properties["@root"]), and the full consent object is in the context. Use them in views or queries if your governance needs a per-row consent filter.
Testing
- Enable the integration in Signal and trigger a test event on your website.
- In the Azure portal, open your Cosmos DB account > Data Explorer.
- Expand the database and container, then click Items.
- Verify that event documents are appearing with the correct partition key and data.
- In Signal, check the Live Events view to confirm delivery status shows as successful.
Troubleshooting
| Problem | Solution |
|---|---|
| Events not appearing in the container | Verify the endpoint, database ID, container ID, and partition key path are correct. |
Unauthorized (401) | The access key is invalid or has been regenerated. Get the current key from Settings > Keys. |
NotFound (404) | The database or container does not exist. Verify the IDs in Data Explorer. |
RequestEntityTooLarge (413) | Individual documents cannot exceed 2 MB. Check the event payload size. |
TooManyRequests (429) | You have exceeded the provisioned RU/s. Increase throughput or switch to autoscale. |
| Partition key mismatch | partition_key_path must name the container’s partition key path (Data Explorer > container Settings) and be one of the supported paths above. |
| Credential errors | Verify the endpoint URL ends with :443/ and the access key is the full key string from the Keys page. |
Visit Azure Cosmos DB documentation for full API reference, partitioning strategies, and throughput management.