Event governance
Event governance ensures events flowing through Active Reach are high-quality, within your plan’s limits, and compliant with your consent settings.
The five layers
Every incoming event passes through five checks:
1. Schema validation
The gateway validates incoming events against your workspace’s event schema:
- Required fields present (type, at least one identity)
- Property types match expectations (if you’ve defined a schema for the event)
- Payload size within limits
- No circular references or excessively nested objects
Events that fail validation are rejected with a 400 response and a descriptive error.
2. Rate limiting
Hierarchical rate limits protect the platform and your workspace:
| Level | What it limits |
|---|---|
| Workspace | Total events/second across all sources |
| Per-source | Events/second per SDK instance or API key |
| Per-contact | Events/second per individual contact (prevents runaway loops) |
When you hit a rate limit, the gateway returns 429 Too Many Requests with a Retry-After header.
Limits depend on your plan tier. View your current limits in Settings → Developer → Throughput.
3. Unique event-name cap
Each plan tier has a maximum number of distinct custom event names the workspace can register. This is about schema cardinality, not event volume — you can fire order_placed a million times, but registering order_placed_1, order_placed_2, …, order_placed_N all count against the same cap.
| Plan | Unique event-name cap |
|---|---|
| Trial | 50 |
| Growth | 100 |
| Scale | 200 |
| Enterprise | Unlimited |
Standard events (page_view, identify, session_start, etc.) are always allowed and do not count against the cap. Only custom event names consume a slot.
When you hit the cap:
- First breach — a 7-day grace window opens automatically (see § Grace period below)
- After grace expires — novel event names are dropped at the gateway; the event returns
verdict: over_capin the batch response - Registered names keep working —
order_placedcontinues to flow even if you’ve hit your cap via other names
SDK-side self-throttling
The Web, iOS, Android, and React Native SDKs all ship with a client-side Bloom filter that lets them drop novel event names on the device before a network call. This collapses the amplification that would otherwise happen when a bug like track('view_' + productId) creates a new name per visit.
The Bloom filter is delivered on /v1/sdk/bootstrap as part of the response:
{
"propertyId": "prop_xyz",
"organizationId": "org_abc",
"eventGovernance": {
"bloom_algo": "mmh3_x86_32_km",
"seed_a": 0,
"seed_b": 1,
"k": 7,
"m": 1024,
"bloom_b64": "AAAE…",
"remaining_new_names": 23,
"grace_active": false,
"ttl_seconds": 300
}
}Pass the eventGovernance object to aegis.ingestGovernanceHint(hint) after bootstrap(). The SDK then:
- Checks known names against the Bloom filter —
has(name) === truemeans “already registered, send it” - Tracks a local counter of novel names registered in this session so repeating the same name doesn’t double-charge
- Once
remaining_new_namesreaches 0 andgrace_active === false, drops novel names locally and logs a singleconsole.warn/Log.w/printper distinct name - Emits a coalesced
aegis.client.name_governor_droppedmeta event at the next batch flush so ops dashboards see novel-name amplification patterns
The hash is MurmurHash3 x86-32 with Kirsch-Mitzenmacher double-hashing — byte-identical across Python, TypeScript, Swift, and Kotlin. A pinned test fixture at libs/shared-types/bloom-test-vectors.json runs in CI on all four languages to prevent silent drift.
See the per-SDK docs for integration:
- Web SDK — Event governance
- iOS SDK — Event governance
- Android SDK — Event governance
- React Native SDK — Event governance
TraitGovernor — client-side trait-write guards
The same SDKs ship a TraitGovernor that runs the platform’s ingestion guards client-side on every identify() / group() / aegis.user.setX(...) write. Whatever the cell-plane backend rejects at record_attribute_keys, the SDK warns about before the payload leaves the device — operators see the problem in dev tools the first time it happens instead of discovering it in the Validation tab after a production batch.
Five verdicts can fire (codes match the backend ingestion_dlq.verdict enum + the frontend IngestionDLQVerdict union — drift-locked across four surfaces by tests/drift/trait-governor-verdicts.json):
| Verdict | What it catches | What the SDK does |
|---|---|---|
bad_key_format | Key reduces to empty after normalization ("", "___", only whitespace) | Drops the trait, warns once |
reserved_prefix | Key starts with a reserved namespace (system., user., loyalty., cart., bill., $, _, …) | Drops the trait — these prefixes belong to the platform |
value_too_long | String value exceeds the soft cap | Truncates and warns; if it exceeds the hard cap, drops outright (DoS protection) |
bad_date_format | Date-keyed field (*_at, *_on, created, expires, dob, …) holds a string that doesn’t parse as ISO-8601 or epoch | Drops the trait, warns once |
name_too_long | Reserved for the parallel event-name guard | (NameGovernor path — listed here for symmetry) |
In addition to the verdict-driven drops, the governor normalizes:
- camelCase / PascalCase / dashed / space-separated keys → snake_case —
firstNamebecomesfirst_nameautomatically before the trait crosses the network - ISO-8601 / epoch strings on date-keyed fields → epoch milliseconds — so segmenter time arithmetic doesn’t have to re-parse strings
Warnings are rate-limited to the first 3 per (workspace_id, verdict) per session. After that they go silent — the developer got the message and further warnings would just spam the console. The drops still flow through silently; backend ingestion guards remain the authoritative gate.
This client-side path is purely advisory — there are no thresholds the SDK enforces harder than the backend does. The cell-plane is still the chokepoint that decides what lands on the contact graph; the TraitGovernor exists so problems surface earlier in the development loop.
4. Contact-level messaging caps
Active Reach limits the number of outbound messages a contact receives per day. Cap tiers depend on your plan — higher plans allow more messages per contact per day.
This applies to campaigns, journeys, and push/email/WhatsApp/SMS sends — not to incoming events or delivery receipts.
5. Consent enforcement
If your workspace has consent mode enabled (enable_consent_mode: true in the SDK config):
- Events are classified by consent category:
analytics,marketing,functional - Events in a denied category are queued but not processed until consent is granted
- If
wait_for_consent: true, no events are sent at all until the user grants consent - Consent preferences are stored per-contact and respected across all channels
Integration with OneTrust, Cookiebot, and Google Consent Mode v2 is built into the Web SDK.
Grace period (7-day soft cap)
The first time your workspace crosses a unique-event-name cap, Active Reach automatically opens a 7-day grace window:
- Events with novel names continue to flow to the gateway (and downstream analytics / journeys / campaigns)
cap_hit_eventsare still recorded for audit- A daily email reminder goes to the workspace admin with a link to review event names
- The dashboard shows an amber “Approaching event name limit” banner
- On day 8, the window closes and the hard cap kicks in — novel names are dropped
The grace window is a one-time gift per cap. If you upgrade your plan during the window, the window is cleared. If you hit the new plan’s cap later, the grace doesn’t reopen — it exists only for the first-ever breach of any cap.
Why 7 days
Seven days is long enough to:
- Review the dashboard and identify the offending integration
- Ship a fix that replaces dynamically-generated names with a static name + property (e.g.
track('product_viewed', { product_id: id })instead oftrack('view_' + id)) - Decide whether to upgrade
…and short enough that schema sprawl doesn’t silently accumulate.
Cap-hit notification surfaces
When your workspace breaches a cap, Active Reach fans out notifications to three surfaces (debounced to once per org, per cap type, per 24 hours so a thousand hits in an hour don’t become a thousand pings):
- Activity feed card — a SYSTEM card appears in your timeline at
/dashboard/[workspace]/timelinewithcard_type: 'system'andevent_id: 'governance.cap_exceeded' - In-app admin notification — a notification row for admin users (renders as a toast + in the bell/inbox)
- Email to workspace admins — using the
tenant_quota_cap_exceededtemplate, with a direct link to the event-review page and an upgrade CTA - Platform-side Slack + email ping to Active Reach ops — so your CSM knows to reach out
The dashboard also shows a persistent red/amber banner via the EventCapThresholdNotificationCard component until the cap state resets (plan upgrade or admin override).
Event quality signals
The platform tracks event quality metrics visible in Settings → Events:
- Validation pass rate — % of events that pass schema validation
- Duplicate rate — % of events that are exact duplicates (deduplicated)
- Ghost event rate — % of events from non-human sources (bots, crawlers)
- Unique event-name usage — current count vs plan cap
- Cap-hit count (last 24h / 7d) — how often novel names are being dropped
These metrics help you identify SDK misconfigurations or integration issues before they impact your reporting.
Billing
- MAU overage is auto-charged on your next billing cycle via Stripe (INR tenants: Razorpay). The overage rate is visible in Settings → Billing → Plan. Matches the MoEngage / Braze model.
- Unique event-name cap is a hard cap with no overage pricing — upgrade your plan to raise it. Matches every major competitor (CleverTap, WebEngage, MoEngage, Netcore).
- Event volume (total events/month) is not billed per-overage; the
aegis.events.quota_exceededsignal is surfaced to your CSM for an upgrade conversation rather than an auto-charge.
What’s next
- Web SDK reference —
aegis.ingestGovernanceHint()on the web - Event ingestion — the batch API endpoint
- MAU classification — how events affect billing
- Architecture overview — where governance fits in the system