Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions apps/sim/app/(landing)/integrations/data/integrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -6077,8 +6077,39 @@
}
],
"operationCount": 31,
"triggers": [],
"triggerCount": 0,
"triggers": [
{
"id": "intercom_conversation_created",
"name": "Intercom Conversation Created",
"description": "Trigger workflow when a new conversation is created in Intercom"
},
{
"id": "intercom_conversation_reply",
"name": "Intercom Conversation Reply",
"description": "Trigger workflow when someone replies to an Intercom conversation"
},
{
"id": "intercom_conversation_closed",
"name": "Intercom Conversation Closed",
"description": "Trigger workflow when a conversation is closed in Intercom"
},
{
"id": "intercom_contact_created",
"name": "Intercom Contact Created",
"description": "Trigger workflow when a new lead is created in Intercom"
},
{
"id": "intercom_user_created",
"name": "Intercom User Created",
"description": "Trigger workflow when a new user is created in Intercom"
},
{
"id": "intercom_webhook",
"name": "Intercom Webhook (All Events)",
"description": "Trigger workflow on any Intercom webhook event"
}
],
"triggerCount": 6,
"authType": "api-key",
"category": "tools",
"integrationType": "customer-support",
Expand Down
21 changes: 21 additions & 0 deletions apps/sim/blocks/blocks/intercom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { IntercomIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
import { createVersionedToolSelector } from '@/blocks/utils'
import { getTrigger } from '@/triggers'

export const IntercomBlock: BlockConfig = {
type: 'intercom',
Expand Down Expand Up @@ -1409,6 +1410,26 @@ export const IntercomV2Block: BlockConfig = {
integrationType: IntegrationType.CustomerSupport,
tags: ['customer-support', 'messaging'],
hideFromToolbar: false,
subBlocks: [
...IntercomBlock.subBlocks,
...getTrigger('intercom_conversation_created').subBlocks,
...getTrigger('intercom_conversation_reply').subBlocks,
...getTrigger('intercom_conversation_closed').subBlocks,
...getTrigger('intercom_contact_created').subBlocks,
...getTrigger('intercom_user_created').subBlocks,
...getTrigger('intercom_webhook').subBlocks,
],
triggers: {
enabled: true,
available: [
'intercom_conversation_created',
'intercom_conversation_reply',
'intercom_conversation_closed',
'intercom_contact_created',
'intercom_user_created',
'intercom_webhook',
],
},
tools: {
...IntercomBlock.tools,
access: [
Expand Down
120 changes: 120 additions & 0 deletions apps/sim/lib/webhooks/providers/intercom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import crypto from 'crypto'
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { safeCompare } from '@/lib/core/security/encryption'
import type {
AuthContext,
EventMatchContext,
FormatInputContext,
FormatInputResult,
WebhookProviderHandler,
} from '@/lib/webhooks/providers/types'

const logger = createLogger('WebhookProvider:Intercom')

/**
* Validate Intercom webhook signature using HMAC-SHA1.
* Intercom signs payloads with the app's Client Secret and sends the
* signature in the X-Hub-Signature header as "sha1=<hex>".
*/
function validateIntercomSignature(secret: string, signature: string, body: string): boolean {
try {
if (!secret || !signature || !body) {
logger.warn('Intercom signature validation missing required fields', {
hasSecret: !!secret,
hasSignature: !!signature,
hasBody: !!body,
})
return false
}

if (!signature.startsWith('sha1=')) {
logger.warn('Intercom signature has invalid format', {
signature: `${signature.substring(0, 10)}...`,
})
return false
}

const providedSignature = signature.substring(5)
const computedHash = crypto.createHmac('sha1', secret).update(body, 'utf8').digest('hex')

return safeCompare(computedHash, providedSignature)
} catch (error) {
logger.error('Error validating Intercom signature:', error)
return false
}
}

export const intercomHandler: WebhookProviderHandler = {
verifyAuth({ request, rawBody, requestId, providerConfig }: AuthContext) {
const secret = providerConfig.webhookSecret as string | undefined
if (!secret) {
return null
}

const signature = request.headers.get('X-Hub-Signature')
if (!signature) {
logger.warn(`[${requestId}] Intercom webhook missing X-Hub-Signature header`)
return new NextResponse('Unauthorized - Missing Intercom signature', { status: 401 })
}

if (!validateIntercomSignature(secret, signature, rawBody)) {
logger.warn(`[${requestId}] Intercom signature verification failed`, {
signatureLength: signature.length,
secretLength: secret.length,
})
return new NextResponse('Unauthorized - Invalid Intercom signature', { status: 401 })
}

return null
},

handleReachabilityTest(body: unknown, requestId: string) {
const obj = body as Record<string, unknown> | null
if (obj?.topic === 'ping') {
logger.info(
`[${requestId}] Intercom ping event detected - returning 200 without triggering workflow`
)
return NextResponse.json({
status: 'ok',
message: 'Webhook endpoint verified',
})
}
return null
},

async formatInput({ body }: FormatInputContext): Promise<FormatInputResult> {
return { input: body }
},

async matchEvent({ webhook, body, requestId, providerConfig }: EventMatchContext) {
const triggerId = providerConfig.triggerId as string | undefined
const obj = body as Record<string, unknown>
const topic = obj?.topic as string | undefined

if (triggerId && triggerId !== 'intercom_webhook') {
const { isIntercomEventMatch } = await import('@/triggers/intercom/utils')
if (!isIntercomEventMatch(triggerId, topic || '')) {
logger.debug(
`[${requestId}] Intercom event mismatch for trigger ${triggerId}. Topic: ${topic}. Skipping execution.`,
{
webhookId: webhook.id,
triggerId,
receivedTopic: topic,
}
)
return false
}
}

return true
},

extractIdempotencyId(body: unknown) {
const obj = body as Record<string, unknown>
if (obj?.id && obj?.type === 'notification_event') {
return String(obj.id)
}
return null
},
}
2 changes: 2 additions & 0 deletions apps/sim/lib/webhooks/providers/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { googleFormsHandler } from '@/lib/webhooks/providers/google-forms'
import { grainHandler } from '@/lib/webhooks/providers/grain'
import { hubspotHandler } from '@/lib/webhooks/providers/hubspot'
import { imapHandler } from '@/lib/webhooks/providers/imap'
import { intercomHandler } from '@/lib/webhooks/providers/intercom'
import { jiraHandler } from '@/lib/webhooks/providers/jira'
import { lemlistHandler } from '@/lib/webhooks/providers/lemlist'
import { linearHandler } from '@/lib/webhooks/providers/linear'
Expand Down Expand Up @@ -52,6 +53,7 @@ const PROVIDER_HANDLERS: Record<string, WebhookProviderHandler> = {
grain: grainHandler,
hubspot: hubspotHandler,
imap: imapHandler,
intercom: intercomHandler,
jira: jiraHandler,
lemlist: lemlistHandler,
linear: linearHandler,
Expand Down
41 changes: 41 additions & 0 deletions apps/sim/triggers/intercom/contact_created.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { IntercomIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildIntercomContactOutputs,
buildIntercomExtraFields,
intercomSetupInstructions,
intercomTriggerOptions,
} from '@/triggers/intercom/utils'
import type { TriggerConfig } from '@/triggers/types'

/**
* Intercom Contact Created Trigger
*
* Fires when a new lead is created in Intercom.
* Note: In Intercom, contact.created fires for leads only.
* For identified users, use the User Created trigger (user.created topic).
*/
export const intercomContactCreatedTrigger: TriggerConfig = {
id: 'intercom_contact_created',
name: 'Intercom Contact Created',
provider: 'intercom',
description: 'Trigger workflow when a new lead is created in Intercom',
version: '1.0.0',
icon: IntercomIcon,

subBlocks: buildTriggerSubBlocks({
triggerId: 'intercom_contact_created',
triggerOptions: intercomTriggerOptions,
setupInstructions: intercomSetupInstructions('contact.created'),
extraFields: buildIntercomExtraFields('intercom_contact_created'),
}),

outputs: buildIntercomContactOutputs(),

webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
}
39 changes: 39 additions & 0 deletions apps/sim/triggers/intercom/conversation_closed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { IntercomIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildIntercomConversationOutputs,
buildIntercomExtraFields,
intercomSetupInstructions,
intercomTriggerOptions,
} from '@/triggers/intercom/utils'
import type { TriggerConfig } from '@/triggers/types'

/**
* Intercom Conversation Closed Trigger
*
* Fires when an admin closes a conversation.
*/
export const intercomConversationClosedTrigger: TriggerConfig = {
id: 'intercom_conversation_closed',
name: 'Intercom Conversation Closed',
provider: 'intercom',
description: 'Trigger workflow when a conversation is closed in Intercom',
version: '1.0.0',
icon: IntercomIcon,

subBlocks: buildTriggerSubBlocks({
triggerId: 'intercom_conversation_closed',
triggerOptions: intercomTriggerOptions,
setupInstructions: intercomSetupInstructions('conversation.admin.closed'),
extraFields: buildIntercomExtraFields('intercom_conversation_closed'),
}),

outputs: buildIntercomConversationOutputs(),

webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
}
43 changes: 43 additions & 0 deletions apps/sim/triggers/intercom/conversation_created.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { IntercomIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildIntercomConversationOutputs,
buildIntercomExtraFields,
intercomSetupInstructions,
intercomTriggerOptions,
} from '@/triggers/intercom/utils'
import type { TriggerConfig } from '@/triggers/types'

/**
* Intercom Conversation Created Trigger
*
* This is the PRIMARY trigger - it includes the dropdown for selecting trigger type.
* Fires when a user/lead starts a new conversation or an admin initiates a 1:1 conversation.
*/
export const intercomConversationCreatedTrigger: TriggerConfig = {
id: 'intercom_conversation_created',
name: 'Intercom Conversation Created',
provider: 'intercom',
description: 'Trigger workflow when a new conversation is created in Intercom',
version: '1.0.0',
icon: IntercomIcon,

subBlocks: buildTriggerSubBlocks({
triggerId: 'intercom_conversation_created',
triggerOptions: intercomTriggerOptions,
includeDropdown: true,
setupInstructions: intercomSetupInstructions(
'conversation.user.created and/or conversation.admin.single.created'
),
extraFields: buildIntercomExtraFields('intercom_conversation_created'),
}),

outputs: buildIntercomConversationOutputs(),

webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
}
41 changes: 41 additions & 0 deletions apps/sim/triggers/intercom/conversation_reply.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { IntercomIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildIntercomConversationOutputs,
buildIntercomExtraFields,
intercomSetupInstructions,
intercomTriggerOptions,
} from '@/triggers/intercom/utils'
import type { TriggerConfig } from '@/triggers/types'

/**
* Intercom Conversation Reply Trigger
*
* Fires when a user, lead, or admin replies to a conversation.
*/
export const intercomConversationReplyTrigger: TriggerConfig = {
id: 'intercom_conversation_reply',
name: 'Intercom Conversation Reply',
provider: 'intercom',
description: 'Trigger workflow when someone replies to an Intercom conversation',
version: '1.0.0',
icon: IntercomIcon,

subBlocks: buildTriggerSubBlocks({
triggerId: 'intercom_conversation_reply',
triggerOptions: intercomTriggerOptions,
setupInstructions: intercomSetupInstructions(
'conversation.user.replied and/or conversation.admin.replied'
),
extraFields: buildIntercomExtraFields('intercom_conversation_reply'),
}),

outputs: buildIntercomConversationOutputs(),

webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
}
6 changes: 6 additions & 0 deletions apps/sim/triggers/intercom/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { intercomContactCreatedTrigger } from './contact_created'
export { intercomConversationClosedTrigger } from './conversation_closed'
export { intercomConversationCreatedTrigger } from './conversation_created'
export { intercomConversationReplyTrigger } from './conversation_reply'
export { intercomUserCreatedTrigger } from './user_created'
export { intercomWebhookTrigger } from './webhook'
Loading
Loading