Azure SQL Database
Datafly Signal writes first-party events into an Azure SQL Database table over the SQL Server wire protocol, with TLS encryption required.
Why route Azure SQL through Datafly Signal
- The data lands in infrastructure you own. Signal writes to your Azure SQL 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 table 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 Azure SQL 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 SQL is gated by the same consent state as the rest of your stack, so a withdrawal applies everywhere at once.
Prerequisites
Before configuring Azure SQL Database in Signal, you need an Azure account with an Azure SQL server, a database, a target table, and firewall rules configured to allow Signal access.
Create an Azure Account
If you don’t already have one, sign up at azure.microsoft.com.
Create an Azure SQL Server
- In the Azure portal, search for SQL servers and click Create.
- Select your Subscription and Resource group.
- Enter a Server name (e.g.
datafly-sql). The full hostname will bedatafly-sql.database.windows.net. - Select a Location close to your Signal deployment.
- Set the Server admin login and Password.
- Click Review + Create > Create.
Create a Database
- In the Azure portal, search for SQL databases and click Create.
- Select your server from the previous step.
- Enter a Database name (e.g.
analytics). - Choose a Compute + storage tier:
- Basic or Standard for low-volume workloads.
- General Purpose (vCore) for production workloads.
- Click Review + Create > Create.
Configure Firewall Rules
- Open your Azure SQL server in the portal.
- Go to Security > Networking.
- Under Firewall rules, add the IP addresses of your Signal deployment.
- Optionally enable Allow Azure services and resources to access this server if Signal runs within Azure.
- Click Save.
Never set the firewall to allow all IP addresses (0.0.0.0 to 255.255.255.255) in production. Restrict access to only the IP addresses of your Signal infrastructure.
Create a Table
Connect to the database using Azure Data Studio, SSMS, or the Azure portal Query Editor:
CREATE TABLE dbo.events (
message_id NVARCHAR(64) NOT NULL PRIMARY KEY,
event_type NVARCHAR(32) NOT NULL,
event_name NVARCHAR(255),
anonymous_id NVARCHAR(128),
user_id NVARCHAR(255),
ip_address NVARCHAR(45),
user_agent NVARCHAR(MAX),
page_url NVARCHAR(MAX),
page_path NVARCHAR(MAX),
page_referrer NVARCHAR(MAX),
org_id NVARCHAR(64),
pipeline_id NVARCHAR(64),
event_timestamp DATETIMEOFFSET,
received_at DATETIMEOFFSET,
properties_json NVARCHAR(MAX) CHECK (ISJSON(properties_json) = 1),
context_json NVARCHAR(MAX) CHECK (ISJSON(context_json) = 1)
);
CREATE INDEX IX_events_ts ON dbo.events (event_timestamp DESC);Create a Database User for Signal
Create a dedicated user with limited privileges:
CREATE LOGIN datafly_signal WITH PASSWORD = 'your_secure_password';
CREATE USER datafly_signal FOR LOGIN datafly_signal;
GRANT INSERT ON dbo.events TO datafly_signal;
GRANT SELECT ON dbo.events (message_id) TO datafly_signal;The SELECT on message_id alone is required: Signal checks IF NOT EXISTS (SELECT 1 ... WHERE message_id = ...) before each insert, so a user with INSERT only fails every write with The SELECT permission was denied on the object 'events'.
Configuration
| Field | Type | Required | Description |
|---|---|---|---|
server | string | Yes | The Azure SQL server hostname (e.g. myserver.database.windows.net). |
port | string | No | TCP port. Defaults to 1433. |
database | string | Yes | The target database name. |
schema_name | string | No | The schema to write to. Defaults to dbo. |
table_name | string | Yes | The target table name to insert rows into. |
username | string | Yes | The database username for authentication. |
password | secret | Yes | The database password for authentication. |
encrypt | string | No | TDS encryption: true (default; Azure SQL requires it), false or disable. With true and false the server certificate is verified (false still encrypts the login), against ca_certificate when set, otherwise the system roots, which Azure SQL’s certificate passes. |
ca_certificate | textarea | No | PEM bundle of a private certificate authority, for a SQL Server whose certificate the system roots do not trust. Azure SQL needs none. Refused with encrypt disable. |
Signal Setup
Quick Setup
- Navigate to Integrations in the sidebar.
- Open the Integration Library tab.
- Find Azure SQL Database 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_sql/install \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Azure SQL Database",
"variant": "default",
"config": {
"server": "datafly-sql.database.windows.net",
"port": "1433",
"database": "analytics",
"schema_name": "dbo",
"table_name": "events",
"username": "datafly_signal",
"password": "your_secure_password",
"encrypt": "true"
},
"delivery_mode": "server_side"
}'Schema
Signal inserts one row per event into exactly these sixteen columns, so the table must have them all (extra columns of your own are fine if they are nullable or have defaults):
| Column | Contents |
|---|---|
message_id | Unique per delivered event; the idempotency key (a redelivery is not inserted twice). |
event_type | track, page, identify, and so on. |
event_name | Signal event name (snake_case), for example purchase. |
anonymous_id | First-party visitor identifier. |
user_id | Logged-in user identifier; NULL when absent. |
ip_address, user_agent | Client IP and user agent from the visitor’s request. |
page_url, page_path, page_referrer | Page context. |
org_id, pipeline_id | Signal identifiers. |
event_timestamp | Client event time (UTC). |
received_at | Time Signal received the event (UTC). |
properties_json | The fields the blueprint mapped, as JSON (below). |
context_json | The event context as JSON: page, device, campaign, consent, IP and user agent. |
Signal inserts with IF NOT EXISTS (SELECT 1 ... WHERE message_id = @p1) INSERT ..., so a redelivered event is skipped; index message_id (the primary key above). A value too long for an NVARCHAR(n) column fails the insert. The JSON columns are NVARCHAR(MAX) with an ISJSON check, queried with JSON_VALUE and OPENJSON. Azure SQL Database also has a native json type; Signal’s inserts were verified against NVARCHAR(MAX) only.
What the properties hold
The blueprint decides what properties_json 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, the purchases with their order ID, value and hashed email:
SELECT TOP 10 event_timestamp,
JSON_VALUE(properties_json, '$.transaction_id') AS transaction_id,
CAST(JSON_VALUE(properties_json, '$.value') AS DECIMAL(12,2)) AS value,
JSON_VALUE(properties_json, '$.currency') AS currency,
JSON_VALUE(properties_json, '$."@root".email_sha256') AS email_sha256
FROM dbo.events
WHERE event_name = 'purchase'
ORDER BY event_timestamp DESC;Signal does not write mapped fields to their own columns; create views over properties_json for the fields you query often. 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 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
Azure SQL 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_json), 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.
- Connect to the Azure SQL database and query the target table:
SELECT TOP 10 message_id, event_type, event_name, event_timestamp
FROM dbo.events
ORDER BY event_timestamp DESC;- 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 the table | Verify the server, database, and table name are correct. |
| Connection refused / timeout | Check the firewall rules on the Azure SQL server. Add Signal’s IP addresses under Networking > Firewall rules. |
Login failed for user | The username or password is incorrect. Verify the credentials. Check that the login exists at the server level and the user is mapped to the database. |
INSERT permission denied | The database user lacks INSERT permission on the table. Run GRANT INSERT ON dbo.events TO datafly_signal;. |
The SELECT permission was denied | The user lacks SELECT on message_id, which the duplicate check reads. Run GRANT SELECT ON dbo.events (message_id) TO datafly_signal;. |
Invalid object name | The table does not exist or the user lacks access. Verify the table name and schema. |
| SSL/TLS errors | Azure SQL requires encrypted connections. Ensure your Signal deployment supports TLS 1.2+. |
| DTU/vCore throttling | The database tier may be too small. Monitor DTU/vCore usage in the Azure portal and scale up if needed. |
Visit Azure SQL Database documentation for full T-SQL reference, performance tuning, and scaling guides.