Skip to content

Overview

Please reach out to your accounts team to discuss access.

API version

The Fin Agent API is available on API version 2.14 and above. Make sure your integration uses version 2.14 or later.

Overview

Fin can be accessed programmatically via an API. Integration is centered around two endpoints (/fin/start and /fin/reply) and a set of events that notify the client of Fin's status and responses. Events can be delivered via webhooks or Server-Sent Events (SSE).

Once Fin is initialized, it progresses through a series of statuses such as thinking, replying, awaiting_user_reply, or resolved before ending its workflow with a status of complete . During this workflow, the client should allow Fin to continue uninterrupted until a final complete status is returned, at which point control of the conversation passes back to the client.

Start a conversation with Fin

Endpoint: /fin/start

Initialise Fin by passing it the user's message along with some conversation history and user details. These additional pieces of context will be used by Fin to provide a better and more contextual answer to the user.

Request & Response

Request Payload

KEY TYPE Required DESCRIPTION
messageMessagetrueThe message that the user is sending to Fin.
conversation_idStringtrueThe ID of the conversation that is calling Fin via this API.
userObject<User>trueA user object that identifies the user making the query.
attachmentsArray<Attachment>falseAn array of attachments to include with the message. Note: Maximum of 10 attachments.
conversation_metadataObjectfalseMetadata about the conversation, including history and attributes.
conversation_metadata.historyArray<Message>falseAn array of previous messages of the conversation before Fin is initialised. This data provides context to Fin on the history of the conversation and helps generate a better answer. Note: Limit to the last 10 messages.
conversation_metadata.attributesHash<String,Object>falseA Hash of attributes associated with the conversation. These attributes can be used by Fin to provide more contextual responses. Note: Limit to 10 attributes.

Request Payload Example

{
  "conversation_id": "ext-123",
  "message": {
    "author": "user",
    "body": "How can I see my account details?",
    "timestamp": "2025-01-24T10:01:20.000Z"
  },
  "user": {
    "id": "123456",
    "name": "John Doe",
    "email": "john.doe@example.com",
    "attributes": {
      "plan_type": "Pro",
      "subscription_status": "active"
    }
  },
  "attachments": [
    {
      "type": "url",
      "url": "https://example.com/document.pdf"
    },
    {
      "type": "file",
      "name": "screenshot.png",
      "content_type": "image/png",
      "data": "<base64 encoded file data>"
    }
  ],
  "conversation_metadata": {
    "history": [
      {
        "author": "user",
        "body": "I need help",
        "timestamp": "2025-01-24T10:00:01Z"
      },
      {
        "author": "agent",
        "body": "What do you need help with?",
        "timestamp": "2025-01-24T10:01:00Z"
      }
    ],
    "attributes": {
      "priority_level": "high",
      "department": "sales"
    }
  }
}

Response

KEY TYPE DESCRIPTION
conversation_idStringThe ID of the conversation.
user_idStringThe ID of the user.
statusEnum<String>Fin's status.
created_at_msStringThe timestamp the response was created at, with millisecond precision. Example: 2025-01-24T10:00:00.123Z
sse_subscription_urlStringOptional. A URL to subscribe to SSE events for this conversation, if SSE is enabled. Includes rewind=2m to replay recent events. The JWT token expires after 3 minutes and is revoked when Fin sets the conversation to awaiting_user_reply or complete.
errorsObjectOptional. Contains error details if any user or conversation attribute updates failed.
errors.user.attributesHash<String,String>Optional. Map of user attribute names to error messages.
errors.conversation.attributesHash<String,String>Optional. Map of conversation attribute names to error messages.

Response Example

{
  "conversation_id": "ext-123",
  "user_id": "user-456",
  "status": "thinking",
  "created_at_ms": "2025-01-24T10:00:00.123Z",
  "sse_subscription_url": "https://primary-realtime.intercom-messenger.com/event-stream?channels=fin_agent_api:app123:ext-123&accessToken=eyJhbG...&rewind=2m",
  "errors": {
    "user": {
      "attributes": {
        "invalid_attr": "User attribute 'invalid_attr' does not exist"
      }
    },
    "conversation": {
      "attributes": {
        "bad_attr": "Conversation attribute 'bad_attr' does not exist",
        "priority_level": "'1234' is not a valid value for attribute 'priority_level' of type 'string'"
      }
    }
  }
}

Reply to Fin

Endpoint: /fin/reply

Once Fin has returned a response to a user's message, its status will be awaiting_user_reply. If a user replies, use this endpoint to send this response to Fin.

Request & Response

Request Payload

KEY TYPE Required DESCRIPTION
messageMessagetrueThe user's message.
conversation_idStringtrueThe ID of the conversation.
userObject<User>trueA user object that identifies the user replying to Fin.
attachmentsArray<Attachment>falseAn array of attachments to include with the message. Note: Maximum of 10 attachments.

Request Payload Example

{
  "message": {
    "author": "user",
    "body": "Here's the information you requested.",
    "timestamp": "2025-01-24T09:01:00.000Z"
  },
  "conversation_id": "123456",
  "user": {
    "id": "123456",
    "name": "John Doe",
    "email": "john.doe@example.com",
    "attributes": {
      "plan_type": "Pro",
      "subscription_status": "active"
    }
  },
  "attachments": [
    {
      "type": "url",
      "url": "https://example.com/invoice.pdf"
    }
  ]
}

Response

KEY TYPE DESCRIPTION
conversation_idStringThe ID of the conversation.
user_idStringThe ID of the user.
statusEnum<String>Fin's status.
created_at_msStringThe timestamp the response was created at, with millisecond precision. Example: 2025-01-24T10:00:00.123Z
sse_subscription_urlStringOptional. A URL to subscribe to SSE events for this conversation, if SSE is enabled. The JWT token expires after 3 minutes and is revoked when Fin sets the conversation to awaiting_user_reply or complete.
errorsObjectOptional. Contains error details if any user attribute updates failed.
errors.user.attributesHash<String,String>Optional. Map of user attribute names to error messages.

Response Example

{
  "conversation_id": "ext-123",
  "user_id": "user-456",
  "status": "thinking",
  "created_at_ms": "2025-01-24T10:00:00.123Z",
  "sse_subscription_url": "https://primary-realtime.intercom-messenger.com/event-stream?channels=fin_agent_api:app123:ext-123&accessToken=eyJhbG...",
  "errors": {
    "user": {
      "attributes": {
        "invalid_attr": "User attribute 'invalid_attr' does not exist"
      }
    }
  }
}

Listening to Events

Fin fires events during its workflow to convey its status and deliver replies to the user. These events can be received via Webhooks, SSE, or both — for example, using SSE for low-latency UI rendering with webhooks as a reliable fallback.

Both delivery mechanisms provide the same event types with identical schemas.

Webhooks

A webhook endpoint can be configured in the Fin Agent API settings. The client can then listen for Fin events sent to this webhook and handle them accordingly.

All webhook requests will include an X-Fin-Agent-API-Webhook-Signature header containing an HMAC-SHA256 signature of the request body. Validation can be done by generating a signature using the request body and the signing secret from the settings, and comparing it with the aforementioned header value.

SSE (Server-Sent Events)

You can receive events via Server-Sent Events (SSE) using the sse_subscription_url returned in the /fin/start and /fin/reply responses.

How it works

  1. Call /fin/start or /fin/reply as normal.
  2. The response includes an sse_subscription_url field.
  3. Open an SSE connection to this URL using EventSource or any HTTP client that supports SSE.
  4. Receive fin_replied and fin_status_updated events in real time over the SSE stream.

URL parameters

The sse_subscription_url follows this format:

https://primary-realtime.intercom-messenger.com/event-stream?channels={channel}&accessToken={token}&rewind={duration}
PARAMETER DESCRIPTION
channelsThe SSE channel to subscribe to, in the format fin_agent_api:{app_id}:{conversation_id}.
accessTokenA JWT (JSON Web Token) that authenticates the SSE connection. Expires after 3 minutes and is revoked when Fin sets the conversation to awaiting_user_reply or complete.
rewindOnly present on /fin/start responses. Replays events from the specified duration (e.g., 2m for 2 minutes) to prevent missed events during initial connection.

The rewind parameter

The SSE URL returned from /fin/start includes a rewind=2m query parameter. This instructs the SSE provider to replay any events from the last 2 minutes when the client connects, preventing missed events between the time the HTTP response is received and the SSE connection is established.

The /fin/reply endpoint does not include the rewind parameter, since the client is expected to already have an active SSE connection at that point.

Why rewind is only on /start

When a conversation is first started, there is a gap between receiving the HTTP response and opening the SSE connection. The rewind parameter ensures no events are lost during this window. For subsequent /fin/reply calls, the SSE connection should already be open.

Token expiration and revocation

  • SSE access tokens expire after 3 minutes.
  • The token is revoked immediately when Fin sets the conversation to awaiting_user_reply or complete. The awaiting_user_reply revocation occurs when Fin finishes replying; the complete revocation covers flows where the conversation ends without a reply cycle (e.g., immediate escalation).
  • After receiving awaiting_user_reply, the SSE connection will close. If the user replies, call /fin/reply to obtain a new sse_subscription_url with a fresh token.
Token lifetime

Do not cache or reuse sse_subscription_url values across multiple interactions. Each URL contains a short-lived token scoped to a single Fin response cycle.

Example: Connecting to SSE

// After calling /fin/start
const response = await fetch('https://api.intercom.io/fin/start', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer <YOUR_ACCESS_TOKEN>',
    'Content-Type': 'application/json',
    'Intercom-Version': '2.15'
  },
  body: JSON.stringify({ /* request payload */ })
});

const data = await response.json();

// Connect to SSE using the subscription URL
const eventSource = new EventSource(data.sse_subscription_url);

eventSource.onmessage = (event) => {
  const eventData = JSON.parse(event.data);

  if (eventData.event_name === 'fin_replied') {
    // Handle Fin's reply
    console.log('Fin replied:', eventData.message.body);
  }

  if (eventData.event_name === 'fin_status_updated') {
    console.log('Status updated:', eventData.status);

    if (eventData.status === 'awaiting_user_reply') {
      // Fin is done replying — close the SSE connection
      eventSource.close();
    }
  }
};

eventSource.onerror = (error) => {
  console.error('SSE connection error:', error);
  eventSource.close();
};

Event Types

Status Update

Fin will report its status to the client via this event.

KEY TYPE DESCRIPTION
event_nameStringThe name of the event. Value: fin_status_updated
conversation_idStringThe ID of the conversation.
user_idStringThe ID of the user.
statusEnum<String>Fin's current status. Supported values: escalated resolved complete awaiting_user_reply
reasonStringOptional. A human-readable explanation of why the conversation was escalated. Only present when status is escalated. See Escalation Reasons for possible values.
created_at_msStringThe timestamp the event was created at, with millisecond precision. Example: 2025-01-24T10:00:00.123Z

Status Update Example (Escalated)

{
  "event_name": "fin_status_updated",
  "conversation_id": "123456",
  "user_id": "7891",
  "status": "escalated",
  "reason": "Escalation requested by user",
  "created_at_ms": "2025-01-24T10:00:00.123Z"
}

Status Update Example (Resolved)

{
  "event_name": "fin_status_updated",
  "conversation_id": "123456",
  "user_id": "7891",
  "status": "resolved",
  "created_at_ms": "2025-01-24T10:00:00.123Z"
}

Status Update Example (Awaiting User Reply)

{
  "event_name": "fin_status_updated",
  "conversation_id": "123456",
  "user_id": "7891",
  "status": "awaiting_user_reply",
  "created_at_ms": "2025-01-24T10:00:00.123Z"
}
Done signal

The awaiting_user_reply status on the fin_status_updated event serves as a "done" signal indicating that Fin has finished sending all reply parts. Use this event to know when Fin's response is complete and the user can reply.

Fin Reply

Fin will reply to a user via this event. The content of the response will be contained in the Message object. Intermediate reply events have a status of replying, indicating that more reply parts may follow. When Fin has finished replying, a separate fin_status_updated event with status awaiting_user_reply is fired as a done signal.

Preview API version required

The replying status for intermediate fin_replied events is currently only available in the Preview API version. In stable versions, all fin_replied events use awaiting_user_reply as the status. See the Preview changelog for details.

KEY TYPE DESCRIPTION
event_nameStringThe name of the event. Value: fin_replied
conversation_idStringThe ID of the conversation.
user_idStringThe ID of the user.
messageMessageFin's answer to the user's query. Includes timestamp_ms for millisecond precision timing.
statusEnum<String>Fin's current status. Values: replying (intermediate reply) awaiting_user_reply (legacy; see done signal above)
stream_idStringOptional. Present when the reply was generated via streaming. Correlates this event with the fin_reply_chunk events that preceded it — use it to know when to replace streamed text with the final HTML body.
created_at_msStringThe timestamp the event was created at, with millisecond precision. Example: 2025-01-24T10:00:00.123Z

Fin Reply Example (Intermediate)

{
  "event_name": "fin_replied",
  "conversation_id": "123456",
  "user_id": "7891",
  "message": {
    "author": "fin",
    "body": "<p>You can see your account details by clicking on the <em>Account</em> tab in the top right corner of the screen.</p>",
    "timestamp_ms": "2025-01-24T09:01:00.456Z"
  },
  "status": "replying",
  "created_at_ms": "2025-01-24T10:00:00.123Z"
}
Listening for the done signal

After receiving one or more fin_replied events with status: replying, wait for a fin_status_updated event with status: awaiting_user_reply before prompting the user to reply. This ensures all of Fin's reply parts have been delivered.

Fin Reply Chunk

When SSE is enabled with streaming, Fin will emit fin_reply_chunk events during reply generation. Each chunk contains the full accumulated answer text so far — not a delta. This allows progressive rendering as Fin types.

SSE with streaming only

fin_reply_chunk events are only delivered over SSE when streaming is enabled. They are not available via webhooks.

KEY TYPE DESCRIPTION
event_nameStringThe name of the event. Value: fin_reply_chunk
conversation_idStringThe ID of the conversation.
stream_idStringA unique identifier for this streaming response. Correlates chunks with each other and with the eventual fin_replied event.
chunk_indexInteger0-based counter for this chunk within the stream. Contiguous — no gaps even if tokens are sampled or duplicates skipped.
chunk_textStringThe full accumulated plain text of Fin's answer so far. Each chunk supersedes the previous — replace rather than append.
statusEnum<String>Fin's current status. Value: replying (Preview) or awaiting_user_reply (v2.14/v2.15)
created_at_msStringThe timestamp the event was created at, with millisecond precision. Example: 2025-01-24T10:00:00.123Z

Fin Reply Chunk Example

{
  "event_name": "fin_reply_chunk",
  "conversation_id": "123456",
  "stream_id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
  "chunk_index": 3,
  "chunk_text": "You can see your account details by clicking",
  "status": "replying",
  "created_at_ms": "2025-01-24T10:00:00.123Z"
}
Streaming flow

Accumulate fin_reply_chunk events keyed by stream_id for progressive rendering. When the fin_replied event arrives with the same stream_id, replace your streamed text with the final HTML in message.body. Discard any late-arriving chunks for that stream_id.

Fin's Statuses

During a conversation, Fin goes through a number of statuses as it progresses through a workflow. The client that is calling Fin will need to listen to these statuses via Webhooks or SSE and react accordingly.

Statuses

STATUS DESCRIPTION
thinkingFin is currently thinking about a response. When Fin is thinking, the client should consider indicating to the user that Fin is working on an answer.
replyingFin is sending reply parts. Each fin_replied event with this status contains a part of Fin's answer. Wait for the fin_status_updated event with awaiting_user_reply before prompting the user to reply.
awaiting_user_replyFin has finished replying and is waiting for the user to respond. This status is delivered via the fin_status_updated event as a done signal after all fin_replied events.
escalatedThe conversation has been escalated by the user or Fin. This status means that the Fin has determined or the user has requested that the conversation be escalated to a human. The client should respond accordingly.
resolvedThe user's query has been resolved. The user has indicated to Fin that it has successfully answered their query.
completeFin has completed its workflow and the conversation is over. Control of the conversation is now back with the client.

Escalation Reasons

When Fin escalates a conversation and reports an escalated status via the fin_status_updated event, it includes a reason field that provides context for why the conversation was escalated. This helps you understand the path the conversation took and respond appropriately.

The following reasons may be provided:

REASON DESCRIPTION
Escalation requested by userThe user explicitly requested to speak with a human agent during the conversation.
Escalation rule: {rule_name}An escalation rule was triggered based on conversation attributes or conditions. The {rule_name} will be replaced with the actual name of the matched rule.
Escalation rule matchedA generic escalation rule was triggered, but no specific rule name is available.
Routed to teamThe conversation was routed to a specific team based on your routing configuration.
Conversation finished without resolutionThe conversation ended without being resolved or explicitly escalated through one of the above paths.

Note: The reason field is optional and may not be present in all escalated status update events. Legacy implementations or certain edge cases may not include this field.

Authentication

All requests to this API require an API key in the HTTP header. You can obtain an API key after completing the Fin Agent API setup.

For general authentication guidelines, see Intercom authentication.

Keep your API key secure

Your API key grants access to the Fin Agent API on behalf of your workspace. Do not share it publicly or expose it in client-side code.

OAuth Scopes

The Fin Agent API access token is created with the write_conversations OAuth scope, which provides the minimum permissions needed for Fin Agent API operations. This follows security best practices by limiting what an access token can do if compromised.

For more information about OAuth scopes and token security, see the OAuth Scopes & Permissions section in the setup guide.

User Handling

When you provide a User object to the Fin Agent API, the following behavior applies:

  • User role only: The Fin Agent API only supports the User role. Leads and Visitors are not supported.

  • User lookup: The id field is used to look up existing users in Intercom. This value maps to the user_id field on the Intercom User object.

  • User creation: If no user is found with the provided id, a new user will be created with the given parameters.

  • Email updates: If an email is provided and differs from the existing user's email, the user's email will be updated.

  • Attribute updates: If attributes are provided, they will be merged with the user's existing attributes. New attributes are added and existing attributes with the same key are overwritten.

    User data update considerations

    If your users interact with Intercom across multiple channels (e.g., Messenger and the Fin Agent API), updating the email or attributes via this API will affect the user record across all channels. Ensure that the data you provide is authoritative and consistent with your other integrations to avoid unintended changes to user data.

Example

Below is a basic example of the flow of API requests and events when a client passes a message to Fin.

Flow

When to use SSE?

SSE and webhooks serve different purposes and can be used independently or together. Choose based on your integration's needs:

USE CASE RECOMMENDED DELIVERYWHY
Frontend chat UI with real-time typingSSEReduces time-to-first-token (TTFT) by streaming chunks progressively as Fin generates its reply. Users see text appear immediately rather than waiting for the full response.
Backend processing and persistenceWebhooksWebhooks guarantee delivery of all events including those that occur when no SSE connection is active (e.g., inactivity messages). They are the reliable choice for persisting conversation state to your database.
Production frontend + backendSSE & WebhooksUse SSE for real-time rendering on the frontend and webhooks for durable backend processing. This gives you the best of both worlds.
Server-to-server integration (no UI)WebhooksIf there is no frontend to render streaming text, webhooks provide complete event delivery without the overhead of maintaining an SSE connection.
SSE + Webhooks together

You can enable both SSE and webhooks simultaneously. This is the recommended setup for production integrations — stream to the frontend for a responsive user experience while relying on webhooks to capture every event on the backend.

Best practices

Streaming content model

  • Chunks are cumulative plain text — Each fin_reply_chunk contains the full accumulated answer text so far, not a delta. You can render the latest chunk directly — no need to concatenate fragments. If you miss a chunk, the next one contains everything. Note that fin_reply_chunk events contain plain text only — rich content such as images is not included in chunks and will only appear in the final fin_replied event.
  • Swap to rich text on completion — When Fin completes its reply, the fin_replied event delivers the final answer as rich text (HTML with formatting, sources, etc.). The recommended strategy: stream fin_reply_chunk events for real-time display, then replace the streamed text with the fin_replied rich text for the final rendered view.
  • Use stream_id to correlate chunks and replies — Each reply stream has a unique stream_id. Use it to match fin_reply_chunk events to the corresponding fin_replied event, especially when handling multiple reply parts.

Token lifecycle

  • Tokens are short-lived — SSE access tokens expire after 3 minutes and are revoked when Fin finishes replying (awaiting_user_reply or complete status).
  • Use /fin/reply for fresh tokens — After a token is revoked, call the /fin/reply endpoint when the user responds. Each response includes a new sse_subscription_url with a fresh token — do not cache or reuse old URLs.

Webhooks and persistence

  • Persist conversations for page refreshes — SSE is ephemeral. Clients must persist conversation state locally or via their backend and rely on webhooks for updates that arrive while no SSE connection is active (e.g., after a page refresh or reconnection).
  • Inactivity messages arrive via webhook only — If Fin times out waiting for a user reply, the inactivity message will not arrive over SSE (the connection will likely have closed by then). Rely on webhooks to capture it.

Handling Fin's replies

  • fin_reply_chunk — Use for progressive rendering. Each chunk contains the cumulative plain-text answer so far with a replying status.
  • fin_replied — Replace the streamed text with the fin_replied event's rich-text body for the final rendered view. Use stream_id to match it to the preceding chunks.
  • fin_status_updated — A follow-up fin_status_updated event is fired after Fin's replies to indicate the conversation has moved to awaiting_user_reply, resolved, or complete status. Use this as the "done" signal.

API version

Use a stable API version (e.g., 2.14, 2.15) for production integrations. The Preview version may include experimental changes that are modified or removed before reaching a stable release. See Update your API version for guidance on choosing and upgrading versions.

HTML in Fin replies

The message.body field in fin_replied events contains server-rendered HTML. The specific elements that appear depend on the knowledge ingested into Fin — for example, if your help center articles contain tables or code blocks, Fin may include those elements in its responses.

The following HTML elements may appear in message.body:

TagWhat it doesDetails
<p>ParagraphPrimary text container.
<h1> <h2> <h3> <h4>Headings
<b> <strong> <i> <em>Text formattingBold and italic.
<a>LinkAttributes: href, target. Also used for inline citations (see below).
<ol> <ul> <li>ListsOrdered and unordered. Can be nested.
<pre> <code>Code<pre> wraps <code> for code blocks. Inline <code> for inline snippets.
<img>ImageAttributes: src, alt, width, height. Wrapped in a <div> container.
<table> <tbody> <tr> <td> <th>TableWrapped in a <div>. Cells may have colspan, rowspan.
<div>ContainerWraps images, tables, and callout blocks.
<br>Line break
<hr>Horizontal rule
Forward compatibility

Over time we may add support for new HTML elements in message.body. This will not constitute a breaking change. We advise client code to gracefully handle unknown HTML elements when rendering Fin's responses.