Alertalytix Documentation

This guide walks you through embedding the browser collector, managing consent, and fetching automated decisions. After creating a domain, copy the public key and follow the steps below.

1. Embed the public key

The key appears on the domain detail screen in the dashboard. Add it as a meta tag so the collector can discover it.

<meta name="alx-public-key" content="YOUR_PUBLIC_KEY">

2. Load the browser collector

Include the script below. It uses an iframe bridge to post to /collect/v1 from the same origin and automatically sends a pageview.

<meta name="alx-public-key" content="YOUR_PUBLIC_KEY">
<script src="https://collector.alertalytix.cloud/v1/alertalytix.js"></script>

3. Consent flow

Require opt-in before collection by registering a callback. When the callback resolves true, events are sent; otherwise they are skipped.

<!-- Require consent before sending events -->
<meta name="alx-consent" content="required">
<meta name="alx-consent-callback" content="onAlxConsent">
<script src="https://collector.alertalytix.cloud/v1/alertalytix.js"></script>

<script>
  window.onAlxConsent = async () => {
    try {
      return !!(window.Cookiebot && Cookiebot.consent && Cookiebot.consent.statistics);
    } catch (err) {
      return false;
    }
  };
</script>
  • No callback or a rejected promise means zero collection (fail safe).
  • Manual calls to ALX.sendEvent(...) also respect the consent requirement.
  • See /examples/consent_banner.html for a reference implementation.

4. Transport & security

  • The collector posts events through public/collector.html using postMessage, eliminating CORS configuration.
  • Alertalytix validates that the domain exists, is active, and belongs to the authenticated tenant.
  • Add <meta name="alx-disable-tracking" content="1"> to suppress automatic sends on specific pages.

5. Offline retry

When offline, events stay in a queued buffer inside the iframe. They replay automatically once connectivity returns.

6. Manual events

Send custom events with ALX.sendEvent('signup', { plan: 'pro' }). Consent enforcement, deduplication and retry are automatic.

Server Event API

Send events directly from your backend when browser instrumentation is unavailable or when you need to enrich requests with server-side data. Authenticate using an API key scoped to the domain you want to monitor.

Endpoint

POST https://api.alertalytix.cloud/api/v1/events
Headers:
  Content-Type: application/json
  X-API-Key: YOUR_API_KEY

Payload

  • event_type (string, required): Describes the action, e.g. login.succeeded.
  • context.ip_address (string, recommended): Source IP. Uses the request IP if omitted.
  • occurred_at (ISO8601, optional): Timestamp when the event happened. Defaults to now.
  • subject.identity_key / subject.session_token (optional): Hash identifiers to respect PII policy.
  • context.user_agent, traits, metadata, attrs (optional): Any additional fields you want stored for future analysis.

JSON payload schema

The Events API accepts JSON that matches the schema below. Optional objects may be omitted entirely. Unknown keys are preserved under attrs.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["event_type"],
  "additionalProperties": false,
  "properties": {
    "event_type": { "type": "string", "minLength": 1 },
    "occurred_at": { "type": "string", "format": "date-time" },
    "context": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "ip_address": { "type": "string" },
        "user_agent": { "type": "string" },
        "device_fingerprint": { "type": "string" },
        "host": { "type": "string" }
      }
    },
    "subject": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "identity_key": { "type": "string" },
        "session_token": { "type": "string" }
      }
    },
    "traits": { "type": "object", "additionalProperties": true },
    "metadata": { "type": "object", "additionalProperties": true },
    "attrs": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "subject": { "$ref": "#/properties/subject" },
        "context": { "$ref": "#/properties/context" },
        "traits": { "$ref": "#/properties/traits" },
        "metadata": { "$ref": "#/properties/metadata" }
      }
    }
  }
}

Example request

{
  "event_type": "login_attempt",
  "occurred_at": "2024-10-01T12:34:56Z",
  "context": {
    "ip_address": "192.0.2.10",
    "user_agent": "BackendService/1.0"
  },
  "subject": {
    "identity_key": "hashed-user-id",
    "session_token": "session-uuid"
  },
  "traits": {
    "plan": "pro",
    "channel": "support"
  },
  "metadata": {
    "source": "manual-review"
  }
}

Response

{
  "event_id": "1234567890123"
}

The event flows through the same processing pipeline as browser events: risk signals, scoring and policy decisions are attached asynchronously.

Decision API

Retrieve the automated decision (allow, challenge or deny) for an ingested event. Use an API key with query scope.

Endpoint

GET https://api.alertalytix.cloud/api/v1/events/:event_id/decision
Headers:
  X-API-Key: YOUR_API_KEY

Response example

{
  "event_id": "12345678-1234-1234-1234-1234567890ab",
  "decision": {
    "id": "8460e3b3-adfa-4508-aacd-8ba85e02649d",
    "decision": "allow",
    "policy_version": "v1",
    "decided_at": "2024-09-01T12:34:56Z",
    "reasons": { "risk": 42 }
  }
}

Recommended flow

  1. Collect any client-side context your backend needs (hashed identifiers, session tokens, etc.) and send it to the server over TLS. If you want the device fingerprint, call ALX.createHarvester() in the browser to obtain the exact_id value and include it in that payload.
  2. Your backend authenticates with an API key and calls POST https://api.alertalytix.cloud/api/v1/events to create the event. Include event_type, context.ip_address and any identifiers or traits (such as exact_id) that you want analysed. Use the collector endpoint (POST /collect/v1) only when sending directly from the browser.
  3. Poll GET https://api.alertalytix.cloud/api/v1/events/:event_id/decision until a 200 response is returned, backing off between attempts.

Decisions are created asynchronously. Expect a short delay (hundreds of milliseconds) before the decision becomes available.

cURL

# 1) Ingest (server-side)
curl -sS -X POST "https://api.alertalytix.cloud/api/v1/events" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d '{
    "event_type": "login_attempt",
    "occurred_at": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",
    "context": { "ip_address": "192.0.2.10", "user_agent": "Mozilla/5.0" },
    "subject": { "identity_key": "hashed-user-id", "session_token": "SESSION_TOKEN" },
    "traits": { "email_sha256": "..." }
  }'

# => { "event_id": "..." }

# 2) Decision
curl -sS "https://api.alertalytix.cloud/api/v1/events/EVENT_ID/decision" \
  -H "X-API-Key: YOUR_API_KEY"

Node.js

import fetch from "node-fetch";

export async function decide({ apiKey, ipAddress, identityHash, sessionToken, exactId }) {
  const ingest = await fetch("https://api.alertalytix.cloud/api/v1/events", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": apiKey
    },
    body: JSON.stringify({
      event_type: "login_attempt",
      occurred_at: new Date().toISOString(),
      context: { ip_address: ipAddress, user_agent: "Backend/1.0" },
      subject: {
        identity_key: identityHash,
        session_token: sessionToken
      },
      traits: exactId ? { exact_id: exactId } : undefined
    })
  });
  if (!ingest.ok) throw new Error(`ingest failed: ${ingest.status}`);
  const { event_id } = await ingest.json();

  for (let attempt = 0; attempt < 5; attempt += 1) {
    const res = await fetch(`https://api.alertalytix.cloud/api/v1/events/${event_id}/decision`, {
      headers: { "X-API-Key": apiKey }
    });
    if (res.status === 200) return (await res.json()).decision;
    await new Promise(resolve => setTimeout(resolve, 200));
  }
  throw new Error("decision timeout");
}

Ruby

require "net/http"
require "json"

def decide(api_key:, ip_address:, identity_hash:, session_token: nil)
  uri = URI("https://api.alertalytix.cloud/api/v1/events")
  req = Net::HTTP::Post.new(uri)
  req["Content-Type"] = "application/json"
  req["X-API-Key"] = api_key
  req.body = {
    event_type: "login_attempt",
    occurred_at: Time.now.utc.iso8601,
    context: { ip_address: ip_address, user_agent: "Backend/1.0" },
    subject: { identity_key: identity_hash, session_token: session_token }
  }.to_json

  res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
    http.request(req)
  end
  event_id = JSON.parse(res.body).fetch("event_id")

  5.times do
    decision_uri = URI("https://api.alertalytix.cloud/api/v1/events/#{event_id}/decision")
    decision_req = Net::HTTP::Get.new(decision_uri)
    decision_req["X-API-Key"] = api_key
    res_decision = Net::HTTP.start(decision_uri.host, decision_uri.port, use_ssl: decision_uri.scheme == "https") do |http|
      http.request(decision_req)
    end
    return JSON.parse(res_decision.body)["decision"] if res_decision.code == "200"
    sleep 0.2
  end

  raise "decision timeout"
end
Keep API keys on the server only—never expose them to client code. Scope keys to query; other scopes will be introduced later as needed.