Skip to Content
DevelopersConceptsEvent governance

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:

LevelWhat it limits
WorkspaceTotal events/second across all sources
Per-sourceEvents/second per SDK instance or API key
Per-contactEvents/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.

PlanUnique event-name cap
Trial50
Growth100
Scale200
EnterpriseUnlimited

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_cap in the batch response
  • Registered names keep workingorder_placed continues 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:

  1. Checks known names against the Bloom filter — has(name) === true means “already registered, send it”
  2. Tracks a local counter of novel names registered in this session so repeating the same name doesn’t double-charge
  3. Once remaining_new_names reaches 0 and grace_active === false, drops novel names locally and logs a single console.warn / Log.w / print per distinct name
  4. Emits a coalesced aegis.client.name_governor_dropped meta 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:

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):

VerdictWhat it catchesWhat the SDK does
bad_key_formatKey reduces to empty after normalization ("", "___", only whitespace)Drops the trait, warns once
reserved_prefixKey starts with a reserved namespace (system., user., loyalty., cart., bill., $, _, …)Drops the trait — these prefixes belong to the platform
value_too_longString value exceeds the soft capTruncates and warns; if it exceeds the hard cap, drops outright (DoS protection)
bad_date_formatDate-keyed field (*_at, *_on, created, expires, dob, …) holds a string that doesn’t parse as ISO-8601 or epochDrops the trait, warns once
name_too_longReserved 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_casefirstName becomes first_name automatically 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.

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_events are 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 of track('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):

  1. Activity feed card — a SYSTEM card appears in your timeline at /dashboard/[workspace]/timeline with card_type: 'system' and event_id: 'governance.cap_exceeded'
  2. In-app admin notification — a notification row for admin users (renders as a toast + in the bell/inbox)
  3. Email to workspace admins — using the tenant_quota_cap_exceeded template, with a direct link to the event-review page and an upgrade CTA
  4. 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_exceeded signal is surfaced to your CSM for an upgrade conversation rather than an auto-charge.

What’s next