Splunk HEC
Send events to OBSESC with the Splunk HTTP Event Collector protocol, including JSON, raw and useACK clients.
OBSESC implements the Splunk HTTP Event Collector (HEC) protocol, so any tool with a HEC output can add OBSESC as a second destination. You keep the same token scheme and payload format. OBSESC accepts HEC only. It doesn’t receive the Splunk forwarder-to-indexer protocol, so a forwarder must send to OBSESC through a HEC output.
Endpoints
| Method and path | Purpose |
|---|---|
POST /services/collector | JSON events (same as /event) |
POST /services/collector/event | JSON events: one object, newline-separated objects, or back-to-back objects |
POST /services/collector/raw | Raw text. Each non-empty line is one event |
GET /services/collector/health | Health probe. Returns {"text":"HEC is healthy","code":17}, no token needed |
POST /services/collector/ack | Acknowledgement status for useACK clients |
The default port is 8088 (ingest.hec_port). The stock CloudFormation stack doesn’t open 8088, so add an inbound security group rule first. See Network configuration.
Request bodies can be up to 16 MiB by default (ingest.hec_max_body_bytes). Content-Encoding: gzip is accepted.
Configure tokens first
The HEC listener fails closed. Until you configure at least one token, it rejects every request with 403 Invalid token.
security:
hec_tokens:
- "<your-hec-token>"
You can also put hec_tokens (a JSON array) in the node’s Secrets Manager secret. See Configuration examples. Clients send Authorization: Splunk <token>, and the token must match exactly.
Quick test
curl -sS -X POST "http://obsesc.your-domain.internal:8088/services/collector/event" \
-H "Authorization: Splunk $OBSESC_HEC_TOKEN" \
-H "Content-Type: application/json" \
-d '{"time": 1767225600.123, "sourcetype": "checkout", "host": "web-1", "event": "order placed id=42"}'
A successful request returns {"text":"Success","code":0}.
Client configurations
OpenTelemetry Collector (splunk_hec exporter)
exporters:
splunk_hec/obsesc:
endpoint: http://obsesc.your-domain.internal:8088/services/collector
token: ${env:OBSESC_HEC_TOKEN}
sourcetype: checkout
For the Collector, OTLP is usually the better choice, because it keeps resource attributes and also carries traces.
Fluent Bit (splunk output)
[OUTPUT]
Name splunk
Match *
Host obsesc.your-domain.internal
Port 8088
Splunk_Token ${OBSESC_HEC_TOKEN}
TLS Off
Vector (splunk_hec_logs sink)
[sinks.obsesc_hec]
type = "splunk_hec_logs"
inputs = ["app_logs"]
endpoint = "http://obsesc.your-domain.internal:8088"
default_token = "${OBSESC_HEC_TOKEN}"
sourcetype = "checkout"
encoding.codec = "text"
Cribl Stream
Add a Splunk HEC destination with the endpoint http://obsesc.your-domain.internal:8088/services/collector/event and a token from security.hec_tokens. Then add a route or a Clone function so the same stream also goes to OBSESC.
How events are mapped
JSON endpoints
| HEC field | OBSESC field |
|---|---|
sourcetype, else source, else none | service. With neither, the event goes to ingest.default_service |
event (string) | body, as sent |
event (object) | body holds the full JSON, and its fields are also flattened into dotted attributes |
time (epoch seconds, fractional allowed, number or string) | timestamp. If it’s missing, the node’s receive time is used |
host, index | host and index attributes |
source (when it isn’t already the service) | source attribute |
fields | One attribute per key |
Every event must have an event field. A batch where any event is missing it, or any event isn’t valid JSON, is rejected whole with code 6. Nothing from that batch is stored, so the forwarder can safely resend it.
Raw endpoint
/services/collector/raw takes its metadata from the query string, not the body:
curl -sS -X POST \
"http://obsesc.your-domain.internal:8088/services/collector/raw?sourcetype=checkout&host=web-1" \
-H "Authorization: Splunk $OBSESC_HEC_TOKEN" \
--data-binary @app.log
Each non-empty line becomes one event, with trailing \r removed. Raw events have no per-line timestamp, so they are stamped with the time the node received them.
Indexer acknowledgement (useACK)
Clients that send the X-Splunk-Request-Channel header get an ackId in each success response:
{"text":"Success","code":0,"ackId":7}
The client then polls POST /services/collector/ack with the same channel header and a body of {"acks":[7]}. An ackId is only issued after the batch is durable. A request to /ack without the channel header gets code 10, Data channel is missing.
Response codes
| HTTP | HEC code | Meaning |
|---|---|---|
| 200 | 0 | Success. The batch is durable |
| 401 | 2 | Token is required: no token was sent |
| 403 | 4 | Invalid token: the token isn’t in hec_tokens, or no tokens are configured |
| 400 | 6 | Invalid data format: malformed JSON, missing event, or a bad gzip body |
| 413 | n/a | Body, or decompressed body, is over the limit |
| 415 | n/a | Content-Encoding other than gzip or identity |
| 503 | 9 | Server is busy: backpressure or draining. Retry with backoff |
Delivery and retries
- JSON events that carry an explicit
timeare protected from duplicates: if a forwarder resends the identical batch soon after the original, it’s stored once. - Events without
time, and everything sent to/raw, are at-least-once. A resent batch can be stored twice.
Multiline on the raw endpoint
/raw is line-oriented, so a multi-line stack trace arrives as several events. If the forwarder can’t assemble multiline events itself, turn on node-side assembly with ingest.multiline.enabled: true. See Configuration examples.