Segment-Compatible Endpoint

Anything that emits the Segment HTTP API wire format — Segment itself, RudderStack in HTTP mode, Jitsu, or an analytics.js clone — can point its writeKey and host at this endpoint. Events already arrive named and shaped, so they go straight into the analysis layer with zero annotation-rule authoring.

5-minute quickstart

Get a publishable key at drengr.dev/pro (free), then send a single track call. Basic auth uses the writeKey as the username with an empty password — exactly how a real Segment client authenticates:

bash
curl -X POST function(){throw Error("Attempted to call FN_BASE() from the server but FN_BASE is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")}/segment-ingest/v1/track \
  -u "drengr_pk_YOUR_KEY:" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "track",
    "event": "Order Completed",
    "userId": "user_123",
    "properties": {
      "revenue": 42.50,
      "currency": "USD"
    },
    "context": {
      "app": { "name": "com.example.myapp", "version": "3.2.1" },
      "os": { "name": "iOS", "version": "17.4" }
    }
  }'

The writeKey can also be sent as a writeKey field in the JSON body instead of Basic auth — both are accepted, matching real Segment client behavior.

Endpoint

track
function(){throw Error("Attempted to call FN_BASE() from the server but FN_BASE is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")}/segment-ingest/v1/track
identify
function(){throw Error("Attempted to call FN_BASE() from the server but FN_BASE is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")}/segment-ingest/v1/identify
batch
function(){throw Error("Attempted to call FN_BASE() from the server but FN_BASE is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")}/segment-ingest/v1/batch

The event type is read off the last path segment, so /v1/track and a bare /track both work. identify, page, screen, group, and alias are supported the same way. Posting to the bare endpoint with no recognizable path segment falls back to reading type (single event) or batch (array) from the body.

Auth

HTTP Basic writeKey: (base64, empty password) OR a writeKey field in the JSON body — both work. The writeKey is your drengr_pk_… publishable key from /pro. Any auth failure (unknown key, revoked key, wrong key kind) returns the same uniform 401 — never a differentiated error.

RudderStack & Jitsu

RudderStack (HTTP mode)

RudderStack SDKs speak the same Segment HTTP API wire format. Point the SDK's data-plane URL at function(){throw Error("Attempted to call FN_BASE() from the server but FN_BASE is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")}/segment-ingestand its write key at your Drengr publishable key — check your specific RudderStack SDK's config for the exact field name (typically dataPlaneUrl + writeKey in the client init call).

Jitsu

Jitsu's Segment-compatible receiver forwards the same wire format. Point its destination host at this endpoint with your Drengr publishable key as the write key.

Event mapping

What lands where in semantic_events:

Source fieldsemantic_events fieldNotes
event (track)event_nameLowercased, non-alphanumeric runs collapsed to "_", capped at 64 chars. Original preserved in dims._source_event_name if it changed.
properties / traitsdims + measuresFlattened one level (a nested object becomes parent.child; deeper values are stringified). Numeric values → measures (≤20 keys, revenue/total/price/quantity prioritized); everything else → dims (≤40 keys, 64-char keys, 256-char values).
userIdexternal_id
anonymousIdinstall_idFalls back to userId if anonymousId is absent.
userId + anonymousId both missing(event dropped)Fail-open: the event is skipped, the rest of the batch still succeeds.
context.app.versiondims.app_version
context.os.name + .versiondims.osCombined as "name version".
context.device.modeldims.device_model
type = page / screenevent_name = page_viewed / screen_viewedname → dims.name
type = identify / group / aliasevent_name = identify / group / alias
timestampoccurred_atPrefers item.timestamp; else corrects for clock skew via sentAt/originalTimestamp; else receive time. Clamped to a sane window (year 2000 → now+1 day).
messageIdevent_id (dedup key)Falls back to a hash of userId/anonymousId + event name + timestamp when absent.
"Order Completed" etc. + revenue/total/price/quantity/tax/shipping/discountmeasures (forced numeric)Ecommerce-reserved event names get these fields coerced to numbers even if sent as strings ("9.99").
email/phone/name/address/password/token/ssn/card/cvv-like keys(dropped)Substring-matched on the key name, case-insensitive, before dims or measures are ever built.

Limits

  • 32KB per message, 500KB per request
  • 2,500 events per batch
  • 5,000,000 events/day per tenant (soft cap — metering fails open, so a metering hiccup never blocks ingestion)
  • Oversized requests/messages return 400, never 5xx — Segment and RudderStack clients retry hard on 5xx

Response

Segment's own success contract — always 200 { success: true }, even when every event in the batch was dropped (missing identifiers, unrecognized type). Errors:

json
400  { "error": "Request too large" }        // >500KB
400  { "error": "Message too large" }        // one item >32KB
400  { "error": "Batch too large" }          // >2500 events
400  { "error": "Invalid JSON" }
401  { "error": "Unauthorized" }             // uniform for any auth failure
502  { "error": "Ingest failed" }            // upstream write failed

Troubleshooting

Check that every event has a userId or anonymousId. Events missing both are silently dropped — the request still returns 200 (fail-open), it just contains zero storable rows.