Send product analytics to Elk
Trail receives product analytics over the Segment HTTP tracking API. If your app already sends events to Segment, RudderStack or Jitsu, pointing it at Elk is a one-line change. If it sends nothing yet, you install a stock open-source SDK — not ours.
What you need from us
Two values, which we issue per app:
| Endpoint | https://signal-receiver-709757975936.us-west1.run.app/r/<signal-id> |
|---|---|
| Write key | An opaque string. Sent as the HTTP Basic username, with an empty password — exactly as Segment does it. |
Your SDK appends the protocol path (/v1/batch, /v1/track, …) to the
endpoint itself. You only ever configure the base.
If you already send to Segment
Change the host and the write key. Nothing else — not the events, not the call shapes, not the SDK.
Node — @segment/analytics-node
import { Analytics } from '@segment/analytics-node'
const analytics = new Analytics({
writeKey: process.env.ELK_WRITE_KEY,
host: 'https://signal-receiver-709757975936.us-west1.run.app',
path: '/r/<signal-id>/v1/batch',
})
analytics.track({ userId: 'user-123', event: 'Match Started' })
Browser — analytics.js
analytics.load('<write-key>', {
integrations: {
'Segment.io': {
apiHost: 'signal-receiver-709757975936.us-west1.run.app/r/<signal-id>/v1',
},
},
})
Python — analytics-python
import segment.analytics as analytics
analytics.write_key = "<write-key>"
analytics.host = "https://signal-receiver-709757975936.us-west1.run.app/r/<signal-id>"
analytics.gzip = False # see "Turn compression off" below
analytics.track("user-123", "Match Started")
If you send nothing yet
Install any Segment-compatible SDK and use it normally. The call vocabulary is Segment's, and all of it is supported:
track | Something happened. Needs an event name. |
|---|---|
identify | This anonymous visitor is this user. Merges their history. |
page / screen | A view happened. |
group | This user belongs to this account or team. |
alias | These two identities are the same person. |
Send identify as soon as you know who someone is, with the same
anonymousId the earlier events carried. That is what stitches a signed-out
session to the account it became — without it, the journey starts at login and the
interesting half is missing.
No SDK at all
curl -X POST "https://signal-receiver-709757975936.us-west1.run.app/r/<signal-id>/v1/batch" \
-u "<write-key>:" \
-H 'Content-Type: application/json' \
-d '{"batch":[
{"type":"track","event":"Match Started","userId":"user-123",
"messageId":"evt-1","timestamp":"2026-08-12T09:00:00Z"}
]}'
Confirming it worked
A successful call returns 200 with a body:
{"success":true,"accepted":2}
If some events in a batch were unusable, you still get 200 — the batch was
fine, those items weren't — and the body says which and why:
{"success":true,"accepted":2,
"dropped":[{"index":2,"id":"evt-3","reason":"track call has no event"}]}
Read that field. It is the difference between "analytics are working" and "analytics are working for two thirds of your events". Log it in whatever wraps your SDK.
| 200 | Accepted. Check dropped. |
|---|---|
| 400 | The body was unusable — not JSON, empty batch, over the cap. Retrying will not help. |
| 401 | Wrong write key. |
| 403 | The Signal is paused. Talk to us. |
| 404 | Wrong signal id in the URL. |
| 413 | Body too large. Send smaller batches. |
| 415 | Compressed body — see below. |
| 429 / 503 | Back off and retry; Retry-After is set. |
| 500 | Ours. Retry — duplicate delivery is safe. |
Things that will bite you
415 rather than silently mangling them.
RudderStack's .NET SDK gzips by default from v2.0.0 — pass
gzip: false. Segment's Node SDK never compresses;
analytics-python only does if you ask. If you hit this, the 415
names the cause and the fix.
415 happens before we read the body, so there is no delivery to show you
and the integration simply looks quiet. If a new integration shows nothing at all,
check your sender's own logs first.
Send a messageId. Every stock SDK does. It is how a retried
delivery is recognised as the same event instead of becoming a duplicate. Without one
we derive a stable hash of the payload, which works but is weaker.
Retries are safe. Redelivering an identical batch overwrites rather than
duplicates, so retry on 5xx freely. Do not retry 400 or
401 — nothing about them improves.
Browser events expose the write key. That is inherent to client-side analytics and true of Segment too, but the key is append-only ingest for one Signal. If you would rather not ship it at all, post events to your own backend route and forward them server-side.
What you get
Events land as rows you can query, keyed so that a retry cannot duplicate them. From there, sessionization, identity resolution, funnels, retention and DAU/WAU are SQL views over those rows — Elk materializes them when someone asks a question, rather than making you build them in a dashboard tool first.
You do not need to model any of that up front. Send the events; the questions come later.