Event forwarding

Event forwarding sends your EyalSec events to your own SIEM or log platform as they arrive, over HTTPS. Your security team can then search, alert and correlate on them next to everything else they already collect. This page covers what is sent, the settings, how to check the signature, and step-by-step recipes for Microsoft Sentinel, Google Security Operations, Splunk, Elastic and Datadog.

Overview

Stream your events to your own SIEM over HTTPS. You add up to 5 destinations on Settings > Event forwarding. Each destination is an HTTPS address that accepts a POST, such as an HTTP Event Collector, a webhook feed or a logs intake URL. EyalSec sends new events to it within a few seconds of storing them, in batches, and signs every request so your receiver can check it came from us.

Forwarding runs beside your dashboard, not in front of it. A slow or unreachable destination never delays your machines, your events list or your other destinations.

What is sent

A destination receives the events your own events list shows, and only yours:

  • Your account only. Events from other accounts are never sent to your destinations.
  • Products you hold. Only events from products your account has.
  • Not suppressed events. An event the list puts under Suppressed is not sent. Events you chose to discard on arrival are never stored, so they are never sent either.
  • Not hidden events. Your Hide and Show rules and your saved filters apply, as they do on the events list with All machines selected.

On top of that, each destination has its own minimum severity and, optionally, a list of products.

Each request carries a batch of events. One event looks like this:

{
  "time": "2026-10-19T09:12:03Z",
  "event_key": "5f0c7a1e9b2d4c3a8e6f1b0d2c4a6e8f",
  "vendor": "EyalSec",
  "product": "es-python",
  "agent_kind": "python",
  "severity": "high",
  "message": "es-python high: os.system from http-param on web-1",
  "machine": "web-1",
  "sink": "os.system",
  "source": "http-param",
  "value": "ping -c1 8.8.8.8; id",
  "count": 3,
  "origin": "http-param:host",
  "trace": "File \"/srv/app/views.py\", line 42, in ping",
  "impact_tags": [{"label": "rce", "sev": "critical"}]
}
Field Meaning
time When EyalSec stored this batch, in UTC.
event_key A stable id for the row on your events list. Every occurrence of the same finding has the same key, so you can group on it.
product, agent_kind The product and its events list (python for es-python, browser for es-chromium).
severity info, low, medium, high or critical.
message One readable line, for tools that show a message column.
machine The machine's name on your Machines page.
sink, source, value The operation, where the data came from, and the value that reached it.
count How many times it happened in this batch. Add these up for a total.
context, origin, repr_created, trace The same detail the event detail panel shows, when there is some.
sanitize_reason, impact_tags The same notes and impact classes the events list shows.
test true on a test event, and absent otherwise.

Long values are cut at a few kilobytes and end in ...[truncated]. Empty fields are left out.

Add a destination

To add a destination:

  1. Open Settings > Event forwarding.
  2. Paste the HTTPS address your SIEM gives you, pick a Format, and click Add.
  3. Copy the signing secret that appears. It is shown once. Store it where your receiver can read it.
  4. If your SIEM needs a token, fill in Auth header name and Auth header value.
  5. Click Send test event. The page tells you what the destination answered.

Every setting saves as soon as you change it, and the box says Saved. There is no Save button.

Settings

Each destination has these settings.

Setting Choices Default
Name A label for you, up to 80 characters empty
Sending On or off. Off keeps the settings and sends nothing. On
HTTPS address The address events are POSTed to required
Format JSON array, JSON lines, or Splunk HEC (see Formats) JSON array
Send events of A minimum severity: info, low, medium, high or critical Medium and above
Products The products to send. None ticked sends every product. none ticked
Auth header name and value One extra header sent on every request, such as Authorization: Splunk <token> none

The auth header value is stored encrypted and is never shown again. To change it, type a new one. To remove it, clear the header name.

The box also shows counters since the destination was added: Delivered, Failed (gave up after retries), Dropped (could not be queued, see Delivery) and Waiting (queued right now), plus the time of the last delivery and the last error.

Formats

The format decides how a batch is written in the request body.

Format Body Content-Type Use it for
JSON array (json) [{event}, {event}] application/json Microsoft Sentinel, Datadog, Elastic, most webhooks
JSON lines (ndjson) one event object per line application/x-ndjson Google SecOps, Logstash, tools that split on newlines
Splunk HEC (splunk_hec) one HEC envelope per line, with the event under event application/json Splunk HTTP Event Collector

A Splunk HEC envelope looks like this:

{"time":1760865123.000,"host":"web-1","source":"eyalsec","sourcetype":"eyalsec:event","event":{...}}

A batch holds at most 500 events and stays under about 900 KB, so it fits the request limits of the platforms below.

Request headers

Every request carries these headers.

Header Value
X-EyalSec-Signature sha256= and the hex HMAC-SHA256 of the body. See Check the signature.
X-EyalSec-Delivery An id for the batch. It stays the same when a batch is retried, so you can drop duplicates.
X-EyalSec-Timestamp When this attempt was sent, in Unix seconds.
X-EyalSec-Event-Count How many events are in the body.
User-Agent EyalSec-Forwarder/1

Plus your own auth header, if you set one.

Check the signature

Checking the signature tells your receiver the request came from EyalSec and was not changed on the way. The signature is an HMAC-SHA256 of the exact request body, keyed with your signing secret as text, written as sha256= followed by lowercase hex.

Compute it over the raw body bytes, before any JSON parsing, and compare in constant time.

Python:

import hashlib
import hmac

def is_from_eyalsec(secret: str, body: bytes, header: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header or "")

Node.js:

const crypto = require("crypto");

function isFromEyalSec(secret, rawBody, header) {
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(header || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

You can check a secret by hand with OpenSSL:

printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET"

The signature covers the body only. To refuse a replayed request, remember the X-EyalSec-Delivery ids you have seen for a few hours and drop repeats.

To replace a secret, click New signing secret. The old one stops working at once, so update your receiver as soon as you copy the new one.

Delivery and retries

Events are delivered in batches, usually within a few seconds of arriving. Any 2xx answer counts as delivered.

  • Retries. A batch is retried after a timeout, a connection error, HTTP 408, 425, 429 or any 5xx. Retries wait about 2, 4, 8, 16 and 32 seconds, and a Retry-After header up to 60 seconds is honoured. After 6 tries the batch is counted as Failed and the error is shown.
  • No retry. Any other 4xx, and any redirect, is reported at once and not retried: it will not succeed on its own. Fix the address or the token and send a test event.
  • Timeouts. Each request has 15 seconds to get an answer.
  • Queue. Up to 10,000 events can wait for one destination. When the queue is full, for example while the destination is down, new events for it are counted as Dropped instead of waiting. They stay on your events list.
  • Order. Batches for one destination are sent one at a time, in order.

Forwarding starts with new events. Events stored before you added a destination are not sent; use Export for those.

Addresses EyalSec will not send to

To keep a destination from being pointed at a network it should not reach, EyalSec only sends to public HTTPS addresses:

  • The address must start with https://, and its certificate must be valid for its host name. A self-signed certificate is refused.
  • A host that points at a private, loopback, link-local or cloud metadata address is refused when you save it. The check runs again on every connection, so a name that later changes to point inside a private network is refused then too.
  • Redirects are not followed. Use the final address.
  • Put credentials in the auth header, not in the address. The address is shown on the settings page to anyone who can sign in to your account.

Recipes

These recipes connect the platforms security teams use most. Each one needs an HTTPS address that accepts a POST and, usually, a token for the auth header.

Microsoft Sentinel

Sentinel stores custom data through the Azure Monitor Logs Ingestion API. That API takes a short-lived Microsoft Entra token, so put a Logic App or an Azure Function in front of it. Both start the same way:

  1. In your Log Analytics workspace, create a custom table such as EyalSec_CL, with a data collection rule (DCR) and a data collection endpoint (DCE). Add the columns you want, for example severity, product, machine, sink, source, value, count and event_key.
  2. In the DCR, set the transform to source | extend TimeGenerated = todatetime(['time']).

Option 1: Logic App.

  1. Create a Logic App with the trigger When a HTTP request is received. Save it and copy its URL. The URL carries its own sig= key, so no auth header is needed.
  2. Turn on the Logic App's managed identity and give it the Monitoring Metrics Publisher role on the DCR.
  3. Add an HTTP action: method POST, URI https://<your DCE>/dataCollectionRules/<DCR immutable id>/streams/Custom-EyalSec_CL?api-version=2023-01-01, body the trigger body, authentication Managed identity with audience https://monitor.azure.com.
  4. In EyalSec, add the Logic App URL with format JSON array.

Option 2: Azure Function. Use this when you want to check the signature before anything is stored.

  1. Create a Python HTTP-triggered function with a managed identity that holds Monitoring Metrics Publisher on the DCR.
  2. Check the signature, then upload:
import hashlib, hmac, json, os
import azure.functions as func
from azure.identity import DefaultAzureCredential
from azure.monitor.ingestion import LogsIngestionClient

client = LogsIngestionClient(os.environ["DCE_URL"], DefaultAzureCredential())

def main(req: func.HttpRequest) -> func.HttpResponse:
    body = req.get_body()
    expected = "sha256=" + hmac.new(os.environ["EYALSEC_SECRET"].encode(), body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, req.headers.get("X-EyalSec-Signature", "")):
        return func.HttpResponse(status_code=401)
    client.upload(rule_id=os.environ["DCR_ID"], stream_name="Custom-EyalSec_CL", logs=json.loads(body))
    return func.HttpResponse(status_code=204)
  1. In EyalSec, add the function URL with format JSON array, auth header name x-functions-key and the function key as the value.

Google Security Operations

Google Security Operations (formerly Chronicle) takes events through a webhook feed. Use a webhook feed, not the legacy Ingestion API: Google retires that API on 2027-07-20, and a webhook feed is the current path for pushing events over HTTPS.

  1. In Google SecOps, go to SIEM Settings > Feeds and add a feed with source type Webhook. Pick the log type your team uses for custom JSON sources, and set the split delimiter to a newline (\n).
  2. Copy the feed's endpoint URL. Generate its secret key.
  3. In the Google Cloud console for the same project, create an API key restricted to the Chronicle API.
  4. In EyalSec, add the endpoint URL with ?key=<your API key> on the end, format JSON lines. Set the auth header name to X-Webhook-Access-Key and the value to the feed's secret key. If your feed page names different headers, use the names it shows.
  5. Send a test event, then search for EyalSec in Google SecOps. Add a parser for the log type to map the fields into UDM.

Splunk

Splunk receives events through the HTTP Event Collector (HEC).

  1. In Splunk, go to Settings > Data inputs > HTTP Event Collector, add a token, and pick the index. Set the source type to eyalsec:event or leave it automatic. Leave indexer acknowledgment off.
  2. Make sure HEC is reachable over HTTPS with a certificate valid for its host name. Splunk Cloud addresses look like https://http-inputs-<stack>.splunkcloud.com/services/collector/event; Splunk Enterprise uses port 8088 by default.
  3. In EyalSec, add https://<host>:8088/services/collector/event with format Splunk HEC. Set the auth header name to Authorization and the value to Splunk <your HEC token>.
  4. Send a test event and search sourcetype="eyalsec:event".

Elastic

Elastic takes events through Elastic Agent's Custom HTTP Endpoint Logs integration, which can also check the signature for you.

  1. In Kibana, add the Custom HTTP Endpoint Logs integration to an agent policy. Set the listen address and port, turn on SSL with a certificate for the agent's public host name, and pick a dataset name such as eyalsec.events.
  2. Under authentication, set a secret header and value, for example Authorization and a long random string.
  3. Under HMAC, set the header to X-EyalSec-Signature, the key to your EyalSec signing secret, the type to sha256 and the prefix to sha256=.
  4. In EyalSec, add https://<agent host>:<port>/<path> with format JSON array, and the same auth header name and value.

If you use Logstash instead, an http input with codec => json and SSL turned on takes the JSON array format and makes one event per element.

Datadog

Datadog receives events through its HTTP logs intake.

  1. In Datadog, create an API key under Organization Settings > API Keys.
  2. In EyalSec, add the logs intake address for your Datadog site with format JSON array, for example https://http-intake.logs.datadoghq.com/api/v2/logs?ddsource=eyalsec&service=eyalsec for US1 or https://http-intake.logs.datadoghq.eu/api/v2/logs?ddsource=eyalsec&service=eyalsec for EU.
  3. Set the auth header name to DD-API-KEY and the value to your API key.
  4. Send a test event and search source:eyalsec in Log Explorer. The message field is shown as the log line; add a remapper if you want machine as the host.

Troubleshooting

The last error on the destination's box usually says what to fix.

You see What to do
this address points inside a private network Use the public address of your receiver.
the destination answered HTTP 401 or 403 Check the auth header name and value.
the destination answered with a redirect Use the final address, often with or without a trailing /.
the destination did not answer in time Check the receiver is up and answers within 15 seconds.
a certificate error The receiver needs a certificate from a public authority for its host name.
Dropped keeps growing The destination has been down for a while. Fix it, then send a test event; new events flow again at once.
nothing arrives, and no error Check Sending is on, the minimum severity, the product list, and that the events show on your events list.

Something unclear or missing on this page? Email support@eyalsec.com.

EyalSec Pricing Docs Security Research Vulnerabilities Play Security glossary About Contact עברית Login Get started Protect my Python app