@@ -552,6 +552,115 @@ All fields automatically have:
552552- ` mode: 'trigger' ` - Only shown in trigger mode
553553- ` condition: { field: 'selectedTriggerId', value: triggerId } ` - Only shown when this trigger is selected
554554
555+ ## Webhook Provider Handler (Optional )
556+
557+ If the service requires ** custom webhook auth ** (HMAC signatures , token validation ), ** event matching ** (filtering by trigger type ), or ** idempotency dedup ** , create a provider handler in the webhook provider registry .
558+
559+ ### Directory
560+
561+ ```
562+ apps/sim/lib/webhooks/providers/
563+ ├── types.ts # WebhookProviderHandler interface
564+ ├── utils.ts # Shared helpers (createHmacVerifier, verifyTokenAuth, skipByEventTypes)
565+ ├── registry.ts # Handler map + default handler
566+ ├── index.ts # Barrel export
567+ └── {service}.ts # Your provider handler
568+ ```
569+
570+ ### When to Create a Handler
571+
572+ | Behavior | Method to implement | Example providers |
573+ |---|---|---|
574+ | HMAC signature auth | `verifyAuth` via `createHmacVerifier` | Ashby, Jira, Linear, Typeform |
575+ | Custom token auth | `verifyAuth` via `verifyTokenAuth` | Generic, Google Forms |
576+ | Event type filtering | `matchEvent` | GitHub, Jira, Confluence, Attio, HubSpot |
577+ | Event skip by type list | `shouldSkipEvent` via `skipByEventTypes` | Stripe, Grain |
578+ | Idempotency dedup | `extractIdempotencyId` | Slack, Stripe, Linear, Jira |
579+ | Custom success response | `formatSuccessResponse` | Slack, Twilio Voice, Microsoft Teams |
580+ | Custom error format | `formatErrorResponse` | Microsoft Teams |
581+
582+ If none of these apply, you do NOT need a handler file. The default handler provides bearer token auth for providers that set `providerConfig.token`.
583+
584+ ### Simple Example: HMAC Auth Only
585+
586+ ```typescript
587+ import type { WebhookProviderHandler } from '@/lib/webhooks/providers/types'
588+ import { createHmacVerifier } from '@/lib/webhooks/providers/utils'
589+ import { validate{Service}Signature } from '@/lib/webhooks/utils.server'
590+
591+ export const {service}Handler: WebhookProviderHandler = {
592+ verifyAuth: createHmacVerifier({
593+ configKey: 'webhookSecret',
594+ headerName: 'X-{Service}-Signature',
595+ validateFn: validate{Service}Signature,
596+ providerLabel: '{Service}',
597+ }),
598+ }
599+ ```
600+
601+ ### Example: Auth + Event Matching + Idempotency
602+
603+ ``` typescript
604+ import { createLogger } from ' @sim/logger'
605+ import type { EventMatchContext , WebhookProviderHandler } from ' @/lib/webhooks/providers/types'
606+ import { createHmacVerifier } from ' @/lib/webhooks/providers/utils'
607+ import { validate {Service }Signature } from ' @/lib/webhooks/utils.server'
608+
609+ const logger = createLogger (' WebhookProvider:{Service}' )
610+
611+ export const {service}Handler: WebhookProviderHandler = {
612+ verifyAuth: createHmacVerifier ({
613+ configKey: ' webhookSecret' ,
614+ headerName: ' X-{Service}-Signature' ,
615+ validateFn: validate {Service }Signature ,
616+ providerLabel: ' {Service}' ,
617+ }),
618+
619+ async matchEvent({ webhook , workflow , body , requestId , providerConfig }: EventMatchContext ) {
620+ const triggerId = providerConfig .triggerId as string | undefined
621+ const obj = body as Record <string , unknown >
622+
623+ if (triggerId && triggerId !== ' {service}_webhook' ) {
624+ const { is{Service}EventMatch } = await import (' @/triggers/{service}/utils' )
625+ if (! is {Service }EventMatch (triggerId , obj )) {
626+ logger .debug (
627+ ` [${requestId }] {Service} event mismatch for trigger ${triggerId }. Skipping. ` ,
628+ { webhookId: webhook .id , workflowId: workflow .id , triggerId }
629+ )
630+ return false
631+ }
632+ }
633+
634+ return true
635+ },
636+
637+ extractIdempotencyId(body : unknown ) {
638+ const obj = body as Record <string , unknown >
639+ if (obj .id && obj .type ) {
640+ return ` ${obj .type }:${obj .id } `
641+ }
642+ return null
643+ },
644+ }
645+ ```
646+
647+ ### Registering the Handler
648+
649+ In ` apps/sim/lib/webhooks/providers/registry.ts ` :
650+
651+ ``` typescript
652+ import { {service }Handler } from ' @/lib/webhooks/providers/{service}'
653+
654+ const PROVIDER_HANDLERS: Record <string , WebhookProviderHandler > = {
655+ // ... existing providers (alphabetical) ...
656+ {service }: {service }Handler ,
657+ }
658+ ```
659+
660+ ### Adding a Signature Validator
661+
662+ If the service uses HMAC signatures, add a ` validate{Service}Signature ` function in ` apps/sim/lib/webhooks/utils.server.ts ` alongside the existing validators. Then reference it from your handler via ` createHmacVerifier ` .
663+
555664## Trigger Outputs & Webhook Input Formatting
556665
557666### Important: Two Sources of Truth
@@ -696,6 +805,14 @@ export const {service}WebhookTrigger: TriggerConfig = {
696805- [ ] Added ` delete{Service}Webhook ` function to ` provider-subscriptions.ts `
697806- [ ] Added provider to ` cleanupExternalWebhook ` function
698807
808+ ### Webhook Provider Handler (if needed)
809+ - [ ] Created ` apps/sim/lib/webhooks/providers/{service}.ts ` handler file
810+ - [ ] Registered handler in ` apps/sim/lib/webhooks/providers/registry.ts ` (alphabetical)
811+ - [ ] Used ` createHmacVerifier ` from ` providers/utils ` for HMAC-based auth
812+ - [ ] Used ` verifyTokenAuth ` from ` providers/utils ` for token-based auth
813+ - [ ] Added ` validate{Service}Signature ` in ` utils.server.ts ` (if HMAC auth needed)
814+ - [ ] Event matching uses dynamic ` await import() ` for trigger utils
815+
699816### Webhook Input Formatting
700817- [ ] Added handler in ` apps/sim/lib/webhooks/utils.server.ts ` (if custom formatting needed)
701818- [ ] Handler returns fields matching trigger ` outputs ` exactly
0 commit comments