Skip to content

Commit d9da544

Browse files
authored
feat(triggers): add Twilio SMS, Clerk, incident.io, Rootly, RevenueCat, Loops, and Sentry webhook triggers (#5230)
* feat(triggers): add Twilio SMS, Clerk, incident.io, Rootly, RevenueCat, Loops, Sentry webhook triggers Adds inbound webhook triggers for seven existing integrations, each verified against the provider's official webhook docs (event types, payload fields, signature scheme) and aligned with Sim's trigger conventions. - Twilio SMS: inbound message + status callback (X-Twilio-Signature, HMAC-SHA1) - Clerk: user/session/organization lifecycle (Svix) - incident.io: incident created/updated/status + alert created (Svix) - Rootly: incident created/updated/resolved + alert created (HMAC-SHA256); auto-registers and tears down the webhook via the Rootly API - RevenueCat: purchase/renewal/cancellation/expiration/product-change (Authorization header); auto-registers via the RevenueCat v2 API - Loops: email lifecycle + campaign/loop/transactional sent (Svix-compatible) - Sentry: issue/error/issue-alert/metric-alert (Sentry-Hook-Signature, fail-closed) Clerk, incident.io, Loops, Twilio, and Sentry use manual signing-secret setup where the provider exposes no clean webhook-management API; Rootly and RevenueCat auto-provision so the user only supplies an API key. * fix(triggers): address review — fail-closed auth, Twilio event filtering, user-only secrets - Twilio: add matchEvent so inbound-SMS and status-callback triggers don't cross-fire on a shared webhook URL (inbound = SmsStatus 'received') - Rootly + RevenueCat: verifyAuth now fails closed (401) when the signing secret is absent from provider config (both auto-register, so it is always present after deploy) instead of skipping verification - Mark every user-provided credential field paramVisibility 'user-only' (Clerk, incident.io, RevenueCat, Sentry, Twilio) so secrets are never exposed as LLM-visible trigger params - Sentry: correct metric_alert web_url description per docs * fix(triggers): key Twilio status callbacks by SID + status for idempotency Twilio sends multiple delivery callbacks per message (sent -> delivered -> ...) sharing one MessageSid; keying idempotency on the SID alone dropped every status after the first. Status callbacks now key on SID + delivery status so each state is distinct (while still deduping Twilio's retries of the same status); inbound messages still key by SID since they fire once. * fix(triggers): require a positive signal in Twilio matchEvent routing Previously an empty MessageStatus/SmsStatus was treated as a status callback, so an ambiguous or partial payload could match twilio_sms_status (or skip twilio_sms_received). Both triggers now require a positive signal — inbound needs status 'received', status callbacks need a non-'received' status — so a payload missing both fields matches neither rather than misrouting. * fix(triggers): attach Twilio auth fields to both triggers so status callbacks are verified The Account SID / Auth Token fields were only on the primary trigger, so deploying twilio_sms_status alone never captured authToken into providerConfig and signature verification was silently skipped. Following the incident.io / Rootly / RevenueCat pattern, the auth fields are now added to each trigger conditioned on its own selectedTriggerId; the shared inner subBlock IDs keep the values persisted across trigger types. * fix(triggers): mark shared block-level API keys user-only to protect trigger secrets The trigger credential fields (RevenueCat/Rootly apiKey, Twilio authToken) are user-only, but the same-id tool-level block fields were not, so the stored secret stayed reachable as an LLM-visible block parameter. Mark those block credential fields paramVisibility 'user-only' too (matching instantly.ts) so the secret is user-only on every path. accountSid is an identifier, not a secret, so it is left as-is.
1 parent 3143a15 commit d9da544

73 files changed

Lines changed: 5879 additions & 3 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/sim/blocks/blocks/clerk.ts

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

67
export const ClerkBlock: BlockConfig<ClerkResponse> = {
78
type: 'clerk',
@@ -275,8 +276,28 @@ export const ClerkBlock: BlockConfig<ClerkResponse> = {
275276
},
276277
mode: 'advanced',
277278
},
279+
...getTrigger('clerk_user_created').subBlocks,
280+
...getTrigger('clerk_user_updated').subBlocks,
281+
...getTrigger('clerk_user_deleted').subBlocks,
282+
...getTrigger('clerk_session_created').subBlocks,
283+
...getTrigger('clerk_organization_created').subBlocks,
284+
...getTrigger('clerk_organization_membership_created').subBlocks,
285+
...getTrigger('clerk_webhook').subBlocks,
278286
],
279287

288+
triggers: {
289+
enabled: true,
290+
available: [
291+
'clerk_user_created',
292+
'clerk_user_updated',
293+
'clerk_user_deleted',
294+
'clerk_session_created',
295+
'clerk_organization_created',
296+
'clerk_organization_membership_created',
297+
'clerk_webhook',
298+
],
299+
},
300+
280301
tools: {
281302
access: [
282303
'clerk_list_users',

apps/sim/blocks/blocks/incidentio.ts

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

67
export const IncidentioBlock: BlockConfig<IncidentioResponse> = {
78
type: 'incidentio',
@@ -15,6 +16,15 @@ export const IncidentioBlock: BlockConfig<IncidentioResponse> = {
1516
integrationType: IntegrationType.Observability,
1617
bgColor: '#FFFFFF',
1718
icon: IncidentioIcon,
19+
triggers: {
20+
enabled: true,
21+
available: [
22+
'incidentio_incident_created',
23+
'incidentio_incident_updated',
24+
'incidentio_incident_status_updated',
25+
'incidentio_alert_created',
26+
],
27+
},
1828
subBlocks: [
1929
{
2030
id: 'operation',
@@ -929,6 +939,11 @@ Return ONLY the JSON array - no explanations or markdown formatting.`,
929939
password: true,
930940
required: true,
931941
},
942+
// Trigger subBlocks (webhook configuration)
943+
...getTrigger('incidentio_incident_created').subBlocks,
944+
...getTrigger('incidentio_incident_updated').subBlocks,
945+
...getTrigger('incidentio_incident_status_updated').subBlocks,
946+
...getTrigger('incidentio_alert_created').subBlocks,
932947
],
933948
tools: {
934949
access: [

apps/sim/blocks/blocks/loops.ts

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

67
export const LoopsBlock: BlockConfig<LoopsResponse> = {
78
type: 'loops',
@@ -391,7 +392,28 @@ Return ONLY the JSON object - no explanations, no extra text.`,
391392
password: true,
392393
required: true,
393394
},
395+
...getTrigger('loops_email_delivered').subBlocks,
396+
...getTrigger('loops_email_opened').subBlocks,
397+
...getTrigger('loops_email_clicked').subBlocks,
398+
...getTrigger('loops_email_hard_bounced').subBlocks,
399+
...getTrigger('loops_email_soft_bounced').subBlocks,
400+
...getTrigger('loops_campaign_email_sent').subBlocks,
401+
...getTrigger('loops_loop_email_sent').subBlocks,
402+
...getTrigger('loops_transactional_email_sent').subBlocks,
394403
],
404+
triggers: {
405+
enabled: true,
406+
available: [
407+
'loops_email_delivered',
408+
'loops_email_opened',
409+
'loops_email_clicked',
410+
'loops_email_hard_bounced',
411+
'loops_email_soft_bounced',
412+
'loops_campaign_email_sent',
413+
'loops_loop_email_sent',
414+
'loops_transactional_email_sent',
415+
],
416+
},
395417
tools: {
396418
access: [
397419
'loops_create_contact',

apps/sim/blocks/blocks/revenuecat.ts

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

67
export const RevenueCatBlock: BlockConfig<RevenueCatResponse> = {
78
type: 'revenuecat',
@@ -40,6 +41,7 @@ export const RevenueCatBlock: BlockConfig<RevenueCatResponse> = {
4041
title: 'API Key',
4142
type: 'short-input',
4243
password: true,
44+
paramVisibility: 'user-only',
4345
placeholder: 'Enter your RevenueCat API key',
4446
required: true,
4547
},
@@ -359,7 +361,24 @@ Return ONLY the numeric timestamp, no text.`,
359361
},
360362
mode: 'advanced',
361363
},
364+
...getTrigger('revenuecat_initial_purchase').subBlocks,
365+
...getTrigger('revenuecat_renewal').subBlocks,
366+
...getTrigger('revenuecat_cancellation').subBlocks,
367+
...getTrigger('revenuecat_expiration').subBlocks,
368+
...getTrigger('revenuecat_non_renewing_purchase').subBlocks,
369+
...getTrigger('revenuecat_product_change').subBlocks,
362370
],
371+
triggers: {
372+
enabled: true,
373+
available: [
374+
'revenuecat_initial_purchase',
375+
'revenuecat_renewal',
376+
'revenuecat_cancellation',
377+
'revenuecat_expiration',
378+
'revenuecat_non_renewing_purchase',
379+
'revenuecat_product_change',
380+
],
381+
},
363382
tools: {
364383
access: [
365384
'revenuecat_get_customer',

apps/sim/blocks/blocks/rootly.ts

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

67
export const RootlyBlock: BlockConfig<RootlyResponse> = {
78
type: 'rootly',
@@ -16,6 +17,15 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
1617
bgColor: '#6C72C8',
1718
iconColor: '#6C72C8',
1819
icon: RootlyIcon,
20+
triggers: {
21+
enabled: true,
22+
available: [
23+
'rootly_incident_created',
24+
'rootly_incident_updated',
25+
'rootly_incident_resolved',
26+
'rootly_alert_created',
27+
],
28+
},
1929
subBlocks: [
2030
{
2131
id: 'operation',
@@ -1640,8 +1650,14 @@ export const RootlyBlock: BlockConfig<RootlyResponse> = {
16401650
type: 'short-input',
16411651
placeholder: 'Enter your Rootly API key',
16421652
password: true,
1653+
paramVisibility: 'user-only',
16431654
required: true,
16441655
},
1656+
1657+
...getTrigger('rootly_incident_created').subBlocks,
1658+
...getTrigger('rootly_incident_updated').subBlocks,
1659+
...getTrigger('rootly_incident_resolved').subBlocks,
1660+
...getTrigger('rootly_alert_created').subBlocks,
16451661
],
16461662
tools: {
16471663
access: [

apps/sim/blocks/blocks/sentry.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { SentryIcon } from '@/components/icons'
33
import type { BlockConfig, BlockMeta } from '@/blocks/types'
44
import { AuthMode, IntegrationType } from '@/blocks/types'
55
import type { SentryResponse } from '@/tools/sentry/types'
6+
import { getTrigger } from '@/triggers'
67

78
export const SentryBlock: BlockConfig<SentryResponse> = {
89
type: 'sentry',
@@ -605,7 +606,23 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
605606
placeholder: 'Your Sentry organization slug',
606607
required: true,
607608
},
609+
610+
...getTrigger('sentry_issue_created').subBlocks,
611+
...getTrigger('sentry_issue_resolved').subBlocks,
612+
...getTrigger('sentry_error_created').subBlocks,
613+
...getTrigger('sentry_issue_alert').subBlocks,
614+
...getTrigger('sentry_metric_alert').subBlocks,
608615
],
616+
triggers: {
617+
enabled: true,
618+
available: [
619+
'sentry_issue_created',
620+
'sentry_issue_resolved',
621+
'sentry_error_created',
622+
'sentry_issue_alert',
623+
'sentry_metric_alert',
624+
],
625+
},
609626
tools: {
610627
access: [
611628
'sentry_issues_list',

apps/sim/blocks/blocks/twilio.ts

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

67
export const TwilioSMSBlock: BlockConfig<TwilioSMSBlockOutput> = {
78
type: 'twilio_sms',
@@ -43,6 +44,7 @@ export const TwilioSMSBlock: BlockConfig<TwilioSMSBlockOutput> = {
4344
type: 'short-input',
4445
placeholder: 'Your Twilio Auth Token',
4546
password: true,
47+
paramVisibility: 'user-only',
4648
required: true,
4749
},
4850
{
@@ -52,7 +54,13 @@ export const TwilioSMSBlock: BlockConfig<TwilioSMSBlockOutput> = {
5254
placeholder: 'e.g. +1234567890',
5355
required: true,
5456
},
57+
...getTrigger('twilio_sms_received').subBlocks,
58+
...getTrigger('twilio_sms_status').subBlocks,
5559
],
60+
triggers: {
61+
enabled: true,
62+
available: ['twilio_sms_received', 'twilio_sms_status'],
63+
},
5664
tools: {
5765
access: ['twilio_send_sms'],
5866
config: {

0 commit comments

Comments
 (0)