Skip to main content
The SDK ships three built-in triggers you can enable without writing any detection code. Each one is registered in the built-in catalog and selectable via integration_config.proactive_triggers.builtins in your products.json.
Need to write your own trigger? See Authoring proactive triggers.

How to enable built-ins

Add a builtins array to integration_config.proactive_triggers. Each row must include id, name, and description β€” these are required non-empty strings used in logs and admin UIs.
Order matters β€” evaluate_first returns the first matching trigger. Put higher-priority triggers earlier in the list. Two optional per-row overrides apply to any built-in: Invalid rows (unknown id, missing required fields) raise SdkConfigError and log event=proactive_builtin_spec_invalid.

Built-in catalog

What it detects

The user is bouncing back and forth between the same canonical URLs β€” a strong signal of hesitation, confusion, or indecision. The trigger fires after the URL sequence shows at least min_cycles complete back-and-forth cycles between the same pages.Example pattern that fires (min_cycles=1):

Stable ID

Default registry

This is the only trigger included in default_proactive_trigger_registry(). If you omit builtins entirely from your config, ping-pong is active by default.

Tuning

min_cycles is set at catalog instantiation time via the JSON row β€” it is not a runtime context_extra key.

Result

JSON config example

Scoping to a URL prefix

To limit ping-pong detection to a specific area of your product (e.g. only the /projects section), use ScopedCanonicalPingPongTrigger in code β€” this is not configurable from JSON.

What it detects

The user has been on the same canonical URL for longer than dwell_threshold_seconds and has performed fewer than user_page_dwell_max_actions actions during that time. This distinguishes passive lingering (stuck, confused, reading carefully) from active exploration (clicking around, filling forms).The trigger only looks at the trailing run of recent_actions that share the same canonical_url as the latest action β€” it ignores earlier visits to the same page.Fires when:
  1. The trailing URL streak spans β‰₯ dwell_threshold_seconds
  2. The streak contains ≀ user_page_dwell_max_actions actions
If the user has more actions on the streak than the max, the trigger does not fire β€” the system treats it as intentional exploration, not passive linger.

Stable ID

Tuning

All tuning keys can be set in integration_config.proactive_triggers (stock connector copies them into context_extra automatically) or passed directly in context_extra when building ProactiveTriggerContext in code.Test-only key: eval_now (Unix timestamp as float) β€” fixes the definition of β€œnow” during unit tests so dwell duration is deterministic.

Result

JSON config example

What it detects

The user is in a part of your product (a β€œsection”) that has a matching entry in a guidance playbook you define. The trigger resolves which section the user is currently in, looks up the playbook, and fires with the section-specific copy if a match is found.This is the most configurable built-in β€” it lets you define custom proactive messages for different areas of your product without writing Python code.

Stable ID

Configuration inputs

All inputs are passed via context_extra (or integration_config.proactive_triggers when using the stock event connector, which copies the keys automatically).

section_url_rules β€” Map URLs to sections

An ordered array of { prefix, section_id } objects. The first prefix that matches the user’s current canonical URL wins. Put longer / more specific prefixes first.
section_url_fallback_id (optional) β€” bucket ID for URLs that match no prefix. Defaults internally to "other" when resolving unmapped paths.

section_playbook β€” Guidance copy per section

A dict mapping section_id to either a plain body string or a structured row:

product_section_playbook β€” Section activity data (auto-computed)

When section_url_rules is non-empty, the stock event connector computes this automatically and attaches it to context_extra. It contains:
  • runtime.current_section_id β€” the section ID resolved from the user’s latest action
  • sections[section_id].visit_count β€” number of times the user visited this section
  • sections[section_id].dwell_seconds_per_visit β€” list of floats, one per visit
  • sections[section_id].first_visited_at / last_visited_at β€” ISO 8601 UTC timestamps
You do not need to set this manually when using the stock connector.

Resolution order

When multiple sections could match, the trigger picks the section to use in this order:
  1. runtime.current_section_id if it exists in section_playbook (current page takes priority)
  2. The section with the highest total dwell time across all visits, among sections that appear in both product_section_playbook.sections and section_playbook (tie-break: visit_count)
  3. runtime.current_section_id if it has a playbook row (fallback)

Result

JSON config example


Quick comparison