> ## 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

> Intercom-specific SDK helpers in autoplay_sdk.integrations.intercom and how they map to the event connector.

`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_id`s; `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](#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 support AI agent 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.

### `format_reactive_session_link_script(connector_host, product_id) -> str`

Returns an HTML/JS snippet that registers `Intercom("onConversationStarted", …)` and `POST`s 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`](/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`](#module-autoplay_sdkintegrationsintercom)                                              | **`intercom_quick_reply_http_headers`**, **`build_intercom_quick_reply_reply_payload`** — never hand-roll **`Intercom-Version`**.                                                                                                   |
| **Idle teardown** (chat)    | [`autoplay_sdk.agent_states`](/sdk/agent-states) + [`autoplay_sdk.integrations.intercom`](#delete-conversation-idle-teardown) | **`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`](/sdk/typed-payloads) **`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

1. **Build context** — Instantiate **`ProactiveTriggerContext`** with chronological **`canonical_urls`** (and **`session_id`**, **`conversation_id`**, **`action_count`**, **`product_id`** when you have them).

2. **Evaluate** — **`result = registry.evaluate_first(ctx)`** using **`default_proactive_trigger_registry()`** or your own **`ProactiveTriggerRegistry([...])`**. If **`result`** is **`None`**, do not send.

3. **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](/sdk/agent-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`](/sdk/agent-states)** 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)](#delete-conversation-idle-teardown).

4. **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).

5. **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)

```python theme={null}
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](/sdk/proactive-triggers)** page. Wire a firing **`ProactiveTriggerResult`** into **`build_intercom_quick_reply_reply_payload`** as in the [recipe](#recipe-end-to-end) above.

### Adding your own trigger

1. Implement **`ProactiveTrigger`**: **`trigger_id`** and **`evaluate(ctx) -> ProactiveTriggerResult | None`**.
2. Optionally wrap with **`ProactiveTriggerEntity(inner, ProactiveTriggerTimings(...))`** for custom timeouts / cooldown.
3. Register on **`ProactiveTriggerRegistry`** in priority order; call **`evaluate_first`** in production.

### Optional: connector LLM-generated button labels

**`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`** ([Support agent writer](/sdk/support-agent-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 support agent writer page.

***

## Related

* [Support agent writer](/sdk/support-agent-writer) — `BaseChatbotWriter` contract and note format.
* [Agent context](/sdk/agent-context) — summariser + `overwrite_with_summary` flow.
* [Agent session states](/sdk/agent-states) — FSM gates when using the optional proactive connector endpoint with `conversation_id`.
