Intercom Webhooks to an Internal CRM Bridge Using Skooh
Sync Intercom conversations and tags to internal CRM systems in real-time.
Use Case
Your support team uses Intercom, but customer data lives in your internal CRM. When a conversation starts, gets tagged, or closes, you want your CRM updated automatically.
Skooh forwards Intercom webhooks to your internal CRM bridge service.
Setup
1. Create Skooh Endpoint
Create "Intercom CRM Sync" in Skooh.
2. Run the Skooh Proxy
Using the Desktop App:
- Download the Skooh Desktop App
- Enter your API key and hook token
- Set forwarding URL (e.g., `http://localhost:3000/webhooks/intercom\`)
- Click Connect
Using Docker:
docker run -d \\
--name skooh-intercom \\
-e SKOOH_TOKEN=intercom-crm \\
-e SKOOH_API_KEY=your_api_key \\
-e FORWARD_URL=http://crm-bridge:3000/webhooks/intercom \\
--restart unless-stopped \\
skooh/proxy
3. Configure Intercom
Note: See Intercom's Webhook documentation for current setup instructions.
In Intercom Developer Hub:
- Create or select your app
- Go to Webhooks
- Add your Skooh URL
- Select topics:
- `conversation.user.created`
- `conversation.user.replied`
- `conversation.admin.replied`
- `conversation.admin.closed`
- `contact.tag.created`
- `contact.tag.deleted`
Handling Intercom Webhooks
app.post('/webhooks/intercom', express.json(), async (req, res) => {
const { topic, data } = req.body;
// Verify webhook signature
if (!verifyIntercomSignature(req)) {
return res.status(401).send('Invalid signature');
}
switch (topic) {
case 'conversation.user.created':
await handleNewConversation(data);
break;
case 'conversation.admin.closed':
await handleConversationClosed(data);
break;
case 'contact.tag.created':
await handleTagAdded(data);
break;
case 'contact.tag.deleted':
await handleTagRemoved(data);
break;
}
res.status(200).send('OK');
});
Syncing Conversations to CRM
async function handleNewConversation(data) {
const { item: conversation } = data;
// Extract customer info
const customer = conversation.user;
const email = customer.email;
// Find customer in CRM
const crmCustomer = await crmClient.findByEmail(email);
if (crmCustomer) {
// Create interaction record
await crmClient.createInteraction({
customerId: crmCustomer.id,
type: 'support_conversation',
channel: 'intercom',
externalId: conversation.id,
subject: conversation.source?.subject || 'Support Request',
createdAt: new Date(conversation.created_at * 1000)
});
// Update last contact date
await crmClient.updateCustomer(crmCustomer.id, {
lastContactDate: new Date()
});
}
}
Syncing Tags
Tags in Intercom → segments in CRM:
async function handleTagAdded(data) {
const { item: tagEvent } = data;
const contact = tagEvent.contact;
const tag = tagEvent.tag;
const crmCustomer = await crmClient.findByEmail(contact.email);
if (crmCustomer) {
// Map Intercom tag to CRM segment
const segmentMap = {
'vip': 'high_value_customer',
'churning': 'at_risk',
'enterprise': 'enterprise_tier',
'product_feedback': 'feedback_provider'
};
const segment = segmentMap[tag.name];
if (segment) {
await crmClient.addToSegment(crmCustomer.id, segment);
}
}
}
Verifying Intercom Webhooks
const crypto = require('crypto');
function verifyIntercomSignature(req) {
const signature = req.headers['x-hub-signature'];
if (!signature) return false;
const hmac = crypto.createHmac('sha1', process.env.INTERCOM_CLIENT_SECRET);
const digest = 'sha1=' + hmac.update(JSON.stringify(req.body)).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(digest)
);
}
Two-Way Sync
Update Intercom when CRM changes:
// When customer upgraded in CRM
async function onCustomerUpgraded(customer) {
// Add VIP tag in Intercom
await intercomClient.tags.tag({
name: 'vip',
users: [{ email: customer.email }]
});
}
Testing with Replay
- Create a test conversation in Intercom
- Find the webhook in Skooh
- Replay to test your CRM bridge
- Verify data appears in CRM
- Replay again after fixing bugs
Written by Skooh Team