Elasticsearch

Datafly Signal indexes first-party events into Elasticsearch for full-text search, real-time analytics, log aggregation, and observability dashboards with Kibana.

Why route Elasticsearch through Datafly Signal

  • The data lands in infrastructure you own. Signal writes to your Elasticsearch 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 index holds email_sha256, phone_sha256 and 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 Elasticsearch 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. Elasticsearch is gated by the same consent state as the rest of your stack, so a withdrawal applies everywhere at once.

Prerequisites

Before configuring Elasticsearch in Signal, you need an Elasticsearch cluster (self-hosted or Elastic Cloud), an index, and an API key for authentication.

Set Up an Elasticsearch Cluster

You have two options:

Option A: Elastic Cloud (Managed)

  1. Sign up at cloud.elastic.co.
  2. Click Create deployment.
  3. Choose a cloud provider and region.
  4. Select a deployment size based on your expected data volume.
  5. Click Create deployment.
  6. Note the Cloud ID from the deployment overview and the Elasticsearch endpoint URL.

Option B: Self-Hosted

  1. Install Elasticsearch using the official guide.
  2. Ensure the cluster is accessible from your Signal infrastructure.
  3. Note the Endpoint URL (e.g. https://elasticsearch.example.com:9200).

Create an Index

Create an index with an appropriate mapping for event data:

curl -X PUT "https://your-cluster:9200/datafly-events" \
  -H "Content-Type: application/json" \
  -d '{
    "mappings": {
      "properties": {
        "@timestamp":   { "type": "date" },
        "received_at":  { "type": "date" },
        "event_type":   { "type": "keyword" },
        "event_name":   { "type": "keyword" },
        "anonymous_id": { "type": "keyword" },
        "user_id":      { "type": "keyword" },
        "org_id":       { "type": "keyword" },
        "pipeline_id":  { "type": "keyword" },
        "properties":   { "type": "object" },
        "context":      { "type": "object" },
        "collected_ids": { "type": "object" },
        "click_ids":    { "type": "object" }
      }
    }
  }'

These are the field names Signal writes. keyword suits the IDs and names, and @timestamp (the client event time) is the index time field. Fields inside properties and context are mapped dynamically as they first appear; each distinct field counts towards the index’s field limit (index.mapping.total_fields.limit, 1,000 by default), so a vertical preset with many events may need a higher limit.

Generate an API Key

  1. In Kibana, go to Stack Management > API Keys > Create API key. Or use the API:
curl -X POST "https://your-cluster:9200/_security/api_key" \
  -H "Content-Type: application/json" \
  -u "elastic:your_password" \
  -d '{
    "name": "datafly-signal",
    "role_descriptors": {
      "datafly_writer": {
        "cluster": [],
        "index": [
          {
            "names": ["datafly-events"],
            "privileges": ["write", "create_index"]
          }
        ]
      }
    }
  }'
  1. The response contains an encoded field — this is the Base64-encoded API key to use in Signal.
⚠️

Store the API key securely. Create keys with the minimum required privileges (write access to the specific index only).

Configuration

FieldTypeRequiredDescription
addressesstringOne of theseComma-separated list of cluster URLs (e.g. https://es-1:9243,https://es-2:9243). Also accepts a single endpoint value.
cloud_idstringOne of theseThe Elastic Cloud deployment ID. Alternative to addresses for Elastic Cloud deployments.
indexstringYesThe target index name to write documents to.
api_keysecretOne of theseBase64-encoded API key (preferred).
usernamestringOne of theseBasic auth username — use only when API keys aren’t available.
passwordsecretOne of theseBasic auth password.

Signal Setup

Quick Setup

  1. Navigate to Integrations in the sidebar.
  2. Open the Integration Library tab.
  3. Find Elasticsearch or filter by Database.
  4. Click Install, select a variant if available, and fill in the required fields.
  5. 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/elasticsearch/install \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Elasticsearch",
    "variant": "default",
    "config": {
      "addresses": "https://my-cluster.es.us-east-1.aws.elastic-cloud.com:9243",
      "index": "datafly-events",
      "api_key": "BASE64_ENCODED_API_KEY"
    },
    "delivery_mode": "server_side"
  }'

Schema

Each event is indexed as one document with the message_id as its _id; a redelivered event replaces its own document:

{
  "@timestamp": "2026-09-23T10:00:00Z",
  "received_at": "2026-09-23T10:00:00Z",
  "event_type": "track",
  "event_name": "purchase",
  "anonymous_id": "anon-7f3c",
  "user_id": "user-1042",
  "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
}

The document has no message_id field: use _id. user_id is an empty string for an anonymous visitor. Signal indexes one document per request; Elastic recommends the bulk API for high ingest rates, which Signal does not use yet.

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_type or policy_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 flags consent_analytics and consent_marketing, and the hashed identity email_sha256, phone_sha256, first_name_sha256, last_name_sha256, name_sha256.
  • identify adds name_sha256 and postal_code_sha256 at the top level.
  • An event the preset does not list stores its envelope and the @root block only.

For example:

curl -s "https://your-cluster:9200/datafly-events/_search" -H "Content-Type: application/json" -d '{
  "query": { "term": { "event_name": "purchase" } },
  "_source": ["@timestamp", "properties.transaction_id", "properties.value", "properties.@root.email_sha256"],
  "sort": [{ "@timestamp": "desc" }]
}'

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.

StoredWhat it contains
Visitor requestIP address, user agent, Accept-Language, locale and timezone
PageURL including its query string, path, title and referrer
CampaignUTM source, medium, campaign, term and content
ConsentThe visitor’s consent state
LocationGeoIP country, region and city, and the network (ASN)
DeviceScreen size; for app events the OS and version, manufacturer, model, advertising ID and IDFV when the app collected them
Vendor IDsCookie and click identifiers in vendor_ids (for example the _fbp browser ID, gclid, ttclid, the GA client ID)
Session and librarySession ID, number and engagement time; the collector’s name and version
Partner attributesProfile 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.

Elasticsearch 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

  1. Enable the integration in Signal and trigger a test event on your website.
  2. In Kibana, go to Discover and select the datafly-events index pattern.
  3. Verify that event documents are appearing with the correct fields.
  4. Or query directly:
curl "https://your-cluster:9200/datafly-events/_search?size=10&sort=@timestamp:desc" \
  -H "Authorization: ApiKey YOUR_API_KEY"
  1. In Signal, check the Live Events view to confirm delivery status shows as successful.

Troubleshooting

ProblemSolution
Events not appearing in the indexVerify the endpoint, index name, and API key are correct.
Unauthorized (401)The API key is invalid or has been revoked. Generate a new API key.
Forbidden (403)The API key lacks write permission on the index. Create a new key with write privileges on the target index.
index_not_found_exceptionThe index does not exist. Create it first, or enable create_index privilege on the API key so it can be auto-created.
Connection timeoutVerify the endpoint URL is correct and accessible from Signal’s network. Check firewall rules.
Mapping conflictsIf fields have different types than expected, check the index mapping. Consider using an index template for consistent mappings.
Cluster health yellow/redCheck cluster health with GET _cluster/health. Yellow means replicas are unassigned; red means primary shards are missing.

Visit Elasticsearch documentation for full API reference, index lifecycle management, and Kibana dashboard setup.

See also