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:
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
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 field | semantic_events field | Notes |
|---|---|---|
| event (track) | event_name | Lowercased, non-alphanumeric runs collapsed to "_", capped at 64 chars. Original preserved in dims._source_event_name if it changed. |
| properties / traits | dims + measures | Flattened 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). |
| userId | external_id | |
| anonymousId | install_id | Falls 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.version | dims.app_version | |
| context.os.name + .version | dims.os | Combined as "name version". |
| context.device.model | dims.device_model | |
| type = page / screen | event_name = page_viewed / screen_viewed | name → dims.name |
| type = identify / group / alias | event_name = identify / group / alias | |
| timestamp | occurred_at | Prefers item.timestamp; else corrects for clock skew via sentAt/originalTimestamp; else receive time. Clamped to a sane window (year 2000 → now+1 day). |
| messageId | event_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/discount | measures (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:
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 failedTroubleshooting
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.