Documentation Index
Fetch the complete documentation index at: https://developers.autoplay.ai/llms.txt
Use this file to discover all available pages before exploring further.
INTERCOM_WEBHOOK_TOPICS is the same tuple IntercomChatbot uses in the event connector for session-link webhooks, so what you subscribe to in Intercom stays aligned with parsing and linking.
intercom_chatbot_webhook_url builds the correct /chatbot-webhook/{product_id} URL and rejects invalid product_ids; format_reactive_session_link_script matches optional POST /sessions/link when you still need browser-side linking. Proactive quick_reply helpers (correct Intercom-Version: Unstable, pingโpong trigger, optional connector LLM-label URL) are documented in Proactive Messenger quick replies below.
Module: autoplay_sdk.integrations.intercom
Small, dependency-light helpers in autoplay_sdk/integrations/intercom.py for Developer Hub URL/topics, the reactive snippet, and proactive Messenger quick replies.
Webhook topics
Subscribe in Intercom to exactly these topics so session linking and chatbot delivery stay aligned with IntercomChatbot parsing in the connector:
| Name | Constant | Value |
|---|
| User created | INTERCOM_WEBHOOK_TOPIC_USER_CREATED | conversation.user.created |
| User replied | INTERCOM_WEBHOOK_TOPIC_USER_REPLIED | conversation.user.replied |
Tuple for loops / UI: INTERCOM_WEBHOOK_TOPICS โ both strings above. Other topics are ignored by the connector.
intercom_chatbot_webhook_url(connector_host, product_id) -> str
Builds the absolute HTTPS URL for Intercom outbound webhooks:
{origin}/chatbot-webhook/{product_id}
connector_host: hostname (event-connector-xxxx.onrender.com) or full origin (https://โฆ). Trailing slashes stripped; bare host gets https:// prepended.
product_id: non-empty, no / (single path segment).
Raises ValueError if host or product id is invalid.
Returns an HTML/JS snippet that registers Intercom("onConversationStarted", โฆ) and POSTs to /sessions/link with product_id, PostHog session_id, and conversation_id.
Use only when you still need browser-side linking; prefer webhooks to /chatbot-webhook/{product_id} first.
Proactive Messenger quick replies
A proactive quick reply is an admin message on the conversation with message_type: quick_reply: an intro body plus up to three tappable reply_options. Send it with POST {INTERCOM_REST_API_BASE}/conversations/{conversation_id}/reply.
Split responsibilities:
| Concern | Module | What you use |
|---|
| When to show an offer | autoplay_sdk.proactive_triggers | ProactiveTriggerContext, ProactiveTriggerRegistry.evaluate_first, ProactiveTriggerResult (copy, optional labels, interaction_timeout_s, cooldown_s). |
| How to send on the wire | autoplay_sdk.integrations.intercom | intercom_quick_reply_http_headers, build_intercom_quick_reply_reply_payload โ never hand-roll Intercom-Version. |
| Idle teardown (chat) | autoplay_sdk.agent_states + autoplay_sdk.integrations.intercom | run_proactive_idle_expiry with delete_remote_chat_thread using build_intercom_delete_conversation_request + DELETE; then clear_local_chat_thread_state (sessionโthread + persisted FSM โ host-owned). |
Prerequisites
conversation_id โ From Intercom webhooks, SlimAction conversation_id, or after POST /sessions/link links the browser session.
- Access token โ Bearer token for the Intercom API (app allowed to reply as admin).
admin_id โ Workspace/agent id Intercom expects on quick_reply payloads (from Intercom app settings).
Recipe: end-to-end
-
Build context โ Instantiate
ProactiveTriggerContext with chronological canonical_urls (and session_id, conversation_id, action_count, product_id when you have them).
-
Evaluate โ
result = registry.evaluate_first(ctx) using default_proactive_trigger_registry() or your own ProactiveTriggerRegistry([...]). If result is None, do not send.
-
Gate (recommended) โ Cooldown: skip if
now - last_fired_at < result.cooldown_s for (conversation_id, result.trigger_id) (you store last_fired_at). FSM: with Agent session states, call can_show_proactive_with_reason() before sending; after a successful send, move to proactive_assistance. When the user never engages past result.interaction_timeout_s, use run_proactive_idle_expiry for chat surfaces (not expire_proactive_to_thinking_if_idle alone): implement ProactiveIdleExpiryHooks โ delete_remote_chat_thread performs DELETE /conversations/{id} via build_intercom_delete_conversation_request, returns True only on 2xx or 404; then the orchestrator runs expire_proactive_to_thinking_if_idle and clear_local_chat_thread_state (sessionโthread + persisted FSM โ your host). See Delete conversation (idle teardown).
-
Build the POST โ
headers = intercom_quick_reply_http_headers(access_token). payload = build_intercom_quick_reply_reply_payload(admin_id=..., body=result.body, prompt_labels=list(result.reply_option_labels)). Empty prompt_labels is fine (intro-only).
-
Send โ
POST {INTERCOM_REST_API_BASE}/conversations/{conversation_id}/reply with json=payload and headers.
Inbound user messages vs the proactive chip: After the connector shows a proactive quick_reply, the session FSM is proactive_assistance. If the user sends normal chat text that does not match the configured chip label, the connector moves the FSM to reactive_assistance and runs the usual RAG reply pipeline so a real question is answered. Tapping the chip (message text matches the label) still enters guidance_execution with the proactive expert-help flow id.
Delete conversation (idle teardown)
For run_proactive_idle_expiry โ delete_remote_chat_thread, build the HTTP target from pure helpers (Bearer + Intercom-Version: 2.15 via INTERCOM_API_VERSION_DELETE_CONVERSATION):
build_intercom_delete_conversation_request(access_token, conversation_id, *, retain_metrics=True) -> tuple[str, dict[str, str]] โ returns (url, headers) for client.delete(url, headers=headers).
- Lower-level:
intercom_delete_conversation_url, intercom_delete_conversation_headers.
Treat 404 as success (conversation already removed). Do not perform local session unlink or FSM deletion until DELETE succeeds โ the SDK orchestrator encodes that order.
Example (Python)
from autoplay_sdk.integrations.intercom import (
INTERCOM_PROACTIVE_QUICK_REPLY_DEFAULT_BODY,
INTERCOM_REST_API_BASE,
build_intercom_quick_reply_reply_payload,
intercom_quick_reply_http_headers,
proactive_trigger_canonical_url_ping_pong,
)
from autoplay_sdk.proactive_triggers import (
ProactiveTriggerContext,
default_proactive_trigger_registry,
)
registry = default_proactive_trigger_registry()
def maybe_send_proactive_quick_reply(
*,
access_token: str,
admin_id: str,
conversation_id: str,
canonical_urls: list[str | None],
session_id: str = "",
) -> bool:
"""Returns True if a quick_reply was sent (after your cooldown/FSM checks)."""
ctx = ProactiveTriggerContext(
canonical_urls=canonical_urls,
conversation_id=conversation_id,
session_id=session_id,
)
result = registry.evaluate_first(ctx)
if result is None:
return False
# Apply cooldown using result.cooldown_s and result.trigger_id.
# Optionally gate with AgentStateMachine.can_show_proactive_with_reason().
payload = build_intercom_quick_reply_reply_payload(
admin_id=admin_id,
body=result.body,
prompt_labels=list(result.reply_option_labels),
)
headers = intercom_quick_reply_http_headers(access_token)
url = f"{INTERCOM_REST_API_BASE}/conversations/{conversation_id}/reply"
# requests.post(url, json=payload, headers=headers, timeout=30)
return True
def minimal_ping_pong_only(
*,
access_token: str,
admin_id: str,
conversation_id: str,
urls: list[str | None],
) -> bool:
"""Same POST shape without the proactive registry โ predicate + default body only."""
if not proactive_trigger_canonical_url_ping_pong(urls):
return False
payload = build_intercom_quick_reply_reply_payload(
admin_id=admin_id,
body=INTERCOM_PROACTIVE_QUICK_REPLY_DEFAULT_BODY,
prompt_labels=[],
)
headers = intercom_quick_reply_http_headers(access_token)
_url = f"{INTERCOM_REST_API_BASE}/conversations/{conversation_id}/reply"
return True
Helpers reference (delivery)
Quick replies must use Intercom-Version: Unstable. Do not reuse a numeric Intercom-Version from other Intercom REST calls.
| Constant | Value |
|---|
INTERCOM_HTTP_HEADER_VERSION | "Intercom-Version" |
INTERCOM_API_VERSION_QUICK_REPLY | "Unstable" (alias: INTERCOM_API_VERSION_UNSTABLE) |
| Helper | Purpose |
|---|
intercom_quick_reply_http_headers(access_token) | Authorization, Content-Type, Intercom-Version: Unstable. |
INTERCOM_PROACTIVE_QUICK_REPLY_DEFAULT_BODY | Default intro ("Need my expert help?") when you build body without ProactiveTriggerResult. |
build_intercom_quick_reply_reply_payload(admin_id=โฆ, body=โฆ, prompt_labels=[โฆ]) | JSON for quick_reply with reply_options (โค INTERCOM_PROACTIVE_PROMPTS_MAX = 3). |
normalize_intercom_quick_reply_labels | Strip / cap labels (used inside build_intercom_quick_reply_reply_payload). |
proactive_trigger_canonical_url_ping_pong(urls) | Low-level boolean predicate if you skip ProactiveTriggerRegistry. |
Base URL: INTERCOM_REST_API_BASE (https://api.intercom.io).
Package: autoplay_sdk.proactive_triggers
Transport-agnostic detection (when to offer): ProactiveTriggerContext, ProactiveTriggerResult (trigger_id, body, optional reply_option_labels, metadata, interaction_timeout_s, cooldown_s โ defaults 10s / 30s), ProactiveTriggerTimings, ProactiveTriggerEntity, ProactiveTriggerRegistry (evaluate_first / evaluate_all). Built-in trigger_id strings for shipped triggers are centralized in defaults (ProactiveTriggerIds, get_proactive_trigger_ids(), TRIGGER_ID_CANONICAL_URL_PING_PONG). CanonicalPingPongTrigger wraps proactive_trigger_canonical_url_ping_pong. default_proactive_trigger_registry() uses ProactiveTriggerEntity(CanonicalPingPongTrigger(), ProactiveTriggerTimings()). Default quick-reply intro copy for Intercom is DEFAULT_PROACTIVE_QUICK_REPLY_BODY in proactive_triggers.defaults, re-exported as INTERCOM_PROACTIVE_QUICK_REPLY_DEFAULT_BODY from integrations.intercom.
See the dedicated Proactive triggers page. Wire a firing ProactiveTriggerResult into build_intercom_quick_reply_reply_payload as in the recipe above.
Adding your own trigger
- Implement
ProactiveTrigger: trigger_id and evaluate(ctx) -> ProactiveTriggerResult | None.
- Optionally wrap with
ProactiveTriggerEntity(inner, ProactiveTriggerTimings(...)) for custom timeouts / cooldown.
- Register on
ProactiveTriggerRegistry in priority order; call evaluate_first in production.
POST โฆ/intercom/proactive/{product_id} (admin key) returns up to three short strings for reply_options text when you want LLM-suggested labels alongside URL pingโpong (or other) proactive flows. Build the URL with intercom_connector_llm_prompt_labels_url, JSON body with build_connector_llm_prompt_labels_request_body. Product integration_config may include a proactive block โ model it with IntercomProactivePolicyConfig.to_integration_config_fragment().
Connector endpoints (reference)
| HTTP | Role |
|---|
POST /chatbot-webhook/{product_id} | Primary path: Intercom signed webhooks for conversation.user.created / conversation.user.replied. |
POST /sessions/link | Optional: JSON body links session โ conversation (reactive snippet). |
POST /intercom/proactive/{product_id} | Optional: LLM-generated proactive quick-reply labels (admin key). |
Webhook verification uses Intercom X-Hub-Signature-256 and your app client secret in product config.
Delivery stack in this repo
BaseChatbotWriter (Chatbot writer) โ pre-link buffer, post-link debounce, shared note body format (format_chatbot_note_header, numbered action lines, binning).
IntercomChatbot (event connector flows/chatbot/intercom.py) โ subclass; implements Intercom REST admin notes (_post_note, _redact_part) and webhook payload parsing for linking.
For LLM summaries and redaction ordering, pair with AsyncAgentContextWriter as described on the chatbot-writer page.