</> Developers

One response envelope, stable error codes, idempotency keys, keyset pagination, and a sandbox that shows exactly what would have been sent.

4native SDKs: Web, Android, iOS, Flutter
~12 KBWeb SDK, gzipped
37scopes in the API key tree
1response envelope for every endpoint
01 Quickstart

From install to first event in four steps

Pick your platform and step through. It plays on its own while you read, and stops the moment you take over.

Install · WebShell
npm i @trueengage/web
  • One package
  • No transitive analytics SDKs
02 End to end

From event to delivered message

One tracked event, followed through every hop. Scroll and the packet follows it.

  • Live key: sent through your vendor
  • Test key: captured, not sent
  1. Your app calls track

    Event names are title case and identical across platforms. Properties are typed, and dates are sent as datetimes.

    track('Order Placed', { … })
  2. It lands in the device queue

    Offline-tolerant, so a dead network loses nothing. Calls made before initialize are queued too.

    queued · IndexedDB on web
  3. Batched and gzipped

    Flushed on a timer, at 50 items, when the app goes to the background, and immediately for identify. Retried with backoff.

    POST /sdk/batch · gzip
  4. Ingest accepts it

    Items stay ordered. The response carries a requestId you can quote to support.

    202 Accepted
  5. Journeys listening to it start

    Every journey listening to the event starts, with the attributes you sent. Each instance keeps a step trail.

    run inspector · step trail
  6. The send gate decides

    Consent, frequency cap, quiet hours, render. A skipped send gets a reason code. Transactional sends bypass caps and DND on purpose, and are audited.

    skipped · reason code
  7. Sent in Live, captured in Test

    A Live key sends through your vendor. A Test key captures the exact vendor request, with secrets redacted, and sends nothing.

    Test · captured, not sent
03 One envelope

Every response has the same shape

Write one unwrapping routine. Send a call, then hover a field to see what it promises.

GET any list endpoint200 OK
{
  "requestId": "01J8Z6QK3N7V2M4R8W1T",
  "data": {
    "data": [ { "id": "seg_01J8…", "name": "Dormant 30d" }, … ],
    "nextCursor": "eyJpZCI6IjAxSjhaNlFL…"
  }
}
Response received
  • requestIdOn every response, success or failure. Correlates the call with server logs; quote it to support.
  • dataThe payload. List endpoints return a page and an opaque nextCursor: keyset pagination that stays stable while rows are written.
  • messageHuman readable. On a validation failure it is the list of violations.
  • codeStable and machine readable, absent on success. Branch on the code, never on the message.
TRANSACTIONAL_VARIABLES_MISSINGRATE_LIMITEDINSUFFICIENT_SCOPEAPI_KEY_REVOKEDIP_NOT_ALLOWEDENVIRONMENT_FORBIDDENVALIDATION_FAILEDTRANSACTIONAL_ENDPOINT_NOT_FOUNDORIGIN_NOT_ALLOWEDCONFLICT
04 Keys and retries

Safe to retry, scoped by design

Two guarantees you can test from this page.

Idempotency keys

Send the same key twice and get the first result back. Retries from queues and webhooks are harmless.

"idempotencyKey": "otp-u-1001-1725790000"
  1. 1First try: the response is lost to a timeout, but the send already happened.sent
  2. 2Same key: the first result comes back. Nothing is sent again.first result
  3. 3Same key: the first result comes back. Nothing is sent again.first result
3 attempts · 1 message sent

Scoped API keys

Server, Data API and SDK keys with a 37-scope tree. A key can never exceed the person who minted it.

  • Server
  • Data API
  • SDK

24 granted · 13 above the minter's ceiling, never granted

05 Server recipes

Your backend talks, journeys listen

Server keys use Basic auth, the app key as user and the secret as password. Read the API Guide

Transactional sendcURL
curl -X POST https://api.true-engage.com/v1/transactional/otp-login/send \  -u 'live_9f2a…:YOUR_SECRET' \  -H 'Content-Type: application/json' \  -d '{    "userId": "u-1001",    "variables": { "otp": "483920", "expiryMinutes": 10 },    "idempotencyKey": "otp-u-1001-1725790000",    "deviceUid": "d-android-77"  }'
  1. 1 Idempotency key checked
  2. 2 Caps and DND bypassed, audited
  3. 3 Routed to the asking device
06 SDKs

Same events, same queue, every platform

Android, iOS, Web and Flutter at 0.3.0. Names are title case and identical across platforms. Batched and offline-tolerant, with opt-in device location and per-scope consent.

Read the Developer Guide
track, on every platform
WebTrueEngage.track('Order Placed', { amount: 2499.5 })
AndroidTrueEngage.track("Order Placed", mapOf("amount" to 2499.50))
iOSTrueEngage.track("Order Placed", properties: ["amount": 2499.50])
FlutterTrueEngage.track('Order Placed', {'amount': 2499.50});
  • A timerWhatever is queued goes out on a schedule.
  • At 50 itemsA full batch does not wait for the timer.
  • On backgroundWhen the app leaves the screen, the queue empties.
  • identify, at onceIdentity changes are never held back.

Web

npm i @trueengage/web
  • TypeScript, ES2018, ~12 KB gzipped
  • Offline queue in IndexedDB
  • Web Push over VAPID
  • No cookies; calls before initialize are queued

Android

implementation("io.trueengage:trueengage-android:0.3.0")
  • Kotlin, Java-interoperable, minSdk 23
  • Durable queue, WorkManager flush
  • Sessions via ProcessLifecycleOwner
  • Remote config with ETag caching

iOS

pod 'TrueEngage', '~> 0.3'
  • Swift, iOS 14+
  • APNs push
  • Push Received via a service extension
  • Distributed through CocoaPods

Flutter

trueengage_flutter: ^0.3.0
  • Dart layer bridged to the native cores
  • Identical queueing to Android and iOS
  • Flutter 3.10+, Dart 3.5+
  • Android 7.0+, iOS 14+
07 Sandbox

See the request, not the bill

In a Test environment nothing leaves the building. Every send captures the exact vendor request that would have been made, with secrets redacted, so you can check templates, headers and DLT ids before you promote to Live.

  • Allowlisted recipients only
  • Secrets redacted in every capture
  • Test keys can never write Live data
Captured request · TestJSON
// Test environment · captured, not sent
{
  "provider": "msg91",
  "request": {
    "method": "POST",
    "url": "https://control.msg91.com/api/v5/flow",
    "headers": { "authkey": "[redacted]", "Content-Type": "application/json" },
    "body": {
      "template_id": "1207172501234567890",
      "sender": "TRUENG",
      "recipients": [{ "mobiles": "919876543210", "var1": "483920" }]
    }
  }
}
08 Under the hood

Built for the second integration too

The parts you reach for after the first event is flowing.

Custom vendors

Declare a request template and response mapping, or ship a sandboxed transform script with a pinned runtime.

Imports with a dry run

Bring users and events in from CSV. A dry run predicts the result, and every failed row is listed with its reason.

Run inspector

Every flow instance has a step trail. Every skipped send has a reason code from a fixed vocabulary.

Your first event is one call away.

Sign up, create a workspace, and the SDK key is on screen. Test is sandboxed until you say otherwise.