# How to Integrate Lovable with Intercom

- Tool: Lovable
- Difficulty: Intermediate
- Time required: 30 minutes
- Last updated: March 2026

## TL;DR

To integrate Intercom with Lovable, generate an access token from your Intercom Developer Hub app, store it in Cloud → Secrets, then build Edge Functions that proxy calls to the Intercom REST API for conversations, contacts, companies, and articles. Intercom's conversational-first approach makes it ideal for building in-app messaging features, user onboarding flows, and help center widgets inside Lovable apps.

## Add conversational messaging and help center features to Lovable with Intercom's API

Intercom pioneered the in-app messaging bubble that has become standard in modern SaaS products. Unlike Zendesk's ticket-queue approach, Intercom is designed for conversations — real-time messaging between users and support teams, triggered onboarding flows, and proactive outreach based on user behavior. For Lovable apps targeting paying SaaS customers, Intercom integration creates a native-feeling support experience directly embedded in your product.

Intercom's integration approach has two distinct layers. The first is the Messenger widget — a JavaScript-based chat bubble embedded in your app that handles real-time conversation UI natively. The second is the REST API — used for creating and updating contacts (Intercom's term for users), managing companies, fetching conversation history, and accessing the help center knowledge base. For Lovable apps, the Messenger widget handles real-time chat while Edge Functions handle the API operations that require server-side credentials.

The Intercom REST API v2 uses Bearer token authentication from a custom Developer Hub application. Unlike HubSpot's Private Apps, Intercom access tokens do not expire — they are permanent until you regenerate them. API scopes determine what the token can access: reading contacts, writing messages, managing companies, and so on. All API calls include the header 'Intercom-Version: 2.11' to target the current stable API version.

The most common Lovable + Intercom pattern is a hybrid: embed the Messenger widget for real-time chat, use Edge Functions to create or update Intercom contact records when users sign up or perform key actions, and query the Intercom Articles API to render help center content inside your app without redirecting users to a separate help site.

## Before you start

- A Lovable project with Lovable Cloud enabled (Edge Functions require a deployed app)
- An Intercom workspace (free trial available at intercom.com — note that API access requires a paid plan for production use)
- Access to create apps in Intercom Developer Hub (developer.intercom.com)
- Basic familiarity with Intercom concepts: contacts (users), companies, conversations, and the Messenger widget

## Step-by-step guide

### 1. Create an Intercom app in Developer Hub and get an access token

Intercom uses app-based authentication via its Developer Hub. Each integration requires creating a custom app in Developer Hub, which provides an access token scoped to the permissions you configure.

Go to developer.intercom.com and sign in with your Intercom account. Click 'New App' in the top right. Give your app a name (e.g., 'Lovable Integration') and select the workspace it should connect to. If you have multiple workspaces (e.g., development and production), create separate apps for each.

After creation, go to the 'Authentication' section of your new app. You will see an 'Access Token' — copy this immediately. Intercom access tokens are long strings starting with 'dG9r...' (base64 encoded). Unlike many OAuth2 services, Intercom access tokens do not expire automatically, so you do not need to implement token refresh logic.

Configure the appropriate API scopes for your integration. Click 'Permissions' in the left sidebar of your app settings. Enable:
- Read contacts (for fetching user data)
- Write contacts (for creating/updating contacts)
- Read conversations (for fetching support conversations)
- Write conversations (for sending messages)
- Read articles (for help center content)

Enable only the scopes you actually need. Overly permissive tokens create unnecessary security exposure.

Also note your Intercom App ID — a short alphanumeric string visible in your workspace URL or in the app settings. This is used for configuring the Messenger widget (not the API), but you will need it when adding the chat widget to your Lovable app. Store it as INTERCOM_APP_ID in secrets alongside the access token.

**Expected result:** An Intercom Developer Hub app is created with the appropriate API scopes. You have copied the access token and noted the App ID. Both are ready to be stored in Lovable Cloud → Secrets.

### 2. Store credentials in Cloud Secrets and configure the Messenger widget

Add Intercom credentials to Lovable's Cloud Secrets panel. In Lovable, click the '+' icon at the top to open the Cloud panel, navigate to Secrets, and add:

- INTERCOM_ACCESS_TOKEN — the access token from Developer Hub (the long dG9r... string)
- INTERCOM_APP_ID — your workspace App ID (short alphanumeric, used for the Messenger widget)

The App ID is different from the access token. It is the public identifier used to initialize the Messenger widget in your frontend code. It is safe to include in client-side code (it is not a secret) but storing it in secrets makes it easy to switch workspaces without code changes.

For the Messenger widget (the live chat bubble), Intercom provides a JavaScript snippet that initializes in the browser. In Lovable, you can initialize the Messenger by adding the Intercom script to your React app's main component. The Intercom object is added to window and initialized with your App ID. You can also pass user identity information (user_id, email, name) when initializing, which associates the conversation with the correct Intercom contact.

For user identity verification (a security feature that prevents users from impersonating each other in the Messenger), Intercom requires you to generate an HMAC signature server-side using your Identity Verification Secret. This signature is computed in an Edge Function and passed to the frontend at runtime — it should never be computed client-side.

```
// src/components/IntercomProvider.tsx
import { useEffect } from 'react'
import { useAuth } from '@/hooks/useAuth'

declare global {
  interface Window {
    Intercom: (...args: unknown[]) => void
    intercomSettings: Record<string, unknown>
  }
}

export function IntercomProvider({ children }: { children: React.ReactNode }) {
  const { user } = useAuth()
  const appId = import.meta.env.VITE_INTERCOM_APP_ID

  useEffect(() => {
    if (!appId) return

    // Load Intercom script
    const script = document.createElement('script')
    script.innerHTML = `
      (function(){var w=window;var ic=w.Intercom;
      if(typeof ic==="function"){ic('reattach_activator');ic('update',w.intercomSettings);}
      else{var d=document;var i=function(){i.c(arguments);};i.q=[];i.c=function(args){i.q.push(args);};
      w.Intercom=i;var l=function(){var s=d.createElement('script');
      s.type='text/javascript';s.async=true;
      s.src='https://widget.intercom.io/widget/${appId}';
      var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);};
      if(document.readyState==='complete'){l();}else if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})();
    `
    document.head.appendChild(script)

    return () => { document.head.removeChild(script) }
  }, [appId])

  useEffect(() => {
    if (!window.Intercom || !appId) return
    if (user) {
      window.Intercom('boot', {
        app_id: appId,
        user_id: user.id,
        email: user.email,
        name: user.user_metadata?.full_name || user.email,
      })
    } else {
      window.Intercom('boot', { app_id: appId })
    }
  }, [user, appId])

  return <>{children}</>
}
```

**Expected result:** The Intercom Messenger bubble appears in the bottom-right corner of your deployed Lovable app. When clicked, it opens a conversation window. Logged-in users are identified by their email in the Intercom inbox.

### 3. Create the Intercom REST API proxy Edge Function

Build the Edge Function that handles Intercom REST API operations: creating and updating contacts, fetching conversations, and querying help center articles. These server-side operations require the access token and cannot be performed from the browser.

Intercom API v2 uses consistent REST patterns. All requests include the 'Intercom-Version: 2.11' header to target a stable API version. Contacts are created via POST /contacts with a 'role' field ('user' for customers, 'lead' for anonymous visitors). The upsert pattern for contacts uses POST /contacts with an external_id that matches your app's user ID — if a contact with that external_id exists, it is updated; otherwise, a new contact is created.

Companies are created separately via POST /companies and linked to contacts via POST /contacts/{id}/companies. For SaaS products with account-based pricing, maintaining company records in Intercom enables segment-based messaging and company-level analytics.

For article search, the Articles API is at GET /articles with a ?phrase= search parameter. Articles can also be browsed by collection via GET /help_center/collections/{id}/articles.

Intercom uses cursor-based pagination with a 'pages' object in list responses containing 'next' and 'total_pages'. For small result sets (under 50 items), one page is usually sufficient. For larger datasets, implement cursor pagination by passing the 'starting_after' query parameter.

```
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

const INTERCOM_BASE = 'https://api.intercom.io'

function intercomHeaders() {
  const token = Deno.env.get('INTERCOM_ACCESS_TOKEN')
  if (!token) throw new Error('INTERCOM_ACCESS_TOKEN not configured')
  return {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json',
    'Intercom-Version': '2.11',
  }
}

serve(async (req) => {
  if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders })

  try {
    const { action, params = {} } = await req.json()
    const headers = intercomHeaders()
    let resp: Response

    switch (action) {
      case 'upsert_contact':
        resp = await fetch(`${INTERCOM_BASE}/contacts`, {
          method: 'POST',
          headers,
          body: JSON.stringify({
            role: params.role || 'user',
            external_id: params.userId,
            email: params.email,
            name: params.name,
            custom_attributes: params.customAttributes || {},
          }),
        })
        break

      case 'get_contact':
        resp = await fetch(`${INTERCOM_BASE}/contacts/${params.contactId}`, { headers })
        break

      case 'search_articles':
        resp = await fetch(`${INTERCOM_BASE}/articles?phrase=${encodeURIComponent(params.query || '')}&state=published`, { headers })
        break

      case 'send_message':
        resp = await fetch(`${INTERCOM_BASE}/messages`, {
          method: 'POST',
          headers,
          body: JSON.stringify({
            message_type: params.messageType || 'inapp',
            subject: params.subject || '',
            body: params.body,
            template: 'plain',
            from: { type: 'admin', id: params.adminId },
            to: { type: 'user', id: params.contactId },
          }),
        })
        break

      case 'list_conversations':
        resp = await fetch(`${INTERCOM_BASE}/conversations?contact_id=${params.contactId}&display_as=plaintext`, { headers })
        break

      default:
        return new Response(JSON.stringify({ error: 'Unknown action' }), {
          status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' },
        })
    }

    const data = await resp.json()
    return new Response(JSON.stringify(data), {
      status: resp.ok ? 200 : resp.status,
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    })
  } catch (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    })
  }
})
```

**Expected result:** The intercom-api Edge Function deploys in Cloud → Edge Functions. Test with action 'search_articles' and a query term — published articles from your Intercom help center should appear in the response.

## Best practices

- Always set external_id to your app's user ID when creating Intercom contacts — this is the key to deduplication and ensures Intercom can match users across multiple touch points.
- Use the Intercom Messenger widget for real-time conversations and the REST API via Edge Functions for programmatic operations — never try to do real-time messaging through API polling.
- Store the Intercom contact ID in your Supabase users table after the first upsert so future operations use direct contact IDs instead of search queries.
- Implement Intercom Identity Verification (HMAC signature) for production apps — it prevents users from impersonating each other in the Messenger by requiring a server-generated signature.
- Use custom_attributes sparingly and name them clearly — Intercom limits the number of custom attributes and poorly named attributes clutter your contact records for your support team.
- Trigger contact upserts on significant user events (signup, upgrade, feature activation) rather than on every page view to avoid unnecessary API calls and rate limiting.
- Always include the 'Intercom-Version: 2.11' header in API requests — without it, Intercom uses an older default API version that may have different response shapes.

## Use cases

### Sync new user signups to Intercom as contacts with custom attributes

When a new user registers in your Lovable app, create or update their Intercom contact record with custom attributes like plan, signup source, company size, and feature flags. This gives your Intercom inbox rich context about each user, enabling personalized support messages and automated onboarding sequences triggered by contact attributes. Contacts created via API are immediately visible to your support team in the Intercom inbox.

Prompt example:

```
Create an Edge Function called intercom-contact that accepts { userId, email, name, company, plan, signupSource } and creates or updates an Intercom contact using INTERCOM_ACCESS_TOKEN from secrets. Set custom attributes for plan and signupSource. Use the upsert pattern (POST to contacts with role: 'user'). Return the contact ID. Call this from my registration success handler alongside the Supabase user creation.
```

### Render Intercom help center articles inside your Lovable app

Instead of redirecting users to a separate help.yourcompany.com site, fetch Intercom Articles directly into your Lovable app. An Edge Function queries the Intercom Articles API for articles matching a search term or category, and a React component renders the content inline. Users get contextual help without leaving your product, and you maintain one source of truth in Intercom for all help content.

Prompt example:

```
Create an Edge Function called intercom-articles that accepts { query, collectionId } and searches Intercom help center articles using INTERCOM_ACCESS_TOKEN from secrets. Return article id, title, body (HTML), and url. Then build a Help Center component in Lovable with a search bar and article list. When a user clicks an article, render the body content in a modal with the article title as the heading.
```

### Proactive in-app message when users hit a usage limit

When a user hits a usage limit in your Lovable app (API quota, storage full, feature gated), automatically send them a proactive Intercom message via the API offering to upgrade or explaining next steps. The Edge Function creates an in-app message directed at the user's contact ID with a call-to-action linking to your pricing page or upgrade flow.

Prompt example:

```
Create an Edge Function called intercom-message that accepts { contactId, messageType, body, ctaText, ctaUrl } and sends an in-app message to an Intercom contact using INTERCOM_ACCESS_TOKEN from secrets. Use the admin-initiated conversation endpoint. Return the conversation ID. Call this function when a user's storage hits 90% capacity, sending a message about upgrading their plan.
```

## Troubleshooting

### Intercom API returns 401 Unauthorized on all requests

Cause: The access token is missing, expired, or was created for a different Intercom workspace than the one you are targeting.

Solution: In Cloud → Secrets, verify INTERCOM_ACCESS_TOKEN is present and correct. In Intercom Developer Hub, check the app's 'Authentication' section to confirm the token matches. Also verify the app is connected to the correct workspace — if you have multiple Intercom workspaces, each requires its own Developer Hub app and token.

### Intercom Messenger widget does not appear on the page after adding the script

Cause: The Intercom app is not correctly initialized, or the VITE_INTERCOM_APP_ID environment variable is not available in the browser bundle.

Solution: Ensure VITE_INTERCOM_APP_ID is set as a build-time environment variable (not in Cloud Secrets, which are server-only). In Lovable, this should be configured as a regular environment variable accessible via import.meta.env.VITE_INTERCOM_APP_ID. Also verify the boot call includes the app_id field — Intercom will silently fail to show the widget if app_id is missing or undefined.

### Intercom returns 'User not found' when trying to send a message or fetch conversations

Cause: The contact ID used in the request does not exist in the Intercom workspace, or the contact was created in a different workspace.

Solution: Contact IDs in Intercom are workspace-specific. Always upsert the contact using your app's user ID as external_id before performing operations, and use the Intercom contact ID from the upsert response for subsequent calls. Store the Intercom contact ID in your Supabase users table to avoid repeated lookups.

### Duplicate contacts are being created in Intercom for the same user

Cause: Contacts are being created without a consistent external_id, so Intercom cannot match new contacts to existing ones.

Solution: Always include the external_id field in contact creation, set to your app's internal user ID. Intercom uses external_id as the deduplication key when upserting. Without it, each API call creates a new contact even for the same email address. If duplicates already exist, use the Intercom dashboard's 'Merge contacts' feature to consolidate them.

```
// Always include external_id in contact creation:
body: JSON.stringify({
  role: 'user',
  external_id: params.userId,  // required for deduplication
  email: params.email,
  name: params.name,
})
```

## Frequently asked questions

### Can I use Intercom for free with Lovable?

Intercom offers a 14-day free trial and a free plan for early-stage startups (less than $1M in funding with fewer than 5 team seats — apply at intercom.com/early-stage). The standard paid plans start at $74/month for the Essential plan. API access is available on all plans. For most Lovable integrations, the Messenger widget and basic contact management work within the free trial period while you evaluate fit.

### What is the difference between Intercom contacts, leads, and users?

In Intercom's current data model (API v2), all individuals are 'contacts' with a 'role' property: 'user' for known, logged-in customers and 'lead' for anonymous visitors. Older Intercom documentation uses the terms 'users' and 'leads' as separate endpoints — these were merged in API v2. When creating contacts from your Lovable app for known users, always use role: 'user' with their email and external_id.

### How do I prevent duplicate Intercom contacts from multiple sign-up paths?

The key is always passing an external_id set to your app's internal user ID when creating contacts. Intercom treats external_id as the deduplication key — if a contact with the same external_id exists, it is updated rather than duplicated. Never create contacts without external_id set, and use the upsert endpoint (POST /contacts) rather than checking for existence first.

### Can I embed Intercom help center articles inside my Lovable app without iframes?

Yes — the Intercom Articles API returns article content as HTML body text, which you can render directly in your React components. Fetch articles via your Edge Function's search_articles action and render the HTML content using React's dangerouslySetInnerHTML, or use a sanitization library like DOMPurify to clean the HTML before rendering. This gives you fully embedded help content styled to match your app's design, with no iframe required.

### How do I handle Intercom Identity Verification for security?

Intercom Identity Verification prevents users from changing their user_id or email in the browser to impersonate other users. Enable it in your Intercom app settings (Settings → Installation → Identity Verification). Then create an Edge Function that accepts your app's user ID and returns an HMAC-SHA256 signature computed using your Identity Verification Secret (stored in Cloud → Secrets). Pass this signature to the Intercom Messenger initialization as 'user_hash'. For complex security setups, RapidDev's team can help implement the full verification flow.

---

Source: https://www.rapidevelopers.com/lovable-integration/intercom
© RapidDev — https://www.rapidevelopers.com/lovable-integration/intercom
