TrueEngageDocsStart free

SDK ingest

Send ordered events, identities, attributes and devices from a custom or server-side client with POST /sdk/batch.

Updated 31 Oct 2026 6 min read

The official SDKs call these endpoints for you. Use them directly only if you are writing your own client, for example for a platform without an SDK. For servers, prefer POST /events.

These endpoints return their payload directly, without the usual envelope.

Authentication

Send the SDK app key in the X-TE-App-Key header. It needs no secret. The key decides the environment, so a test_ key can never write Live data. A browser sendBeacon call cannot set headers, so POST /sdk/batch also accepts ?appKey=test_xxxxxxxx.

Optional headers on /sdk/batch:

Header (or query) Meaning
X-TE-Batch-Id (batchId) An id for the whole batch, used to ignore a repeated batch. Use a UUID v7.
X-TE-Sdk (sdk) Your client name and version, such as mycustomclient/1.0.0.

POST /sdk/batch

POST https://api.true-engage.com/v1/sdk/batch

Scope sdk.track. The server processes items in array order, so an identify placed before an event applies to it.

Body

Field Type Required Notes
sentAt string (ISO 8601) Yes When your client flushed. Used to correct device clock skew.
context object Yes Who and what is sending. See below.
items array Yes Up to 500 items.

context

Field Notes
anonymousId Required. A UUID your client generates and keeps until reset or uninstall. Up to 128 characters.
externalId Set after identify.
deviceUid A UUID per install.
sessionId Absent for non-interactive events.
sdkVersion, sdkPlatform, appVersion, appBuild, bundleId App and SDK details.
osName, osVersion, deviceModel, deviceManufacturer Device details.
screenWidth, screenHeight, screenDensity Non-negative integers.
networkType wifi, cellular, none or unknown. The web SDK reports wifi for wired and Wi-Fi connections, because a browser cannot tell them apart. Only Chromium on Android reports a connection type. Safari, Firefox and most desktop browsers send unknown. none means the browser is offline.
carrier, locale, timezone Strings.
deviceCountry ISO 3166-1 alpha-2 code.
deviceLatitude, deviceLongitude, deviceAccuracyMetres, deviceCity, deviceRegion, deviceLocationFixAt, deviceLocationPermission Optional location, only after the user has granted permission.
isFirstSession Boolean.
utmSource, utmMedium, utmCampaign, utmTerm, utmContent, utmId Campaign attribution.
advertisingId Only with the user's opt-in.

Items

Every item has:

Field Required Notes
type Yes event, identify, attributes, device or alias.
messageId Yes A unique id per item, up to 64 characters (UUID v7 recommended). Resend the same id when you retry.
timestamp Yes ISO 8601, when it happened on the device.

Fields by type:

Type Fields
event name (required, up to 100 characters), properties (up to 50 keys), nonInteractive (boolean; does not start or extend a session)
identify externalId (up to 256 characters), attributes (object)
attributes set (object of changed keys), unset (array of keys)
device platform (android, ios or web), pushProvider (fcm, apns, web_push or hms), pushToken (up to 4,096 characters), apnsEnvironment (production or sandbox, iOS only), notificationsEnabled (boolean), webPushSubscription (object)
alias Accepted as a type.

Re-send the device item on every app launch. Token-change callbacks are unreliable, and a repeat is free.

Example

curl -X POST https://api.true-engage.com/v1/sdk/batch \
  -H "X-TE-App-Key: test_xxxxxxxx" \
  -H "X-TE-Batch-Id: 01J9Z3K8M2Q7X4N5P6R7S8T9V0" \
  -H "Content-Type: application/json" \
  -d '{
    "sentAt": "2026-10-18T09:30:05Z",
    "context": {
      "anonymousId": "7b1d6f0a-3c52-4c0e-9d2a-5f6e7a8b9c0d",
      "deviceUid": "c3e4f5a6-1b2c-4d3e-8f4a-5b6c7d8e9f01",
      "sdkPlatform": "custom",
      "sdkVersion": "1.0.0"
    },
    "items": [
      {
        "type": "identify",
        "messageId": "01J9Z3K8M2Q7X4N5P6R7S8T9V1",
        "timestamp": "2026-10-18T09:30:00Z",
        "externalId": "u-1001",
        "attributes": { "firstName": "Pritam" }
      },
      {
        "type": "event",
        "messageId": "01J9Z3K8M2Q7X4N5P6R7S8T9V2",
        "timestamp": "2026-10-18T09:30:02Z",
        "name": "Order Placed",
        "properties": { "orderId": "ORD-8891", "amount": 2499.5 }
      }
    ]
  }'
const res = await fetch('https://api.true-engage.com/v1/sdk/batch', {
  method: 'POST',
  headers: { 'X-TE-App-Key': 'test_xxxxxxxx', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    sentAt: new Date().toISOString(),
    context: { anonymousId, deviceUid, sdkPlatform: 'custom', sdkVersion: '1.0.0' },
    items,
  }),
});

if (res.status === 413) { /* split items in half and send both halves */ }
else if (res.status === 202 || res.status === 207) {
  const { accepted, rejected, errors } = await res.json();
}

Responses

Status Meaning What to do
202 Every item was accepted and queued. Remove the items from your queue.
207 Some items were rejected for good. errors lists each by messageId. Remove the whole batch. Never retry rejected items; they would fail again.
400 The envelope is malformed. Drop the batch. Retrying never succeeds.
413 The batch is too large. Split it in half and send both halves.
429 Rate limited. Wait for Retry-After, then retry.
5xx or network error Temporary. Keep the items and retry with backoff, with the same messageIds.
{
  "accepted": 1,
  "rejected": 1,
  "batchId": "01J9Z3K8M2Q7X4N5P6R7S8T9V0",
  "errors": [
    { "messageId": "01J9Z3K8M2Q7X4N5P6R7S8T9V2", "code": "EVENT_BLOCKED", "message": "That event is blocked for this workspace." }
  ]
}

Limits

Limit Value
Items per batch 500
Batch size 1 MiB (1,048,576 bytes)
Event name 100 characters
Properties per event 50
Property key 100 characters
String value 1,000 characters
Array elements 100
Event payload 32 KB
Event time Not more than 48 hours ahead, or 30 days behind
Rate 6,000/min per key, 300/min per IP

Accepted means queued. The write happens shortly after, so a profile can take a moment to show the new event.

GET /sdk/config

Remote configuration for your client.

curl https://api.true-engage.com/v1/sdk/config \
  -H "X-TE-App-Key: test_xxxxxxxx" \
  -H 'If-None-Match: "abc123"'

The response carries an ETag header and Cache-Control: max-age=3600. Send the last ETag in If-None-Match. If nothing changed you get 304 Not Modified with no body. Cache the last config to disk and use it on the next launch, even offline. Limit: 600/min per key, 60/min per IP.

Fields in the body:

Field Meaning
version Config version number.
reservedEvents, reservedAttributes Names your client must not send.
blockedEvents, blockedAttributes Names to drop before queueing. This is the kill switch for a bad event.
flush intervalSeconds, batchSize, maxQueueSize.
session timeoutMinutes.
features Feature flags.
sampling eventSampleRates, per event name.
endpoints Endpoint URLs for the client.

Other SDK endpoints

Endpoint Scope Purpose
POST /sdk/track, /sdk/identify, /sdk/devices sdk.track, sdk.identify, sdk.device Aliases that accept the same body as /sdk/batch. Use /sdk/batch to keep ordering.
GET /sdk/inbound sdk.inbound Fetch in-app, on-site and card messages. Query: types, screen, anonymousId, externalId. Supports ETag.
POST /sdk/inbound/stats sdk.inbound Report impressions, up to 500 per call.
POST /sdk/links/resolve, /sdk/links/deferred sdk.track Called by the SDKs to resolve a smart link the app was opened with, and to match a first launch to a link click. Use the SDK rather than calling these. See Deep links and deferred deep links.
POST /sdk/links/create sdk.track Creates a short link for in-app content. See Links API and webhooks.

Errors

See Errors for every code, and Authentication for key problems.