Skip to Content
DevelopersEventsEvent ingestion

Event ingestion

The gateway exposes several SDK-authenticated paths, all write-key authed via the X-Aegis-Write-Key header (no JWT). The three core ingestion streams:

EndpointCarriesPayload type
POST /v1/batchSDK event stream — track / identify / page / screen / group / aliasSDKBatchPayload
POST /v1/in_app/eventsIn-app campaign click + dismiss telemetry (impression / clicked / dismissed)InAppEventPayload
POST /v1/push/engagementPush notification lifecycle from mobile + web push SDKs (delivered / shown / clicked / dismissed / permission_denied / subscribed / resubscribed)PushEngagementEventPayload

Plus the device-token + in-app campaign control paths the mobile SDKs use — device registration (/v1/devices/register, /v1/devices/deactivate) and in-app fetch + feedback (GET /v1/in-app/active, /v1/in-app/widgets/{step}/ack, /api/v1/in_app/responses) — documented below.

All terminate at the active-reach-gateway and dispatch into the tenant’s cell. Use /v1/batch for application events; the other endpoints have narrowly-scoped payload contracts and exist so the high-volume in-app + push telemetry streams can be rate-limited and routed independently of the main event stream.

Geofence enter/exit are not a separate endpoint — the mobile SDKs emit geofence.entered / geofence.exited as ordinary track events through /v1/batch. See the iOS, Android, React Native, and Flutter geofencing guides.

/v1/batch — SDK event stream

POST https://ingest.active-reach.ai/v1/batch X-Aegis-Write-Key: <sdk_write_key> Content-Type: application/json

Request body:

{ "batch": [ { "type": "track", "event": "order_completed", "messageId": "msg_01HXXXXX", "timestamp": "2026-05-27T10:30:00.000Z", "anonymousId": "anon_789", "userId": "user_456", "sessionId": "sess_abc", "properties": { "order_id": "ORD-123", "amount": 4999, "currency": "INR" } } ], "sentAt": "2026-05-27T10:30:01.000Z", "writeKey": "sdk_write_YOUR_KEY" }

The writeKey field in the body is the authoritative auth signal; the Web SDK also sets Authorization: Bearer <writeKey> for backward compatibility. The gateway rejects with 401 on a missing or invalid write key.

Top-level fields

FieldRequiredDescription
batchYesArray of one or more event records (see below)
sentAtYesISO 8601 timestamp — when the SDK flushed this batch
writeKeyYesThe workspace SDK write key
contextNoCell metadata stamped by the SDK transport (region, endpoint)

Per-event fields

FieldTypeRequiredDescription
typestringYesOne of: track, identify, page, screen, group, alias
eventstringConditionalEvent name. Required for track.
namestringConditionalPage/screen name for page / screen.
messageIdstringYesSDK-generated unique id for dedup
timestampISO 8601YesWhen the event occurred (SDK clock)
anonymousIdstringYesSDK-generated anonymous id
userIdstringNoYour application user id (when identified)
sessionIdstringYesSDK-managed session id
propertiesobjectNoEvent-specific payload
traitsobjectNoIdentity traits (for identify / group)
contextobjectNoPage / device / campaign metadata

/v1/in_app/events — in-app campaign telemetry

POST https://ingest.active-reach.ai/v1/in_app/events X-Aegis-Write-Key: <sdk_write_key> X-Organization-ID: <org_id> Content-Type: application/json
{ "campaign_id": "iac_abc123", "event_type": "clicked", "user_id": "user_456", "anonymous_id": "anon_789", "platform": "web", "workspace_id": "ws_abc123", "idempotency_key": "iac_abc123:user_456:clicked" }

event_type is one of impression / clicked / dismissed. This endpoint is narrowly scoped to in-app campaign feedback — application events go to /v1/batch.

/v1/push/engagement — push notification lifecycle

POST https://ingest.active-reach.ai/v1/push/engagement X-Aegis-Write-Key: <sdk_write_key> X-Organization-ID: <org_id> X-Property-Id: <property_id> Content-Type: application/json
{ "event_type": "push.clicked", "platform": "ios", "campaign_id": "cmp_42", "message_id": "msg_a1b2", "property_id": "prop_x", "contact_id": "ct_z9", "anonymous_id": "an_q4", "metadata": { "action_url": "myapp://product/sku-42", "action_id": "primary" } }

Every Active Reach SDK (Web, Android, iOS, React Native, Flutter) posts to this endpoint when a push notification is delivered, shown, clicked, or dismissed by the operating system. The gateway forwards to the event switchboard, which dual-routes onto the workspace’s delivery_events ClickHouse table as channel='push' and into timeline_cards for the activity feed.

Allowed event_type values

ValueFires whenDelivery status mapping
push.deliveredOS confirmed the notification reached the deviceshown
push.shownBanner rendered (web push)shown
push.clickedUser tapped the notificationclicked
push.dismissedUser swiped the notification awaydismissed
push.permission_deniedUser declined the push permission prompt(no delivery row)
push.subscribedNew push subscription minted(no delivery row)
push.resubscribedExisting subscription refreshed (token rotation)(no delivery row)

The gateway tolerates bare delivered / clicked / dismissed event types and normalises them to the push.<event> prefix before dispatch — legacy SDK versions can interop while you upgrade.

Required fields

FieldTypeRequiredDescription
event_typestringYesOne of the values above
platformstringYesweb / ios / android
property_idstringYesThe Active Reach property the SDK install belongs to
campaign_idstringNoEmpty string when the SDK cannot resolve (e.g. a transactional push)
message_idstringNoSame — empty string when not resolvable
user_idstringNoFalls back to contact_id when absent
contact_idstringNoActive Reach contact id (when identified)
anonymous_idstringNoSDK-generated anonymous id
metadataobjectNoArbitrary key/value — action_url, action_id, button_id, etc.

The endpoint is best-effort. Push service workers (web), Notification Service Extensions (iOS), and FCM background callbacks (Android) never propagate a failed engagement POST — the OS has already displayed the notification by the time the SDK tries to report.

Push engagement vs delivery webhooks

The /v1/push/engagement endpoint is inbound ingestion from your SDK. Push engagement also flows outbound to your webhook endpoint as standard delivery.delivered / delivery.opened / delivery.clicked events — see Webhook event types. You do not need to consume both: the ingestion endpoint is for the SDK, the webhook is for your downstream systems.

Device registration

Mobile SDKs register their APNs / FCM device token so the platform can target the install with push. Two endpoints manage the token lifecycle.

POST /v1/devices/register

POST https://ingest.active-reach.ai/v1/devices/register X-Aegis-Write-Key: <sdk_write_key> X-Organization-ID: <org_id> Content-Type: application/json
{ "device_token": "<apns_or_fcm_token>", "platform": "ios", "property_id": "prop_x", "contact_id": "ct_z9", "anonymous_id": "an_q4", "app_version": "1.4.0", "user_agent": "ActiveReach-iOS-SDK/1.6.0 iOS 17.4 (iPhone)" }

Called by the SDK after the OS grants push permission and mints a token, and again on every token rotation. Re-registering the same token is idempotent — the platform upserts on (property_id, device_token).

FieldTypeRequiredDescription
device_tokenstringYesRaw APNs hex token or FCM registration token
platformstringYesios / android
property_idstringNoThe Active Reach property the install belongs to
contact_idstringNoActive Reach contact id (when identified)
anonymous_idstringNoSDK-generated anonymous id
app_versionstringNoHost app version, for cohorting
user_agentstringNoSDK + OS descriptor

POST /v1/devices/deactivate

POST https://ingest.active-reach.ai/v1/devices/deactivate X-Aegis-Write-Key: <sdk_write_key> Content-Type: application/json
{ "device_token": "<apns_or_fcm_token>", "platform": "ios", "property_id": "prop_x", "reason": "apns_inactive" }

Called when the push service reports a token as inactive (APNs feedback on iOS, an UNREGISTERED / SenderId mismatch from FCM on Android). The platform marks the token inactive with a soft-quarantine window so a device that comes back online can re-register without losing history. reason is a free-form diagnostic string (e.g. apns_inactive, fcm_unregistered).

In-app campaign fetch + feedback

In-app messaging uses one canonical fetch endpoint plus two feedback paths. The fetch returns the campaigns eligible for the current install (catalog campaigns + journey-queued widgets, consent- and frequency-gated). Feedback splits by campaign origin — see the table below.

GET /v1/in-app/active

GET https://ingest.active-reach.ai/v1/in-app/active?current_surface=<surface> X-Aegis-Write-Key: <sdk_write_key> X-Organization-ID: <org_id> X-Contact-ID: <contact_id> X-User-ID: <user_id>

Returns a JSON array of eligible in-app campaigns. current_surface is optional — when present (one of the canonical placement surfaces, e.g. bill, storefront_catalog), the platform drops campaigns scoped to other surfaces; when absent, no surface filter is applied. This is the single fetch endpoint for every SDK (web + the four mobile SDKs).

POST /v1/in-app/widgets/{step_execution_id}/ack

Acknowledges a journey-queued in-app widget (one that carries a journey_step_execution_id). Used for served / viewed / clicked / dismissed on journey-driven in-apps so the journey can advance.

{ "action": "clicked", "step_id": "node_3", "variant_id": "v_b" }

action is required (served / viewed / clicked / dismissed); step_id and variant_id are optional (multi-step renderers + A/B attribution).

POST /api/v1/in_app/responses

Submits a structured response from an interactive in-app widget (NPS, poll, quiz, rating, multi-step form, countdown).

{ "campaign_id": "iac_abc123", "response_type": "nps", "platform": "ios", "payload": { "score": 9 }, "user_id": "user_456", "contact_id": "ct_z9", "variant_id": "v_b" }
FieldTypeRequiredDescription
campaign_idstringYesThe in-app campaign id
response_typestringYesWidget type — nps / poll / quiz / rating / form / countdown
platformstringYesweb / ios / android
payloadobjectYesResponse data (shape depends on response_type)
user_idstringNoApplication user id
contact_idstringNoActive Reach contact id
variant_idstringNoA/B variant id, when assigned

Which feedback path?

Campaign originCarries journey_step_execution_id?Click / dismiss pathStructured response path
Catalog (operator-authored, served by /v1/in-app/active)NoPOST /v1/in_app/eventsPOST /api/v1/in_app/responses
Journey-queued (a journey SEND step)YesPOST /v1/in-app/widgets/{step}/ackPOST /api/v1/in_app/responses

The SDK routes automatically based on whether the campaign payload carries a journey_step_execution_id — you don’t choose the path by hand.

Batch limits

LimitValue
Max events per batch (/v1/batch)100
Max payload size500 KB
Max property depth5 levels of nesting
Max property key length256 characters
Max property value size10 KB per value

Authentication

X-Aegis-Write-Key: sdk_write_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Write keys are scoped to a single workspace. Manage them under Settings → Developer → SDK Keys. Per-credential rate limits and an origin allow-list are configured alongside the key.

Response

StatusMeaning
200All events accepted
400Missing writeKey / empty batch / malformed JSON
401Invalid or missing write key
429Rate limited — respect Retry-After and back off
502Cell unavailable for tenant

Rate limits

The gateway enforces per-tenant ingress limits plus optional per-write-key minute + hour caps. 429 responses include Retry-After and X-Aegis-Rate-Reset headers in seconds.

Server-side tracking tips

  • Always set timestamp — server receive time is inaccurate for batch imports
  • Use userId over anonymousId when you know the user — it enables identity resolution
  • Batch aggressively — 50-100 events per request is optimal
  • Retry on 5xx and 429 with exponential backoff

What’s next