Envelope fields
id, type, message_id, tenant_id, timestamp, and received_at are present on every event and managed by the platform.
Every event captured by GrowthOS — whether from the client SDK, server API, or internal system — flows through a unified event bus. The bus routes each event to every module that subscribes, enabling cross-module automation without point-to-point wiring.
Every event in GrowthOS follows a standard envelope structure regardless of its source.
{ "id": "evt_abc123", "type": "track", "user_id": "usr_123", "anonymous_id": "anon_xyz", "event": "feature_activated", "properties": {}, "context": { "ip": "203.0.113.1", "user_agent": "...", "locale": "en-US", "page": { "url": "...", "title": "...", "referrer": "..." }, "library": { "name": "@growthos/js", "version": "1.0.0" }, "campaign": { "source": "google", "medium": "cpc", "name": "spring_sale" } }, "timestamp": "2025-06-01T12:00:00Z", "received_at": "2025-06-01T12:00:01Z", "message_id": "msg_unique123", "tenant_id": "tenant_abc"}Envelope fields
id, type, message_id, tenant_id, timestamp, and received_at are present on every event and managed by the platform.
Identity fields
user_id and anonymous_id link the event to a contact. At least one must be present. When both exist, GrowthOS merges them automatically.
Payload fields
event (the name) and properties (arbitrary key-value data) carry the domain-specific information.
Context fields
context is auto-collected by SDKs and includes device, page, campaign, and library metadata.
Consistent naming keeps automations, segments, and analytics reliable across your team.
Use snake_case for all event names and property keys — e.g., feature_activated, plan_name.
Use object.action for domain events — e.g., referral.converted, survey.completed, invoice.paid.
System events start with $ — e.g., $page_view, $session_start. These are emitted by the platform automatically.
Custom events omit the $ prefix — they are developer-defined and free-form within the rules below.
Limits
GrowthOS emits the following system events automatically. You can subscribe to them in automations, segments, and webhooks just like custom events.
| Event | Trigger | Key Properties |
|---|---|---|
$page_view | Auto on page load | url, title, referrer, path |
$session_start | Auto on new session | session_id, duration |
$session_end | Auto on session close | session_id, duration, pages |
$identify | identify() call | traits (merged with contact) |
$form_submitted | Auto on form submit | form_id, fields |
| Event | Trigger | Key Properties |
|---|---|---|
$referral_link_created | Referral link generated | program_id, link_code |
$referral_clicked | Referral link clicked | referrer_id, link_code |
$referral_converted | Referred user converts | referrer_id, referred_id, reward |
| Event | Trigger | Key Properties |
|---|---|---|
$survey_shown | Survey displayed | survey_id, type |
$survey_responded | Survey submitted | survey_id, score, answers |
| Event | Trigger | Key Properties |
|---|---|---|
$waitlist_joined | Waitlist signup | waitlist_id, position |
$waitlist_referred | Shared waitlist link | waitlist_id, referrer_id |
$waitlist_approved | Entry approved | waitlist_id, entry_id |
| Event | Trigger | Key Properties |
|---|---|---|
$email_sent | Email dispatched | campaign_id, template_id |
$email_opened | Email opened | campaign_id, contact_id |
$email_clicked | Link clicked in email | campaign_id, url |
$email_bounced | Bounce received | campaign_id, bounce_type |
$email_unsubscribed | Unsubscribe action | campaign_id, reason |
| Event | Trigger | Key Properties |
|---|---|---|
$nudge_shown | In-app nudge displayed | nudge_id, type |
$nudge_dismissed | Nudge dismissed | nudge_id |
$nudge_clicked | Nudge CTA clicked | nudge_id, action |
$checklist_step_completed | Onboarding step done | checklist_id, step_id |
$checklist_completed | All steps done | checklist_id |
| Event | Trigger | Key Properties |
|---|---|---|
$coupon_redeemed | Coupon used | coupon_code, discount |
$upgrade_prompt_shown | Upgrade prompt displayed | prompt_id, trigger |
$upgrade_prompt_clicked | User clicked upgrade | prompt_id |
The real power of the event bus is cross-module automation — an event in one module triggers actions in others without any custom code.
| Source Event | Condition | Automated Action |
|---|---|---|
$survey_responded | NPS score is 9 or 10 | Auto-trigger referral invite email to the promoter |
$referral_converted | Referred user completes signup | Update referrer contact score and send reward email |
$waitlist_joined | New waitlist entry | Create contact, add to waitlist segment, start welcome email sequence |
$email_clicked | Link clicked in campaign | Update engagement score on contact; optionally trigger an in-app nudge |
Keep names stable
Renaming an event breaks every automation, segment, and funnel that references it. Treat event names as a contract.
Include rich properties
Add enough properties for downstream segmentation — e.g., include plan_name and billing_interval on a subscription.created event.
Use ISO 8601 timestamps
Always send timestamps in ISO 8601 format with UTC offset. The SDK does this by default; server-side callers must ensure it.
No PII in properties
Do not put personally identifiable information (email, phone, name) in event properties. Store PII as traits on the contact via identify() instead.
{ "event": "subscription.created", "properties": { "plan_name": "pro", "billing_interval": "annual", "mrr_cents": 9900 }}{ "event": "SubCreated", "properties": { "email": "user@example.com", "plan": "Pro Plan ($99/yr)" }}