To integrate Replit with Intercom, generate an Intercom Access Token from your Developer Hub app, store it in Replit Secrets (lock icon in sidebar), then call the Intercom REST API to manage contacts, conversations, and messages. For real-time events, register a webhook endpoint on your deployed Replit app URL to receive customer activity notifications.
Why Connect Replit to Intercom?
Intercom sits at the intersection of customer support, sales chat, and product communication. Connecting your Replit application to Intercom unlocks the ability to programmatically create and update contacts when users sign up in your app, trigger automated messages based on user behavior in your product, sync conversation data to other systems, and build custom integrations that Intercom's native workflow tools cannot handle alone.
The most common integration pattern is a contact sync pipeline: when a user registers in your Replit app, you call the Intercom API to create or update a contact record with their name, email, and custom attributes (like plan type, company size, or feature usage). This ensures your support team always has up-to-date context when a customer starts a conversation. You can also tag contacts automatically, assign conversations to specific team inboxes based on business rules, and send targeted messages to specific user segments.
Webhooks make the integration bidirectional β when a customer sends a message or closes a conversation in Intercom, your Replit app receives an event payload and can trigger actions like updating a CRM record, sending a Slack notification to your team, or logging the interaction to a database. This tutorial covers both directions: pushing data into Intercom and receiving events from Intercom in real time.
Integration method
The Replit-Intercom integration works by authenticating with an Intercom Access Token stored in Replit Secrets, then calling the Intercom REST API from your backend server to create or update contacts, send messages, retrieve conversations, and manage inbox assignments. For event-driven workflows, you register a webhook in the Intercom Developer Hub pointing to your deployed Replit app URL, which then receives real-time POSTs whenever customers start conversations, reply, or trigger other actions.
Prerequisites
- An Intercom account (a free trial or paid workspace works)
- A Replit account with a Node.js or Python Repl created
- Basic familiarity with REST APIs and HTTP requests
- A deployed Replit app (for webhook endpoints β use Autoscale deployment)
Step-by-step guide
Create an Intercom Developer App and Get Your Access Token
Create an Intercom Developer App and Get Your Access Token
Intercom uses a developer app model for API authentication. Every API call requires an Access Token associated with a specific Intercom workspace. To generate one, navigate to your Intercom account and click your profile avatar in the bottom-left, then select 'Settings'. Scroll down to the 'Integrations' section and click 'Developer Hub', or go directly to app.intercom.com/a/developer-hub. In the Developer Hub, click 'New App' in the top right. Give it a descriptive name like 'Replit Integration' and select the workspace you want to connect. Click 'Create App'. On the app detail page, navigate to the 'Authentication' tab on the left sidebar. You will see your Access Token β this is a permanent token tied to this app and workspace. Click the copy icon next to the Access Token. This token gives read/write access to your entire Intercom workspace based on the permissions you configure. For the permissions, stay on the Authentication tab and review the 'Access Permissions' section β enable the objects your integration needs: Contacts (read/write), Conversations (read/write), and Messages (write). Click 'Save Changes'. Note: if you are building an integration for customers' Intercom accounts (not just your own workspace), you would use OAuth instead. For personal integrations connecting your own Replit app to your own Intercom account, the Access Token approach is simpler and works well.
Pro tip: Intercom's Developer Hub also shows your app's webhook configuration, test event delivery, and API usage logs. Bookmark this page β you will return to it when configuring webhooks in a later step.
Expected result: A new Intercom developer app is created and you have copied the Access Token to use in the next step.
Store Your Intercom Access Token in Replit Secrets
Store Your Intercom Access Token in Replit Secrets
Click the lock icon (π) in the Replit sidebar to open the Secrets panel. Click 'New Secret', enter INTERCOM_ACCESS_TOKEN as the key, and paste your copied Access Token as the value. Click 'Add Secret'. The token is now encrypted with AES-256 and stored separately from your code β it will never appear in your file tree, Git history, or Replit's public visibility. Replit's Secret Scanner actively monitors for API key patterns being pasted directly into code files and will prompt you to move them to Secrets. Intercom tokens match common API key patterns, so if you accidentally paste the token into a code file, you will see a warning. Always route credentials through Secrets first. If you need to test different Intercom workspaces (e.g., development vs production), create a separate secret key like INTERCOM_ACCESS_TOKEN_PROD and INTERCOM_ACCESS_TOKEN_DEV, then reference the appropriate one based on an environment variable like NODE_ENV. Access the token in your code: - Node.js: const token = process.env.INTERCOM_ACCESS_TOKEN - Python: token = os.environ['INTERCOM_ACCESS_TOKEN'] After saving, the secret is immediately available to your running Repl without a restart. For deployed apps, new secrets require a redeploy to take effect.
Pro tip: After adding the secret, verify it loaded correctly by adding a quick test: console.log('Token loaded:', !!process.env.INTERCOM_ACCESS_TOKEN) β this prints true/false without revealing the actual value.
Expected result: INTERCOM_ACCESS_TOKEN appears in the Secrets panel with the value hidden as dots.
Create and Update Intercom Contacts
Create and Update Intercom Contacts
Contacts are the core data model in Intercom β every user or lead that interacts with your product becomes a contact. The Intercom API lets you create new contacts, search for existing ones by email, update attributes, and tag contacts for segmentation. The code below implements a complete contact management module with both Node.js and Python examples. The Node.js version uses the built-in fetch API (Node 18+, which is Replit's default). The Python version uses the requests library. Install packages if needed: - Node.js: no additional packages required (uses built-in fetch) - Python: pip install requests flask
1// intercom-contacts.js β Node.js contact management2const INTERCOM_BASE = 'https://api.intercom.io';34async function intercomRequest(method, endpoint, body = null) {5 const token = process.env.INTERCOM_ACCESS_TOKEN;6 if (!token) throw new Error('INTERCOM_ACCESS_TOKEN secret not set');78 const options = {9 method,10 headers: {11 'Authorization': `Bearer ${token}`,12 'Accept': 'application/json',13 'Content-Type': 'application/json',14 'Intercom-Version': '2.11'15 }16 };17 if (body) options.body = JSON.stringify(body);1819 const response = await fetch(`${INTERCOM_BASE}${endpoint}`, options);20 const data = await response.json();2122 if (!response.ok) {23 throw new Error(`Intercom API error ${response.status}: ${JSON.stringify(data)}`);24 }25 return data;26}2728// Create or update a contact by email (upsert)29async function upsertContact({ email, name, phone, customAttributes = {} }) {30 // Search for existing contact first31 const searchResult = await intercomRequest('POST', '/contacts/search', {32 query: {33 field: 'email',34 operator: '=',35 value: email36 }37 });3839 if (searchResult.total_count > 0) {40 // Update existing contact41 const contactId = searchResult.data[0].id;42 const updated = await intercomRequest('PUT', `/contacts/${contactId}`, {43 name,44 phone,45 custom_attributes: customAttributes46 });47 console.log(`Updated contact: ${contactId}`);48 return updated;49 } else {50 // Create new contact51 const created = await intercomRequest('POST', '/contacts', {52 role: 'user',53 email,54 name,55 phone,56 custom_attributes: customAttributes57 });58 console.log(`Created contact: ${created.id}`);59 return created;60 }61}6263// Tag a contact64async function tagContact(contactId, tagName) {65 // First, get or create the tag66 const tags = await intercomRequest('GET', '/tags');67 let tag = tags.data.find(t => t.name === tagName);6869 if (!tag) {70 tag = await intercomRequest('POST', '/tags', { name: tagName });71 }7273 // Apply tag to contact74 await intercomRequest('POST', `/contacts/${contactId}/tags`, { id: tag.id });75 return tag;76}7778module.exports = { upsertContact, tagContact, intercomRequest };Pro tip: The Intercom-Version header is important β always pin to a specific version (currently 2.11) to avoid breaking changes when Intercom releases API updates. Check Intercom's changelog at developers.intercom.com/changelog for deprecation notices.
Expected result: The upsertContact function creates a new contact in Intercom if the email does not exist, or updates the existing contact if it does.
Build an API Server for Contact and Conversation Management
Build an API Server for Contact and Conversation Management
With the contact module ready, build an Express server that exposes HTTP endpoints your frontend or other services can call. This server handles the most common Intercom operations: syncing a user on registration, retrieving recent conversations, and sending a proactive message to a contact. The conversations endpoint uses Intercom's search API to retrieve recent open conversations, which is more efficient than pagination for dashboard use cases. The messaging endpoint uses the admin-initiated message endpoint to send a message from a specific admin (your Intercom teammate) to a user.
1// server.js β Intercom Express API server2const express = require('express');3const { upsertContact, tagContact, intercomRequest } = require('./intercom-contacts');45const app = express();6app.use(express.json());78// POST /sync-user β call this when a user registers or updates profile9app.post('/sync-user', async (req, res) => {10 try {11 const { email, name, phone, plan, company } = req.body;12 if (!email) return res.status(400).json({ error: 'email required' });1314 const contact = await upsertContact({15 email,16 name,17 phone,18 customAttributes: {19 plan: plan || 'free',20 company: company || ''21 }22 });2324 // Auto-tag based on plan25 if (plan === 'enterprise') {26 await tagContact(contact.id, 'Enterprise');27 }2829 res.json({ success: true, contactId: contact.id });30 } catch (err) {31 console.error(err.message);32 res.status(500).json({ error: err.message });33 }34});3536// GET /conversations β recent open conversations37app.get('/conversations', async (req, res) => {38 try {39 const result = await intercomRequest('GET',40 '/conversations?display_as=plaintext&per_page=20');41 const conversations = result.conversations.map(c => ({42 id: c.id,43 subject: c.source?.subject || 'No subject',44 state: c.state,45 assignee: c.assignee?.name || 'Unassigned',46 created_at: new Date(c.created_at * 1000).toISOString()47 }));48 res.json({ conversations });49 } catch (err) {50 res.status(500).json({ error: err.message });51 }52});5354// POST /send-message β send proactive message to a user55app.post('/send-message', async (req, res) => {56 try {57 const { email, subject, body, adminId } = req.body;58 if (!email || !body) {59 return res.status(400).json({ error: 'email and body required' });60 }6162 // Find the contact63 const searchResult = await intercomRequest('POST', '/contacts/search', {64 query: { field: 'email', operator: '=', value: email }65 });6667 if (searchResult.total_count === 0) {68 return res.status(404).json({ error: 'Contact not found in Intercom' });69 }7071 const contactId = searchResult.data[0].id;7273 // Send message as email from admin74 const message = await intercomRequest('POST', '/messages', {75 message_type: 'email',76 subject: subject || 'Message from our team',77 body,78 template: 'plain',79 from: { type: 'admin', id: adminId || process.env.INTERCOM_ADMIN_ID },80 to: { type: 'user', id: contactId }81 });8283 res.json({ success: true, messageId: message.id });84 } catch (err) {85 res.status(500).json({ error: err.message });86 }87});8889app.listen(3000, '0.0.0.0', () => {90 console.log('Intercom integration server running on port 3000');91});Pro tip: To find your Intercom Admin ID (required for sending messages), call GET /admins with your Access Token and look for your name in the result list. Store it as an INTERCOM_ADMIN_ID secret so you do not hardcode it.
Expected result: POST /sync-user creates or updates a contact in Intercom. GET /conversations returns a list of recent open conversations.
Set Up Webhooks for Real-Time Events
Set Up Webhooks for Real-Time Events
Webhooks allow Intercom to push events to your Replit app in real time β when a customer sends a message, closes a conversation, or when a new lead is created. This is more efficient than polling the API continuously. First, deploy your Replit app to get a stable URL. Click the Deploy button in the top-right corner of the Replit editor, choose 'Autoscale', and complete the deployment. Copy your deployment URL (e.g., https://your-app.replit.app). Next, configure the webhook in the Intercom Developer Hub: go to app.intercom.com/a/developer-hub, select your app, click 'Webhooks' in the left sidebar, and click 'Add a webhook endpoint'. Enter your deployment URL + /intercom-webhook (e.g., https://your-app.replit.app/intercom-webhook). Select the topics you want to receive β common choices include conversation.user.created, conversation.user.replied, contact.created, and contact.signed_up. Click 'Save'. The webhook handler below verifies the signature using the Client Secret from your Developer Hub app (find it under Authentication > Client Secret), adds it as a Replit secret named INTERCOM_CLIENT_SECRET.
1# webhook_handler.py β Python Flask Intercom webhook receiver2import os3import hmac4import hashlib5import json6from flask import Flask, request, jsonify78app = Flask(__name__)910def verify_intercom_signature(payload_body, signature_header):11 """Verify the webhook is genuinely from Intercom."""12 client_secret = os.environ.get('INTERCOM_CLIENT_SECRET', '')13 if not client_secret or not signature_header:14 return False1516 # Intercom sends sha1=<hex_digest>17 expected = 'sha1=' + hmac.new(18 client_secret.encode('utf-8'),19 payload_body,20 hashlib.sha121 ).hexdigest()2223 return hmac.compare_digest(expected, signature_header)2425@app.route('/intercom-webhook', methods=['POST'])26def intercom_webhook():27 # Verify signature28 signature = request.headers.get('X-Hub-Signature', '')29 if not verify_intercom_signature(request.data, signature):30 return jsonify({'error': 'Invalid signature'}), 4013132 payload = request.json33 topic = payload.get('topic', 'unknown')34 data = payload.get('data', {}).get('item', {})3536 print(f'Intercom webhook: topic={topic}')3738 if topic == 'conversation.user.created':39 user_email = data.get('source', {}).get('author', {}).get('email')40 subject = data.get('source', {}).get('subject', 'No subject')41 print(f'New conversation from {user_email}: {subject}')42 # Add your logic: notify Slack, update CRM, etc.4344 elif topic == 'conversation.user.replied':45 conv_id = data.get('id')46 print(f'User replied on conversation {conv_id}')4748 elif topic == 'contact.created':49 contact_email = data.get('email')50 print(f'New Intercom contact: {contact_email}')5152 # Always return 200 quickly β process async if needed53 return jsonify({'status': 'received'}), 2005455if __name__ == '__main__':56 app.run(host='0.0.0.0', port=3000)Pro tip: Intercom expects your webhook endpoint to respond within 30 seconds. If your processing logic is slow (database writes, external API calls), return 200 immediately and process the event asynchronously using a background task queue.
Expected result: Intercom delivers test webhook events to your deployment URL and they appear in your Replit console logs.
Common use cases
User Registration Contact Sync
Automatically create or update an Intercom contact record every time a user signs up or updates their profile in your Replit app. Include custom attributes like subscription plan, company name, and account creation date so your support team always has context in the conversation inbox.
Build a Node.js Express server with a /user-registered POST endpoint. When called with user data (email, name, plan, company), it should upsert the contact in Intercom using the Contacts API, setting custom attributes for plan and company. Store the INTERCOM_ACCESS_TOKEN in environment variables.
Copy this prompt to try it in Replit
Conversation Assignment Bot
Build a webhook handler that receives incoming Intercom conversation events and automatically assigns them to the correct team inbox based on keywords, user attributes, or tags β for example, routing enterprise customers to a dedicated support team and routing billing questions to the finance team.
Create a Flask server that receives Intercom conversation.user.created webhook events, reads the conversation subject and user plan attribute, then assigns the conversation to the correct team using the Intercom Conversations API. Return 200 OK for all valid webhook deliveries.
Copy this prompt to try it in Replit
Support Metrics Dashboard
Build a real-time dashboard that queries the Intercom API to display key support metrics: open conversation count, average response time, conversations by team inbox, and contact volume over time β giving your team operational visibility without leaving your Replit app.
Create an Express API server with endpoints that query Intercom conversations filtered by status and assignee, calculate average response times from timestamps, and return aggregated metrics as JSON for a frontend dashboard to display as charts.
Copy this prompt to try it in Replit
Troubleshooting
API returns 401 Unauthorized on every request
Cause: The most common causes are: the Access Token was not saved correctly in Replit Secrets, the token was copied with extra whitespace or newline characters, or the Intercom app's permissions do not include the resource being accessed.
Solution: Open Replit Secrets, delete the INTERCOM_ACCESS_TOKEN secret, and re-add it carefully (avoid copy-pasting with surrounding spaces). Verify the token works by running a simple test request: curl -H 'Authorization: Bearer YOUR_TOKEN' https://api.intercom.io/me from the Replit Shell. Also check the Developer Hub app's Authentication tab to confirm the required permissions are enabled.
1// Quick token validation in Node.js2const response = await fetch('https://api.intercom.io/me', {3 headers: { 'Authorization': `Bearer ${process.env.INTERCOM_ACCESS_TOKEN}` }4});5console.log(response.status, await response.json());Webhook events are not being received on the Replit endpoint
Cause: The webhook is likely configured with the development URL (the temporary *.replit.dev URL that only works while the editor is open) instead of the deployment URL (*.replit.app). Intercom webhooks require a publicly accessible, always-on URL.
Solution: Deploy your Replit app using Autoscale deployment to get a stable https://your-app.replit.app URL. Update the webhook endpoint URL in the Intercom Developer Hub under Webhooks > your webhook > Edit. Use the Intercom webhook tester (send test event button) to confirm delivery after updating.
Contact search returns 0 results even though the contact exists in Intercom
Cause: The Contacts search API uses exact matching by default. If the email case does not match exactly (e.g., User@Example.com vs user@example.com), or if the contact was created as a 'lead' type while you are searching for 'user' type, the search may return empty.
Solution: Intercom contact emails are case-insensitive in their system but the search query should use lowercase. Convert all email addresses to lowercase before searching. Also note that contacts can be of type 'user' or 'lead' β a search query does not filter by type by default, but check that you are creating contacts with the correct role field.
1// Always lowercase emails before searching2const email = userEmail.toLowerCase().trim();3const result = await intercomRequest('POST', '/contacts/search', {4 query: { field: 'email', operator: '=', value: email }5});Sending a message returns 'admin_not_found' or 422 error
Cause: The adminId used in the from field of the message request does not match a valid admin in your Intercom workspace, or the admin does not have permission to send outbound messages.
Solution: Find valid admin IDs by making a GET request to https://api.intercom.io/admins with your Access Token. Store the correct admin ID as the INTERCOM_ADMIN_ID secret. Ensure the admin has the 'Send messages' permission in their Intercom teammate role settings.
1// List all admins to find correct IDs2const admins = await intercomRequest('GET', '/admins');3console.log(admins.admins.map(a => ({ id: a.id, name: a.name, email: a.email })));Best practices
- Always upsert contacts using a search-before-create pattern rather than assuming a contact does not exist β duplicate contacts in Intercom can cause confusion for your support team and skew reporting metrics.
- Pin the Intercom-Version header to a specific API version (currently 2.11) in all requests to prevent unexpected breaking changes when Intercom releases updates.
- Store your Intercom Access Token, Client Secret, and Admin ID in Replit Secrets (lock icon) rather than hardcoding them β Replit's Secret Scanner will warn you if it detects API key patterns in code files.
- Return 200 from webhook endpoints immediately and process events asynchronously β Intercom times out webhook deliveries after 30 seconds and will retry, potentially causing duplicate processing if your handler is slow.
- Use Intercom's webhook delivery log in the Developer Hub (Webhooks > your endpoint > Recent deliveries) to debug failed webhook deliveries and inspect the exact payloads being sent.
- Deploy on Autoscale for webhook endpoints rather than leaving your app in development mode β the development URL only works while your Replit editor is open, causing missed webhook events.
- Implement rate limit handling in your API client β Intercom enforces rate limits of 1,000 requests per minute per access token. Add retry logic with exponential backoff for 429 responses.
Alternatives
Zendesk is better suited for ticket-based support workflows with SLA tracking and a large help desk team, while Intercom excels at live chat and in-product messaging.
LiveChat focuses purely on real-time chat with a simpler API, making it easier to integrate for teams that only need live support without Intercom's full customer engagement platform.
Slack is for internal team communication, not customer-facing chat β use it alongside Intercom to notify your team when customers send important messages rather than as a replacement.
HubSpot combines CRM with live chat and marketing tools in one platform, making it a good alternative if you need customer conversations integrated with a full sales pipeline.
Frequently asked questions
How do I store my Intercom API token in Replit?
Click the lock icon (π) in the Replit sidebar to open the Secrets panel, then add a new secret with the key INTERCOM_ACCESS_TOKEN and your token as the value. Access it in Node.js as process.env.INTERCOM_ACCESS_TOKEN or in Python as os.environ['INTERCOM_ACCESS_TOKEN']. Never paste the token directly into your code files.
Does Replit work with Intercom webhooks?
Yes, but you must use a deployed Replit app (Autoscale or Reserved VM) rather than the development preview URL. The development URL is temporary and only works while your Replit browser tab is open. Deploy your app to get a stable https://your-app.replit.app URL, then register that URL in the Intercom Developer Hub under Webhooks.
What is the difference between Intercom Access Token and Client Secret?
The Access Token is used to authenticate all API calls (contacts, conversations, messages). The Client Secret is used to verify webhook signatures β you use it to compute an HMAC hash and compare it against the X-Hub-Signature header Intercom sends with each webhook request. Both are found in your Developer Hub app under the Authentication tab.
Can I use Replit with Intercom for free?
Intercom's API is accessible on all plans, including the Starter plan (~$74/month). There is no free tier for production Intercom usage beyond the 14-day trial. The Intercom Developer Hub and test workspace are free, so you can build and test your Replit integration without a paid plan during development.
How do I avoid creating duplicate contacts in Intercom?
Use the Contacts search API to check if a contact exists by email before creating a new one. This search-before-create pattern (upsert) ensures you update existing contacts rather than creating duplicates. Intercom also provides a dedicated upsert endpoint that handles this automatically β POST to /contacts with the email and it will merge with an existing contact or create a new one.
How do I find my Intercom Admin ID for sending messages?
Make a GET request to https://api.intercom.io/admins with your Authorization header. The response lists all teammates in your workspace with their IDs, names, and emails. Store your admin ID as an INTERCOM_ADMIN_ID secret in Replit and reference it when sending outbound messages.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation