Send server events
Record events against known customers from your backend with POST /events.
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 Placedandorder placedare different events.
Was this page helpful?