# Dashboard Events

<WebSDK2 />

The SDK provides a fire-and-forget event tracking system for sending telemetry to the Incode dashboard. This allows you to track module lifecycle, screen transitions, and custom events throughout your verification flow.

## Overview

Events are sent automatically by the SDK's built-in modules, but you can also track custom events from your own code. All events are:

* **Fire-and-forget**: Never blocks execution, never throws errors
* **Headless-compatible**: Available in `@incodetech/core` with zero UI dependencies
* **Auto-queued**: Events sent before SDK is configured are queued and sent automatically when `setup()` is called

## Quick Start

```typescript
import { 
  addEvent, 
  moduleOpened, 
  moduleClosed,
  eventModuleNames 
} from '@incodetech/core/events';

// Track module lifecycle
useEffect(() => {
  moduleOpened(eventModuleNames.phone);
  return () => moduleClosed(eventModuleNames.phone);
}, []);

// Track custom events
addEvent({
  code: 'customButtonClicked',
  module: eventModuleNames.phone,
  payload: { buttonId: 'submit' },
});
```

### Event Logging

Subscribe to all events for custom logging or analytics:

```typescript
import { subscribeEvent } from '@incodetech/core/events';

// Custom logger - separate from API event sending
const unsubscribeLogger = subscribeEvent((event) => {
  console.log('[Event]', {
    code: event.code,
    module: event.module,
    screen: event.screen,
    payload: event.payload,
    timestamp: new Date(event.clientTimestamp ?? Date.now()).toISOString(),
  });
});

// Events are logged AND sent to API independently
addEvent({ code: 'test', module: 'PHONE' });
// → Logged to console via listener
// → Sent to API (automatic)

// Remove logger when done
unsubscribeLogger();
```

## API Reference

### Core Functions

#### `addEvent(event)`

Sends a single event to the dashboard. Fire-and-forget - never blocks or throws.

```typescript
addEvent({
  code: 'captureAttemptFinished',
  module: eventModuleNames.id,
  screen: eventScreenNames.frontCameraCapture,
  payload: { logs: [], attemptNumber: 1 },
  clientTimestamp: Date.now(), // Optional, defaults to now
});
```

**Parameters:**

| Field             | Type                      | Required | Description                                          |
| ----------------- | ------------------------- | -------- | ---------------------------------------------------- |
| `code`            | `string`                  | Yes      | Event code identifier                                |
| `module`          | `EventModule \| string`   | No       | Module name (use `eventModuleNames` constants)       |
| `screen`          | `EventScreen \| string`   | No       | Screen name (use `eventScreenNames` constants)       |
| `payload`         | `Record<string, unknown>` | No       | Additional event data                                |
| `clientTimestamp` | `number`                  | No       | Timestamp in milliseconds (defaults to `Date.now()`) |

#### `subscribeEvent(listener)`

Subscribes to all SDK events for logging, debugging, or custom analytics. Returns an unsubscribe function.

```typescript
const unsubscribe = subscribeEvent((event) => {
  console.log('[Event]', event.code, event.module);
  // Your custom logic here
});

// Later
unsubscribe();
```

**Parameters:**

| Field      | Type                     | Required | Description                                |
| ---------- | ------------------------ | -------- | ------------------------------------------ |
| `listener` | `(event: Event) => void` | Yes      | Callback function that receives each event |

**Returns:** Unsubscribe function to remove the listener

**Note:** Subscribers receive events **before** they're queued or sent to the API. Listener errors are silently caught and don't affect event sending.

#### Helper Functions

```typescript
// Module lifecycle
moduleOpened(module, screen?, payload?);
moduleClosed(module, screen?, payload?);

// Screen lifecycle
screenOpened(module, screen, payload?);
screenClosed(module, screen, payload?);

// Generic screen event
screenEvent({ module, screen, code, payload });
```

## Available Constants

### Module Names

```typescript
import { eventModuleNames } from '@incodetech/core/events';
```

| Constant                                  | Wire value                    |
| ----------------------------------------- | ----------------------------- |
| `eventModuleNames.selfie`                 | `'SELFIE'`                    |
| `eventModuleNames.authentication`         | `'AUTHENTICATION'`            |
| `eventModuleNames.authFace`               | `'AUTH_FACE'`                 |
| `eventModuleNames.mlConsent`              | `'ML_CONSENT'`                |
| `eventModuleNames.combinedConsent`        | `'COMBINED_CONSENT'`          |
| `eventModuleNames.curpValidation`         | `'CURP_VALIDATION'`           |
| `eventModuleNames.faceMatch`              | `'FACE_MATCH'`                |
| `eventModuleNames.qr`                     | `'QR'`                        |
| `eventModuleNames.videoSelfie`            | `'VIDEO_ONBOARDING'`          |
| `eventModuleNames.id`                     | `'ID'`                        |
| `eventModuleNames.front`                  | `'ID'` (alias)                |
| `eventModuleNames.back`                   | `'ID'` (alias)                |
| `eventModuleNames.passport`               | `'ID'` (alias)                |
| `eventModuleNames.idOcr`                  | `'ID_OCR'`                    |
| `eventModuleNames.document`               | `'DOCUMENT_CAPTURE'`          |
| `eventModuleNames.creditCard`             | `'CREDIT_CARD_FRONT'`         |
| `eventModuleNames.creditCardBack`         | `'CREDIT_CARD_BACK'`          |
| `eventModuleNames.conference`             | `'CONFERENCE'`                |
| `eventModuleNames.otp`                    | `'OTP'`                       |
| `eventModuleNames.signature`              | `'SIGNATURE'`                 |
| `eventModuleNames.ekyc`                   | `'EXTERNAL_VERIFICATION'`     |
| `eventModuleNames.watchlist`              | `'WATCHLIST'`                 |
| `eventModuleNames.globalWatchList`        | `'GLOBAL_WATCHLIST'`          |
| `eventModuleNames.customWatchList`        | `'CUSTOM_WATCHLIST'`          |
| `eventModuleNames.watchlistForBusiness`   | `'WATCHLIST_FOR_BUSINESS'`    |
| `eventModuleNames.email`                  | `'EMAIL'`                     |
| `eventModuleNames.phone`                  | `'PHONE'`                     |
| `eventModuleNames.instantVerifyEmail`     | `'INSTANT_VERIFY_EMAIL'`      |
| `eventModuleNames.instantVerifyConsent`   | `'INSTANT_VERIFY_CONSENT'`    |
| `eventModuleNames.forms`                  | `'FORMS'`                     |
| `eventModuleNames.customModule`           | `'CUSTOM_MODULE'`             |
| `eventModuleNames.customFields`           | `'CUSTOM_FIELDS'`             |
| `eventModuleNames.geolocation`            | `'GEOLOCATION'`               |
| `eventModuleNames.ekyb`                   | `'EKYB'`                      |
| `eventModuleNames.identityReuse`          | `'IDENTITY_REUSE'`            |
| `eventModuleNames.antifraud`              | `'ANTIFRAUD'`                 |
| `eventModuleNames.aeSignature`            | `'AE_SIGNATURE'`              |
| `eventModuleNames.qeSignature`            | `'QE_SIGNATURE'`              |
| `eventModuleNames.crossDocumentDataMatch` | `'CROSS_DOCUMENT_DATA_MATCH'` |

### Screen Names

```typescript
import { eventScreenNames } from '@incodetech/core/events';
```

Grouped by module for readability.

#### Selfie / Face Capture

| Constant                               | Wire value                  |
| -------------------------------------- | --------------------------- |
| `eventScreenNames.faceCaptureTutorial` | `'SELFIE_CAPTURE_TUTORIAL'` |
| `eventScreenNames.faceCaptureCamera`   | `'SELFIE_CAMERA_CAPTURE'`   |
| `eventScreenNames.faceCaptureError`    | `'SELFIE_ATTEMPT_FAILED'`   |
| `eventScreenNames.faceCaptureSuccess`  | `'SELFIE_UPLOAD_SUCCEEDED'` |
| `eventScreenNames.faceCaptureUpload`   | `'SELFIE_UPLOAD_PROGRESS'`  |
| `eventScreenNames.faceMatch`           | `'FACE_MATCH'`              |

#### Auth Face

| Constant                                 | Wire value                     |
| ---------------------------------------- | ------------------------------ |
| `eventScreenNames.authFace`              | `'AUTH_FACE'`                  |
| `eventScreenNames.authFaceTutorial`      | `'AUTH_FACE_TUTORIAL'`         |
| `eventScreenNames.authFaceError`         | `'AUTH_FACE_ATTEMPT_FAILED'`   |
| `eventScreenNames.authFaceUpload`        | `'AUTH_FACE_UPLOAD_PROGRESS'`  |
| `eventScreenNames.authFaceUploadSuccess` | `'AUTH_FACE_UPLOAD_SUCCEEDED'` |
| `eventScreenNames.authFaceUploadFailed`  | `'AUTH_FACE_UPLOAD_FAILED'`    |

#### Consent

| Constant                           | Wire value                   |
| ---------------------------------- | ---------------------------- |
| `eventScreenNames.mlConsent`       | `'MACHINE_LEARNING_CONSENT'` |
| `eventScreenNames.combinedConsent` | `'COMBINED_CONSENT'`         |

#### CURP

| Constant                                 | Wire value                    |
| ---------------------------------------- | ----------------------------- |
| `eventScreenNames.curpEnter`             | `'ENTER_CURP'`                |
| `eventScreenNames.curpValidate`          | `'VALIDATE_CURP'`             |
| `eventScreenNames.curpGenerate`          | `'GENERATE_CURP'`             |
| `eventScreenNames.curpValidation`        | `'CURP_VALIDATION_PROGRESS'`  |
| `eventScreenNames.curpValidationFailed`  | `'CURP_VALIDATION_FAILED'`    |
| `eventScreenNames.curpValidationSuccess` | `'CURP_VALIDATION_SUCCEEDED'` |

#### QR

| Constant                      | Wire value      |
| ----------------------------- | --------------- |
| `eventScreenNames.qrTutorial` | `'QR_TUTORIAL'` |
| `eventScreenNames.qrScan`     | `'QR_SCAN'`     |

#### OTP / Signature

| Constant                          | Wire value          |
| --------------------------------- | ------------------- |
| `eventScreenNames.smsOtp`         | `'OTP'`             |
| `eventScreenNames.simpleOTP`      | `'OTP'` (alias)     |
| `eventScreenNames.signatureInput` | `'SIGNATURE_INPUT'` |

#### ID — Front

| Constant                               | Wire value                    |
| -------------------------------------- | ----------------------------- |
| `eventScreenNames.frontTutorial`       | `'FRONT_ID_CAPTURE_TUTORIAL'` |
| `eventScreenNames.frontCameraCapture`  | `'FRONT_ID_CAMERA_CAPTURE'`   |
| `eventScreenNames.frontHelp`           | `'FRONT_ID_HELP'`             |
| `eventScreenNames.frontReviewPhoto`    | `'FRONT_ID_REVIEW_PHOTO'`     |
| `eventScreenNames.frontUploadProgress` | `'FRONT_ID_UPLOAD_PROGRESS'`  |
| `eventScreenNames.frontAttemptFailed`  | `'FRONT_ID_ATTEMPT_FAILED'`   |
| `eventScreenNames.frontUploadSuccess`  | `'FRONT_ID_UPLOAD_SUCCEEDED'` |

#### ID — Back

| Constant                              | Wire value                   |
| ------------------------------------- | ---------------------------- |
| `eventScreenNames.backTutorial`       | `'BACK_ID_CAPTURE_TUTORIAL'` |
| `eventScreenNames.backCameraCapture`  | `'BACK_ID_CAMERA_CAPTURE'`   |
| `eventScreenNames.backHelp`           | `'BACK_ID_HELP'`             |
| `eventScreenNames.backReviewPhoto`    | `'BACK_ID_REVIEW_PHOTO'`     |
| `eventScreenNames.backUploadProgress` | `'BACK_ID_UPLOAD_PROGRESS'`  |
| `eventScreenNames.backAttemptFailed`  | `'BACK_ID_ATTEMPT_FAILED'`   |
| `eventScreenNames.backUploadSuccess`  | `'BACK_ID_UPLOAD_SUCCEEDED'` |

#### Passport

| Constant                            | Wire value            |
| ----------------------------------- | --------------------- |
| `eventScreenNames.passportTutorial` | `'TUTORIAL_PASSPORT'` |

#### Document Capture

| Constant                                  | Wire value                      |
| ----------------------------------------- | ------------------------------- |
| `eventScreenNames.documentTutorial`       | `'DOCUMENT_CAPTURE_TUTORIAL'`   |
| `eventScreenNames.documentCameraCapture`  | `'DOCUMENT_CAMERA_CAPTURE'`     |
| `eventScreenNames.documentHelp`           | `'DOCUMENT_HELP'`               |
| `eventScreenNames.documentReviewPhoto`    | `'DOCUMENT_REVIEW_PHOTO'`       |
| `eventScreenNames.documentUploadProgress` | `'DOCUMENT_UPLOAD_IN_PROGRESS'` |
| `eventScreenNames.documentAttemptFailed`  | `'DOCUMENT_ATTEMPT_FAILED'`     |
| `eventScreenNames.documentUploadSuccess`  | `'DOCUMENT_UPLOAD_SUCCEEDED'`   |

#### Digital ID

| Constant                                        | Wire value                          |
| ----------------------------------------------- | ----------------------------------- |
| `eventScreenNames.digitalIdFileSelection`       | `'DIGITAL_ID_FILE_SELECTION'`       |
| `eventScreenNames.digitalIdFileReview`          | `'DIGITAL_ID_FILE_REVIEW'`          |
| `eventScreenNames.digitalIdUploadProgress`      | `'DIGITAL_ID_UPLOAD_PROGRESS'`      |
| `eventScreenNames.digitalIdUploadSuccess`       | `'DIGITAL_ID_UPLOAD_SUCCEEDED'`     |
| `eventScreenNames.digitalIdUploadFailed`        | `'DIGITAL_ID_UPLOAD_FAILED'`        |
| `eventScreenNames.digitalIdAnalysisProgress`    | `'DIGITAL_ID_ANALYSIS_PROGRESS'`    |
| `eventScreenNames.digitalIdVerificationSuccess` | `'DIGITAL_ID_VERIFICATION_SUCCESS'` |
| `eventScreenNames.digitalIdVerificationFailed`  | `'DIGITAL_ID_VERIFICATION_FAILED'`  |

#### Conference

| Constant                                 | Wire value                  |
| ---------------------------------------- | --------------------------- |
| `eventScreenNames.conferenceWait`        | `'CONFERENCE_WAIT'`         |
| `eventScreenNames.conferenceVideoChat`   | `'CONFERENCE_VIDEO_CHAT'`   |
| `eventScreenNames.conferenceMessageChat` | `'CONFERENCE_MESSAGE_CHAT'` |

#### Video Selfie

| Constant                                             | Wire value                                 |
| ---------------------------------------------------- | ------------------------------------------ |
| `eventScreenNames.videoSelfieTutorial`               | `'VIDEO_SELFIE_TUTORIAL'`                  |
| `eventScreenNames.videoSelfie`                       | `'VIDEO_SELFIE'`                           |
| `eventScreenNames.videoSelfieFaceCapture`            | `'VIDEO_SELFIE_FACE_CAPTURE'`              |
| `eventScreenNames.videoSelfieFaceUploadProgress`     | `'VIDEO_SELFIE_FACE_UPLOAD_PROGRESS'`      |
| `eventScreenNames.videoSelfieFaceAttemptFailed`      | `'VIDEO_SELFIE_FACE_ATTEMPT_FAILED'`       |
| `eventScreenNames.videoSelfieFaceUploadSucceeded`    | `'VIDEO_SELFIE_FACE_UPLOAD_SUCCEEDED'`     |
| `eventScreenNames.videoSelfieFrontIdCapture`         | `'VIDEO_SELFIE_FRONT_ID_CAPTURE'`          |
| `eventScreenNames.videoSelfieFrontIdUploadProgress`  | `'VIDEO_SELFIE_FRONT_ID_UPLOAD_PROGRESS'`  |
| `eventScreenNames.videoSelfieFrontIdAttemptFailed`   | `'VIDEO_SELFIE_FRONT_ID_ATTEMPT_FAILED'`   |
| `eventScreenNames.videoSelfieFrontIdUploadSucceeded` | `'VIDEO_SELFIE_FRONT_ID_UPLOAD_SUCCEEDED'` |
| `eventScreenNames.videoSelfieBackIdCapture`          | `'VIDEO_SELFIE_BACK_ID_CAPTURE'`           |
| `eventScreenNames.videoSelfieBackIdUploadProgress`   | `'VIDEO_SELFIE_BACK_ID_UPLOAD_PROGRESS'`   |
| `eventScreenNames.videoSelfieBackIdAttemptFailed`    | `'VIDEO_SELFIE_BACK_ID_ATTEMPT_FAILED'`    |
| `eventScreenNames.videoSelfieBackIdUploadSucceeded`  | `'VIDEO_SELFIE_BACK_ID_UPLOAD_SUCCEEDED'`  |
| `eventScreenNames.videoSelfieDocumentCapture`        | `'VIDEO_SELFIE_DOCUMENT_CAPTURE'`          |
| `eventScreenNames.videoSelfieVoiceQuestion`          | `'VIDEO_SELFIE_VOICE_QUESTION'`            |
| `eventScreenNames.videoSelfieVoiceFinalQuestion`     | `'VIDEO_SELFIE_VOICE_FINAL_QUESTION'`      |
| `eventScreenNames.videoSelfieVideoUpload`            | `'VIDEO SELFIE VIDEO UPLOAD'`              |

#### eKYC / eKYB / Forms

| Constant                         | Wire value         |
| -------------------------------- | ------------------ |
| `eventScreenNames.ekycInput`     | `'EKYC_INPUT'`     |
| `eventScreenNames.ekycProgress`  | `'EKYC_PROGRESS'`  |
| `eventScreenNames.ekycSucceeded` | `'EKYC_SUCCEEDED'` |
| `eventScreenNames.ekycFailed`    | `'EKYC_FAILED'`    |
| `eventScreenNames.ekybForm`      | `'EKYB_FORM'`      |
| `eventScreenNames.forms`         | `'FORMS'`          |

#### Watchlists

| Constant                                   | Wire value                     |
| ------------------------------------------ | ------------------------------ |
| `eventScreenNames.globalWatchListInput`    | `'GLOBAL_WATCHLIST_INPUT'`     |
| `eventScreenNames.globalWatchListProgress` | `'GLOBAL_WATCHLIST_PROGRESS'`  |
| `eventScreenNames.globalWatchListSuccess`  | `'GLOBAL_WATCHLIST_SUCCEEDED'` |
| `eventScreenNames.globalWatchListFailed`   | `'GLOBAL_WATCHLIST_FAILED'`    |
| `eventScreenNames.customWatchListInput`    | `'CUSTOM_WATCHLIST_INPUT'`     |
| `eventScreenNames.customWatchListProgress` | `'CUSTOM_WATCHLIST_PROGRESS'`  |
| `eventScreenNames.customWatchListSuccess`  | `'CUSTOM_WATCHLIST_SUCCEEDED'` |
| `eventScreenNames.customWatchListFailed`   | `'CUSTOM_WATCHLIST_FAILED'`    |

#### Phone / Email / Instant Verify

| Constant                         | Wire value         |
| -------------------------------- | ------------------ |
| `eventScreenNames.phoneInput`    | `'PHONE_INPUT'`    |
| `eventScreenNames.emailInput`    | `'EMAIL_INPUT'`    |
| `eventScreenNames.instantVerify` | `'INSTANT_VERIFY'` |

#### Custom Module

| Constant                                  | Wire value                   |
| ----------------------------------------- | ---------------------------- |
| `eventScreenNames.customModuleCallback`   | `'CUSTOM_MODULE_CALLBACK'`   |
| `eventScreenNames.customModuleProcessing` | `'CUSTOM_MODULE_PROCESSING'` |

### Video Selfie Events

```typescript
import { videoSelfieEvents } from '@incodetech/core/events';
```

| Constant                                             | Wire value                           |
| ---------------------------------------------------- | ------------------------------------ |
| `videoSelfieEvents.imageTooBlurry`                   | `'imageTooBlurry'`                   |
| `videoSelfieEvents.fillFrame`                        | `'fillFrame'`                        |
| `videoSelfieEvents.checkCameraOrLighting`            | `'checkCameraOrLighting'`            |
| `videoSelfieEvents.frontIdCaptureStarted`            | `'frontIdCaptureStarted'`            |
| `videoSelfieEvents.lookAtCamera`                     | `'lookAtCamera'`                     |
| `videoSelfieEvents.moveCloser`                       | `'moveCloser'`                       |
| `videoSelfieEvents.lookingForFrontId`                | `'lookingForFrontId'`                |
| `videoSelfieEvents.lookingForBackId`                 | `'lookingForBackId'`                 |
| `videoSelfieEvents.cameraSwitchToBack`               | `'cameraSwitchToBack'`               |
| `videoSelfieEvents.cameraSwitchToFront`              | `'cameraSwitchToFront'`              |
| `videoSelfieEvents.audioStreamOpened`                | `'audioStreamOpened'`                |
| `videoSelfieEvents.audioStreamClosed`                | `'audioStreamClosed'`                |
| `videoSelfieEvents.videoSelfieVideoUploadInProgress` | `'videoSelfieVideoUploadInProgress'` |
| `videoSelfieEvents.videoSelfieTosNotAccepted`        | `'videoSelfieTosNotAccepted'`        |
| `videoSelfieEvents.videoSelfieTosAccepted`           | `'videoSelfieTosAccepted'`           |
| `videoSelfieEvents.videoSelfieStreamCreated`         | `'videoSelfieStreamCreated'`         |
| `videoSelfieEvents.videoSelfieStreamDestroyed`       | `'videoSelfieStreamDestroyed'`       |
| `videoSelfieEvents.videoSelfieReconnecting`          | `'videoSelfieReconnecting'`          |
| `videoSelfieEvents.videoSelfieReconnected`           | `'videoSelfieReconnected'`           |

## Event Queue

Events sent before the SDK is configured (before `setup()` is called with a token) are automatically queued. When `setup()` is called with a token, all queued events are automatically flushed and sent.

```typescript
// This event will be queued
addEvent({ code: 'sdkLoading', payload: { timestamp: Date.now() } });

// Later, after setup with token
await setup({ apiURL: '...', token: 'session-token' });
// ^ Queued events are automatically sent here
```

## Common Event Codes

The SDK automatically sends these events from built-in modules:

| Code                         | Description                 | When Sent                        |
| ---------------------------- | --------------------------- | -------------------------------- |
| `moduleOpened`               | Module started              | When module component mounts     |
| `moduleClosed`               | Module ended                | When module component unmounts   |
| `screenOpened`               | Screen displayed            | When screen component mounts     |
| `screenClosed`               | Screen hidden               | When screen component unmounts   |
| `continue`                   | User continued              | When user clicks continue/submit |
| `tutorialSelfie`             | Selfie tutorial shown       | When selfie tutorial displays    |
| `tutorialVideoStarted`       | ID tutorial video started   | When ID tutorial video plays     |
| `captureAttemptFinished`     | Capture attempt completed   | After capture upload completes   |
| `strSessionDidConnect`       | Recording session connected | When video recording starts      |
| `strStreamVideoCaptureStart` | Video capture started       | When video recording begins      |

## Examples

### Track Custom Button Click

```typescript
import { addEvent, eventModuleNames } from '@incodetech/core/events';

function handleSubmit() {
  addEvent({
    code: 'customSubmitClicked',
    module: eventModuleNames.phone,
    payload: { timestamp: Date.now() },
  });
  
  // Your submit logic...
}
```

### Track Screen Transition

```typescript
import { screenEvent, eventModuleNames, eventScreenNames } from '@incodetech/core/events';

function navigateToTutorial() {
  screenEvent({
    module: eventModuleNames.id,
    screen: eventScreenNames.frontTutorial,
    code: 'screenOpened',
  });
  
  // Navigate...
}
```

### Track Module Lifecycle in Headless Mode

```typescript
import { moduleOpened, moduleClosed, eventModuleNames } from '@incodetech/core/events';

function MyCustomPhoneFlow() {
  useEffect(() => {
    moduleOpened(eventModuleNames.phone);
    
    return () => {
      moduleClosed(eventModuleNames.phone);
    };
  }, []);
  
  // Your custom implementation...
}
```

## Best Practices

1. **Use constants** - Always use `eventModuleNames` and `eventScreenNames` instead of hardcoded strings
2. **Fire-and-forget** - Don't await `addEvent()` - it's designed to never block
3. **Use hooks for lifecycle** - Use `useModuleEvents`/`useScreenEvents` for automatic tracking
4. **Include context** - Add relevant data in `payload` for debugging
5. **Don't track sensitive data** - Never include PII or sensitive information in event payloads
6. **Use subscribers for custom logging** - Use `subscribeEvent()` for custom analytics or logging needs
7. **Keep listeners lightweight** - Event listeners are called synchronously; avoid heavy operations

## See Also

* [Headless Mode](doc:web-sdk-2-headless-mode): Using events in headless mode
* [Event Callbacks](doc:web-sdk-2-event-callbacks): SDK callbacks vs dashboard events
* [API Reference](doc:web-sdk-2-reference): Full API documentation
