Skip to content

Commit baa3dcf

Browse files
committed
feat(trigger): add ServiceNow webhook triggers
1 parent 7bd271a commit baa3dcf

File tree

9 files changed

+349
-0
lines changed

9 files changed

+349
-0
lines changed

apps/sim/blocks/blocks/servicenow.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ServiceNowIcon } from '@/components/icons'
22
import type { BlockConfig } from '@/blocks/types'
33
import { IntegrationType } from '@/blocks/types'
44
import type { ServiceNowResponse } from '@/tools/servicenow/types'
5+
import { getTrigger } from '@/triggers'
56

67
export const ServiceNowBlock: BlockConfig<ServiceNowResponse> = {
78
type: 'servicenow',
@@ -215,6 +216,12 @@ Output: {"state": "2", "assigned_to": "john.doe", "work_notes": "Assigned and st
215216
condition: { field: 'operation', value: 'servicenow_delete_record' },
216217
required: true,
217218
},
219+
// Trigger SubBlocks
220+
...getTrigger('servicenow_incident_created').subBlocks,
221+
...getTrigger('servicenow_incident_updated').subBlocks,
222+
...getTrigger('servicenow_change_request_created').subBlocks,
223+
...getTrigger('servicenow_change_request_updated').subBlocks,
224+
...getTrigger('servicenow_webhook').subBlocks,
218225
],
219226
tools: {
220227
access: [
@@ -262,4 +269,14 @@ Output: {"state": "2", "assigned_to": "john.doe", "work_notes": "Assigned and st
262269
success: { type: 'boolean', description: 'Operation success status' },
263270
metadata: { type: 'json', description: 'Operation metadata' },
264271
},
272+
triggers: {
273+
enabled: true,
274+
available: [
275+
'servicenow_incident_created',
276+
'servicenow_incident_updated',
277+
'servicenow_change_request_created',
278+
'servicenow_change_request_updated',
279+
'servicenow_webhook',
280+
],
281+
},
265282
}

apps/sim/triggers/registry.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,13 @@ import {
235235
salesforceRecordUpdatedTrigger,
236236
salesforceWebhookTrigger,
237237
} from '@/triggers/salesforce'
238+
import {
239+
servicenowChangeRequestCreatedTrigger,
240+
servicenowChangeRequestUpdatedTrigger,
241+
servicenowIncidentCreatedTrigger,
242+
servicenowIncidentUpdatedTrigger,
243+
servicenowWebhookTrigger,
244+
} from '@/triggers/servicenow'
238245
import { slackWebhookTrigger } from '@/triggers/slack'
239246
import { stripeWebhookTrigger } from '@/triggers/stripe'
240247
import { telegramWebhookTrigger } from '@/triggers/telegram'
@@ -437,6 +444,11 @@ export const TRIGGER_REGISTRY: TriggerRegistry = {
437444
salesforce_opportunity_stage_changed: salesforceOpportunityStageChangedTrigger,
438445
salesforce_case_status_changed: salesforceCaseStatusChangedTrigger,
439446
salesforce_webhook: salesforceWebhookTrigger,
447+
servicenow_incident_created: servicenowIncidentCreatedTrigger,
448+
servicenow_incident_updated: servicenowIncidentUpdatedTrigger,
449+
servicenow_change_request_created: servicenowChangeRequestCreatedTrigger,
450+
servicenow_change_request_updated: servicenowChangeRequestUpdatedTrigger,
451+
servicenow_webhook: servicenowWebhookTrigger,
440452
stripe_webhook: stripeWebhookTrigger,
441453
telegram_webhook: telegramWebhookTrigger,
442454
typeform_webhook: typeformWebhookTrigger,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { ServiceNowIcon } from '@/components/icons'
2+
import { buildTriggerSubBlocks } from '@/triggers'
3+
import {
4+
buildChangeRequestOutputs,
5+
buildServiceNowExtraFields,
6+
servicenowSetupInstructions,
7+
servicenowTriggerOptions,
8+
} from '@/triggers/servicenow/utils'
9+
import type { TriggerConfig } from '@/triggers/types'
10+
11+
/**
12+
* ServiceNow Change Request Created Trigger
13+
*/
14+
export const servicenowChangeRequestCreatedTrigger: TriggerConfig = {
15+
id: 'servicenow_change_request_created',
16+
name: 'ServiceNow Change Request Created',
17+
provider: 'servicenow',
18+
description: 'Trigger workflow when a new change request is created in ServiceNow',
19+
version: '1.0.0',
20+
icon: ServiceNowIcon,
21+
22+
subBlocks: buildTriggerSubBlocks({
23+
triggerId: 'servicenow_change_request_created',
24+
triggerOptions: servicenowTriggerOptions,
25+
setupInstructions: servicenowSetupInstructions('Insert (record creation)'),
26+
extraFields: buildServiceNowExtraFields('servicenow_change_request_created'),
27+
}),
28+
29+
outputs: buildChangeRequestOutputs(),
30+
31+
webhook: {
32+
method: 'POST',
33+
headers: {
34+
'Content-Type': 'application/json',
35+
},
36+
},
37+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { ServiceNowIcon } from '@/components/icons'
2+
import { buildTriggerSubBlocks } from '@/triggers'
3+
import {
4+
buildChangeRequestOutputs,
5+
buildServiceNowExtraFields,
6+
servicenowSetupInstructions,
7+
servicenowTriggerOptions,
8+
} from '@/triggers/servicenow/utils'
9+
import type { TriggerConfig } from '@/triggers/types'
10+
11+
/**
12+
* ServiceNow Change Request Updated Trigger
13+
*/
14+
export const servicenowChangeRequestUpdatedTrigger: TriggerConfig = {
15+
id: 'servicenow_change_request_updated',
16+
name: 'ServiceNow Change Request Updated',
17+
provider: 'servicenow',
18+
description: 'Trigger workflow when a change request is updated in ServiceNow',
19+
version: '1.0.0',
20+
icon: ServiceNowIcon,
21+
22+
subBlocks: buildTriggerSubBlocks({
23+
triggerId: 'servicenow_change_request_updated',
24+
triggerOptions: servicenowTriggerOptions,
25+
setupInstructions: servicenowSetupInstructions('Update (record modification)'),
26+
extraFields: buildServiceNowExtraFields('servicenow_change_request_updated'),
27+
}),
28+
29+
outputs: buildChangeRequestOutputs(),
30+
31+
webhook: {
32+
method: 'POST',
33+
headers: {
34+
'Content-Type': 'application/json',
35+
},
36+
},
37+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { ServiceNowIcon } from '@/components/icons'
2+
import { buildTriggerSubBlocks } from '@/triggers'
3+
import {
4+
buildIncidentOutputs,
5+
buildServiceNowExtraFields,
6+
servicenowSetupInstructions,
7+
servicenowTriggerOptions,
8+
} from '@/triggers/servicenow/utils'
9+
import type { TriggerConfig } from '@/triggers/types'
10+
11+
/**
12+
* ServiceNow Incident Created Trigger
13+
*
14+
* Primary trigger — includes the dropdown for selecting trigger type.
15+
*/
16+
export const servicenowIncidentCreatedTrigger: TriggerConfig = {
17+
id: 'servicenow_incident_created',
18+
name: 'ServiceNow Incident Created',
19+
provider: 'servicenow',
20+
description: 'Trigger workflow when a new incident is created in ServiceNow',
21+
version: '1.0.0',
22+
icon: ServiceNowIcon,
23+
24+
subBlocks: buildTriggerSubBlocks({
25+
triggerId: 'servicenow_incident_created',
26+
triggerOptions: servicenowTriggerOptions,
27+
includeDropdown: true,
28+
setupInstructions: servicenowSetupInstructions('Insert (record creation)'),
29+
extraFields: buildServiceNowExtraFields('servicenow_incident_created'),
30+
}),
31+
32+
outputs: buildIncidentOutputs(),
33+
34+
webhook: {
35+
method: 'POST',
36+
headers: {
37+
'Content-Type': 'application/json',
38+
},
39+
},
40+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { ServiceNowIcon } from '@/components/icons'
2+
import { buildTriggerSubBlocks } from '@/triggers'
3+
import {
4+
buildIncidentOutputs,
5+
buildServiceNowExtraFields,
6+
servicenowSetupInstructions,
7+
servicenowTriggerOptions,
8+
} from '@/triggers/servicenow/utils'
9+
import type { TriggerConfig } from '@/triggers/types'
10+
11+
/**
12+
* ServiceNow Incident Updated Trigger
13+
*/
14+
export const servicenowIncidentUpdatedTrigger: TriggerConfig = {
15+
id: 'servicenow_incident_updated',
16+
name: 'ServiceNow Incident Updated',
17+
provider: 'servicenow',
18+
description: 'Trigger workflow when an incident is updated in ServiceNow',
19+
version: '1.0.0',
20+
icon: ServiceNowIcon,
21+
22+
subBlocks: buildTriggerSubBlocks({
23+
triggerId: 'servicenow_incident_updated',
24+
triggerOptions: servicenowTriggerOptions,
25+
setupInstructions: servicenowSetupInstructions('Update (record modification)'),
26+
extraFields: buildServiceNowExtraFields('servicenow_incident_updated'),
27+
}),
28+
29+
outputs: buildIncidentOutputs(),
30+
31+
webhook: {
32+
method: 'POST',
33+
headers: {
34+
'Content-Type': 'application/json',
35+
},
36+
},
37+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export { servicenowChangeRequestCreatedTrigger } from './change_request_created'
2+
export { servicenowChangeRequestUpdatedTrigger } from './change_request_updated'
3+
export { servicenowIncidentCreatedTrigger } from './incident_created'
4+
export { servicenowIncidentUpdatedTrigger } from './incident_updated'
5+
export { servicenowWebhookTrigger } from './webhook'
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import type { SubBlockConfig } from '@/blocks/types'
2+
import type { TriggerOutput } from '@/triggers/types'
3+
4+
/**
5+
* Shared trigger dropdown options for all ServiceNow triggers
6+
*/
7+
export const servicenowTriggerOptions = [
8+
{ label: 'Incident Created', id: 'servicenow_incident_created' },
9+
{ label: 'Incident Updated', id: 'servicenow_incident_updated' },
10+
{ label: 'Change Request Created', id: 'servicenow_change_request_created' },
11+
{ label: 'Change Request Updated', id: 'servicenow_change_request_updated' },
12+
{ label: 'Generic Webhook (All Events)', id: 'servicenow_webhook' },
13+
]
14+
15+
/**
16+
* Generates setup instructions for ServiceNow webhooks.
17+
* ServiceNow uses Business Rules with RESTMessageV2 for outbound webhooks.
18+
*/
19+
export function servicenowSetupInstructions(eventType: string): string {
20+
const instructions = [
21+
'<strong>Note:</strong> You need admin or developer permissions in your ServiceNow instance to create Business Rules.',
22+
'Navigate to <strong>System Definition > Business Rules</strong> and create a new Business Rule.',
23+
`Set the table (e.g., <strong>incident</strong>, <strong>change_request</strong>), set <strong>When</strong> to <strong>after</strong>, and check <strong>${eventType}</strong>.`,
24+
'Check the <strong>Advanced</strong> checkbox to enable the script editor.',
25+
`In the script, use <strong>RESTMessageV2</strong> to POST the record data as JSON to the <strong>Webhook URL</strong> above. Example:<br/><code style="font-size: 0.85em; display: block; margin-top: 4px; white-space: pre-wrap;">var r = new sn_ws.RESTMessageV2();\nr.setEndpoint("&lt;webhook_url&gt;");\nr.setHttpMethod("POST");\nr.setRequestHeader("Content-Type", "application/json");\nr.setRequestBody(JSON.stringify({\n sysId: current.sys_id.toString(),\n number: current.number.toString(),\n shortDescription: current.short_description.toString(),\n state: current.state.toString(),\n priority: current.priority.toString()\n}));\nr.execute();</code>`,
26+
'Activate the Business Rule and click "Save" above to activate your trigger.',
27+
]
28+
29+
return instructions
30+
.map(
31+
(instruction, index) =>
32+
`<div class="mb-3">${index === 0 ? instruction : `<strong>${index}.</strong> ${instruction}`}</div>`
33+
)
34+
.join('')
35+
}
36+
37+
/**
38+
* Extra fields for ServiceNow triggers (optional table filter)
39+
*/
40+
export function buildServiceNowExtraFields(triggerId: string): SubBlockConfig[] {
41+
return [
42+
{
43+
id: 'tableName',
44+
title: 'Table Name (Optional)',
45+
type: 'short-input',
46+
placeholder: 'e.g., incident, change_request',
47+
description: 'Optionally filter to a specific ServiceNow table',
48+
mode: 'trigger',
49+
condition: { field: 'selectedTriggerId', value: triggerId },
50+
},
51+
]
52+
}
53+
54+
/**
55+
* Common record fields shared across ServiceNow trigger outputs
56+
*/
57+
function buildRecordOutputs(): Record<string, TriggerOutput> {
58+
return {
59+
sysId: { type: 'string', description: 'Unique system ID of the record' },
60+
number: { type: 'string', description: 'Record number (e.g., INC0010001, CHG0010001)' },
61+
tableName: { type: 'string', description: 'ServiceNow table name' },
62+
shortDescription: { type: 'string', description: 'Short description of the record' },
63+
description: { type: 'string', description: 'Full description of the record' },
64+
state: { type: 'string', description: 'Current state of the record' },
65+
priority: {
66+
type: 'string',
67+
description: 'Priority level (1=Critical, 2=High, 3=Moderate, 4=Low, 5=Planning)',
68+
},
69+
assignedTo: { type: 'string', description: 'User assigned to this record' },
70+
assignmentGroup: { type: 'string', description: 'Group assigned to this record' },
71+
createdBy: { type: 'string', description: 'User who created the record' },
72+
createdOn: { type: 'string', description: 'When the record was created (ISO 8601)' },
73+
updatedBy: { type: 'string', description: 'User who last updated the record' },
74+
updatedOn: { type: 'string', description: 'When the record was last updated (ISO 8601)' },
75+
}
76+
}
77+
78+
/**
79+
* Outputs for incident triggers
80+
*/
81+
export function buildIncidentOutputs(): Record<string, TriggerOutput> {
82+
return {
83+
...buildRecordOutputs(),
84+
urgency: { type: 'string', description: 'Urgency level (1=High, 2=Medium, 3=Low)' },
85+
impact: { type: 'string', description: 'Impact level (1=High, 2=Medium, 3=Low)' },
86+
category: { type: 'string', description: 'Incident category' },
87+
subcategory: { type: 'string', description: 'Incident subcategory' },
88+
caller: { type: 'string', description: 'Caller/requester of the incident' },
89+
resolvedBy: { type: 'string', description: 'User who resolved the incident' },
90+
resolvedAt: { type: 'string', description: 'When the incident was resolved' },
91+
closeNotes: { type: 'string', description: 'Notes added when the incident was closed' },
92+
record: { type: 'json', description: 'Full incident record data' },
93+
}
94+
}
95+
96+
/**
97+
* Outputs for change request triggers
98+
*/
99+
export function buildChangeRequestOutputs(): Record<string, TriggerOutput> {
100+
return {
101+
...buildRecordOutputs(),
102+
type: { type: 'string', description: 'Change type (Normal, Standard, Emergency)' },
103+
risk: { type: 'string', description: 'Risk level of the change' },
104+
impact: { type: 'string', description: 'Impact level of the change' },
105+
approval: { type: 'string', description: 'Approval status' },
106+
startDate: { type: 'string', description: 'Planned start date' },
107+
endDate: { type: 'string', description: 'Planned end date' },
108+
category: { type: 'string', description: 'Change category' },
109+
record: { type: 'json', description: 'Full change request record data' },
110+
}
111+
}
112+
113+
/**
114+
* Outputs for the generic webhook trigger (all events)
115+
*/
116+
export function buildServiceNowWebhookOutputs(): Record<string, TriggerOutput> {
117+
return {
118+
...buildRecordOutputs(),
119+
eventType: {
120+
type: 'string',
121+
description: 'The type of event that triggered this workflow (e.g., insert, update, delete)',
122+
},
123+
category: { type: 'string', description: 'Record category' },
124+
record: { type: 'json', description: 'Full record data from the webhook payload' },
125+
}
126+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { ServiceNowIcon } from '@/components/icons'
2+
import { buildTriggerSubBlocks } from '@/triggers'
3+
import {
4+
buildServiceNowExtraFields,
5+
buildServiceNowWebhookOutputs,
6+
servicenowSetupInstructions,
7+
servicenowTriggerOptions,
8+
} from '@/triggers/servicenow/utils'
9+
import type { TriggerConfig } from '@/triggers/types'
10+
11+
/**
12+
* Generic ServiceNow Webhook Trigger
13+
* Captures all ServiceNow webhook events
14+
*/
15+
export const servicenowWebhookTrigger: TriggerConfig = {
16+
id: 'servicenow_webhook',
17+
name: 'ServiceNow Webhook (All Events)',
18+
provider: 'servicenow',
19+
description: 'Trigger workflow on any ServiceNow webhook event',
20+
version: '1.0.0',
21+
icon: ServiceNowIcon,
22+
23+
subBlocks: buildTriggerSubBlocks({
24+
triggerId: 'servicenow_webhook',
25+
triggerOptions: servicenowTriggerOptions,
26+
setupInstructions: servicenowSetupInstructions('Insert, Update, or Delete'),
27+
extraFields: buildServiceNowExtraFields('servicenow_webhook'),
28+
}),
29+
30+
outputs: buildServiceNowWebhookOutputs(),
31+
32+
webhook: {
33+
method: 'POST',
34+
headers: {
35+
'Content-Type': 'application/json',
36+
},
37+
},
38+
}

0 commit comments

Comments
 (0)