TrueEngageDocsStart free

Send server events

Record events against known customers from your backend with POST /events.

Updated 18 Oct 2026 3 min read

POST /events records events from your backend. Use it for things your app cannot see, such as a payment settling or an order shipping. Events trigger campaigns and flows exactly as SDK events do.

POST https://api.true-engage.com/v1/events

Authentication

A server key or Data API key with scope data.create_events. SDK app keys are refused. See Authentication.

Request body

Field Type Required Notes
events array Yes 1 to 500 events.

Each event:

Field Type Required Notes
externalId string One of the three Your id for the person. Up to 256 characters.
email string One of the three Used only when externalId is absent. Up to 320 characters.
phone string One of the three E.164, such as +919876543210. Used only when externalId and email are absent.
name string Yes 1 to 100 characters. New names are registered the first time you send them. Reserved SDK names are rejected.
properties object No Up to 50 keys. Types are learned from the first value, so send real numbers and booleans.
timestamp string (ISO 8601) No When it happened. Defaults to now.
messageId string No Up to 64 characters. A retry with the same id within 24 hours is collapsed. Without it, a retry is a second event.

An unknown identifier creates a new profile.

Property limits: key names up to 100 characters, string values up to 1,000 characters, arrays up to 100 elements, and up to 32 KB per event. Timestamps more than 48 hours ahead or 30 days behind are rejected.

Example

curl -X POST https://api.true-engage.com/v1/events \
  -u "test_xxxxxxxx:YOUR_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "externalId": "C10093",
        "name": "Order Shipped",
        "properties": { "orderId": "ORD-8891", "amount": 2499.5, "carrier": "BlueDart" },
        "timestamp": "2026-10-18T09:30:00Z",
        "messageId": "ship-ORD-8891"
      }
    ]
  }'
const auth = Buffer.from(`${process.env.TE_APP_KEY}:${process.env.TE_SECRET}`).toString('base64');

const res = await fetch('https://api.true-engage.com/v1/events', {
  method: 'POST',
  headers: { Authorization: `Basic ${auth}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    events: [{
      externalId: 'C10093',
      name: 'Order Shipped',
      properties: { orderId: 'ORD-8891', amount: 2499.5, carrier: 'BlueDart' },
      messageId: 'ship-ORD-8891',
    }],
  }),
});
const { data } = await res.json();
if (data.rejected > 0) console.warn(data.errors);

Response

Status 202 Accepted. The events are queued, not yet processed.

{
  "requestId": "0b1c6f2e-6a1d-4c58-9f47-2b7d6d1f8a10",
  "data": { "accepted": 1, "rejected": 0, "batchId": "01J9Z3K8M2Q7X4N5P6R7S8T9V0" }
}

Partial errors

A bad event does not fail the batch. The rest land, and each rejected event is listed with its position in your array. Do not retry rejected events unchanged.

{
  "requestId": "0b1c6f2e-6a1d-4c58-9f47-2b7d6d1f8a10",
  "data": {
    "accepted": 1,
    "rejected": 1,
    "batchId": "01J9Z3K8M2Q7X4N5P6R7S8T9V0",
    "errors": [
      { "index": 1, "code": "IDENTIFIER_REQUIRED", "message": "Give one of externalId, email or phone." }
    ]
  }
}

Per-event codes include IDENTIFIER_REQUIRED, EVENT_NAME_REQUIRED, EVENT_RESERVED, EVENT_BLOCKED and EVENT_TOO_LARGE. See Errors.

Request-level errors

Status Code Cause
400 or 422 VALIDATION_ERROR or VALIDATION_FAILED Malformed body, empty events, more than 500 events, or a field over its length limit.
401 or 403 INVALID_API_KEY, KEY_KIND_NOT_ALLOWED, INSUFFICIENT_SCOPE Credential problem.
429 RATE_LIMITED Over 6,000 requests per minute per key, or 1,200 per IP.

Tips

  • Always send messageId, for example your order id plus the event name, so retries are safe.
  • Send up to 500 events per request instead of one request per event.
  • Use the same name spelling everywhere. Order Placed and order placed are different events.