Skip to content
Browse the documentation
Connectors

Intercom

What the Intercom connector reads, with which permissions, what it never touches, how it syncs, and the facts it produces.

Last changed Markdown versionllms.txt

Source module: app/connectors/intercom.py. Kind: both (claims and facts). Display name: "Intercom (Fin)".

Purpose

Pulls Fin AI Agent outcomes as claims (one resolution claim per conversation Fin marks as resolved) and the conversation record as facts (customer messages, human replies, assignments, closes/reopens, conversation ratings) so that the intercom_fin_resolution and intercom_fin_procedure_handoff contracts can be evaluated.

Auth and scopes

Item Value
Method Access token (Intercom OAuth app token or workspace access token), sent as Authorization: Bearer; API version header Intercom-Version: 2.11
Credentials stored (encrypted) access_token; optional base_url (defaults to https://api.intercom.io)
Least-privilege scopes Read conversations, Read contacts, Read admins, Read conversation ratings. No write scopes.
Connection test GET /me (workspace name is recorded in health)
Non-secret config contract_id (default intercom_fin_resolution), unit_price (default 0.99)

Fields pulled and why

Field Used for
conversation.id, created_at, updated_at Subject id, opening message timestamp, incremental watermark
conversation.state (read; not currently stored)
conversation.source.type, source.delivered_as Channel (email when the source type is email, otherwise delivered_as or chat) for channel exclusions
conversation.ai_agent.resolution_state, resolved_at, last_answer_type Whether Fin counted a resolution (claim), and its timestamp
conversation_parts[].part_type, author.type, assigned_to.type, created_at, id Fact classification: customer message vs human reply vs bot message, escalation, close, reopen
conversation_rating.rating, created_at CSAT fact (1–5 scale)
contacts.contacts[0].email HMAC-hashed into customer_hid for cross-source identity resolution
Assignee (via assignment parts) Escalation detection

Conversations are fetched with display_as=plaintext when parts are missing from the search result, so that part authorship can be read; message bodies are not stored.

What is hashed / never stored

  • Contact email → HMAC-SHA256 with the tenant key → attributes.customer_hid. The email itself is discarded.
  • Message bodies, contact names, phone numbers, custom attributes, tags, and any other conversation content are not persisted.
  • The access token is stored only as AES-256-GCM ciphertext.

Incremental sync and watermark

  • POST /conversations/search with updated_at > <watermark as epoch seconds> (0 on first sync), per_page: 150, cursor pagination via pages.next.starting_after.
  • Each conversation is fully ingested on every appearance; upserts are idempotent (claim_id / fact_id derived from sha256(tenant_id | raw_ref)), so re-syncs update rather than duplicate.
  • Watermark = the maximum updated_at seen, stored as connections.watermark; the next sync starts from it. A manual backfill (POST /v1/connections/{id}/sync {"backfill_days": N}, max 400) temporarily moves the start back. The initial sync after credentials are saved backfills 90 days.

Rate-limit handling

Shared HttpClient: on 429 or any 5xx it waits Retry-After if present, otherwise min(60, 2^attempt) seconds, up to 6 attempts, then fails the sync with gave up after retries. 401/403 fail immediately with reauth = true (see RUNBOOKS.md → Connector auth failure). Request count is recorded in health.requests.

Fact types produced

Subject kind: conversation. Every fact carries customer_hid and channel in attributes.

fact_type When Extra attributes raw_ref
customer_message Conversation source message; any comment part authored by user, lead or contact part: "source" for the opener intercom:conversation:{id}:source, intercom:part:{id}:{part_id}
human_reply comment part authored by admin author: "admin" intercom:part:…
agent_message comment part authored by bot author: "fin" intercom:part:…
escalation assignment / assign_and_reopen part assigning to an admin to: "human" intercom:part:…
status_change close part → `from: , to: closed, actor: customer bot agent; open/reopen/assign_and_reopenpart →from: closed, to: open, actor: `
reopen open / reopen / assign_and_reopen part by: <author type> intercom:part:…
csat_response conversation_rating.rating present score (1–5), scale: 5, sentiment (≥4 positive, ≤2 negative, else neutral) intercom:rating:{id}
customer_confirmation ai_agent.resolution_state == confirmed_resolution method: "fin_confirmed" intercom:fin_confirmation:{id}

Claims produced

Condition Claim
ai_agent.resolution_state in confirmed_resolution, assumed_resolution, resolved outcome_type: resolution, claimed_at = ai_agent.resolved_at (falls back to updated_at), unit_price from config, channel, attributes: {resolution_state, customer_hid}, raw_ref: intercom:fin_outcome:{id}, contract_id from config

Subject id format

intercom_conv_{conversation_id} for both claims and facts.

Known limitations

  • Only resolution claims are produced. Procedure-handoff outcomes (intercom_fin_procedure_handoff) must currently be supplied via CSV upload.
  • conversation_rating.created_at may be absent; the connector then uses the conversation's updated_at as the rating timestamp, which can be later than the actual rating.
  • The connector does not store message text, so transcript assist cannot use Intercom conversations unless the tenant also uploads a CSV with keep_text.
  • Assignment to a team (not an admin) is not treated as an escalation.
  • Fin's own "returned for further assistance" deduction is not read from Intercom; reopen facts are evaluated by the contract instead.
  • Voice channels are not distinguished beyond source.delivered_as; the library contract excludes voice/phone by channel name.