트리거(Trigger) 플러그인
트리거(Trigger) 플러그인
서드파티 웹훅 이벤트를 워크플로 시작 신호로 바꾸는 Dify 1.10.0+ 트리거 플러그인을 만들어 봐요.
출처: 공식문서
트리거 플러그인이 뭔가요?
트리거는 Dify v1.10.0에서 새 유형의 시작 노드로 도입됐어요. Code, Tool, Knowledge Retrieval 같은 기능 노드와 달리, 트리거는 서드파티 이벤트를 Dify가 인식·처리할 수 있는 입력 형식으로 변환해요.
예를 들어 Gmail에서 Dify를 new email 이벤트 수신자로 설정하면, 새 이메일이 올 때마다 Gmail이 자동으로 Dify에 이벤트를 보내고 그 이벤트가 워크플로를 발화할 수 있어요. 그런데:
- Gmail의 원래 이벤트 형식은 Dify 입력 형식과 호환되지 않아요.
- 전 세계에 수천 개의 플랫폼이 있고, 각자 고유한 이벤트 형식을 갖고 있어요.
트리거 플러그인이 이 격차를 메워요. 각 플랫폼의 이벤트를 정의하고 파싱해서, Dify가 받아들일 수 있는 입력 형식으로 통일해 주거든요.
기술 개요
Dify 트리거는 웹 전반에 널리 쓰이는 웹훅 위에 만들어져요. GitHub, Slack, Linear 같은 주류 SaaS 플랫폼은 웹훅을 지원하고 문서화도 잘 돼 있어요.
웹훅은 HTTP 기반 이벤트 디스패처예요. 이벤트 수신 주소를 설정하면, 구독한 이벤트가 발생할 때마다 플랫폼이 자동으로 그 주소에 이벤트 데이터를 푸시해요.
다른 플랫폼의 웹훅 이벤트를 통일된 방식으로 다루기 위해 Dify는 두 가지 핵심 개념을 정의해요: **Subscription(구독)**과 Event(이벤트).
- Subscription: 서드파티 플랫폼의 개발자 콘솔에서 Dify의 네트워크 주소를 대상 서버로 등록하는 설정.
- Event: 플랫폼이 이메일 수신, 이메일 삭제, 이메일 읽음 처리 같은 여러 유형의 이벤트를 보낼 수 있는데, 전부 등록된 주소로 푸시돼요. 트리거 플러그인은 여러 이벤트 유형을 다룰 수 있고, 각 이벤트는 Dify 워크플로의 Plugin Trigger 노드 하나에 대응해요.
플러그인 개발
트리거 플러그인 개발은 다른 플러그인 유형(Tool, Data Source, Model 등)과 같은 과정을 따라요.
dify plugin init 명령으로 개발 템플릿을 만들어요. 생성된 파일 구조는 표준 플러그인 형식 스펙을 따릅니다.
├── _assets
│ └── icon.svg
├── events
│ └── star
│ ├── star_created.py
│ └── star_created.yaml
├── main.py
├── manifest.yaml
├── provider
│ ├── github.py
│ └── github.yaml
├── README.md
├── PRIVACY.md
└── requirements.txt
manifest.yaml: 플러그인의 기본 메타데이터를 설명해요.provider디렉토리: 프로바이더의 메타데이터, 구독 생성 코드, 웹훅 요청 수신 후 이벤트를 분류하는 코드를 담아요.events디렉토리: 이벤트 처리·필터링 코드를 담아요. 노드 레벨에서 로컬 이벤트 필터링을 지원하죠. 관련 이벤트를 묶으려면 하위 디렉토리를 만들면 돼요.
📝 트리거 플러그인은 최소 요구 Dify 버전을
1.10.0으로, SDK 버전을>= 0.6.0으로 설정하세요.
다음 섹션들은 GitHub를 예시로 개발 과정을 따라갑니다.
구독(Subscription) 만들기
웹훅 설정 방법은 주류 SaaS 플랫폼마다 크게 달라요.
- 어떤 플랫폼(GitHub 등)은 API 기반 웹훅 설정을 지원해요. 이런 플랫폼은 OAuth 인증이 끝나면 Dify가 자동으로 웹훅을 설정할 수 있어요.
- 다른 플랫폼(Notion 등)은 웹훅 설정 API를 제공하지 않아 수동 인증이 필요할 수 있어요.
이 차이를 수용하기 위해 구독 과정을 Subscription Constructor와 Subscription 두 부분으로 나눠요.
Notion 같은 플랫폼에서는 구독을 만들려면 사용자가 Dify가 제공하는 콜백 URL을 직접 복사해 Notion 워크스페이스에 붙여넣어 웹훅 설정을 완료해야 해요. 이 과정이 Dify 인터페이스의 Paste URL to create a new subscription 옵션에 해당해요.
수동 URL 붙여넣기로 구독 생성이 되도록 하려면 두 파일(github.yaml, github.py)을 수정해요.
github.yaml — GitHub 웹훅은 암호화 메커니즘을 써서, 들어오는 요청을 복호화·검증하려면 비밀 키가 필요해요. github.yaml에 webhook_secret을 선언해요.
subscription_schema:
- name: "webhook_secret"
type: "secret-input"
required: false
label:
zh_Hans: "Webhook Secret"
en_US: "Webhook Secret"
ja_JP: "Webhookシークレット"
help:
en_US: "Optional webhook secret for validating GitHub webhook requests"
ja_JP: "GitHub Webhookリクエストの検証用のオプションのWebhookシークレット"
zh_Hans: "可选的用于验证 GitHub webhook 请求的 webhook 密钥"
github.py — 먼저 dispatch_event 인터페이스를 구현해요. 콜백 URL로 보내진 모든 요청은 이 인터페이스가 처리하고, 처리된 이벤트는 디버깅·검증을 위해 Request Logs 섹션에 표시돼요.
코드에서 github.yaml에 선언한 webhook_secret은 subscription.properties를 통해 가져올 수 있어요.
dispatch_event 메서드는 요청 내용에서 이벤트 유형을 판별해요. 아래 예시에서 _dispatch_trigger_event 메서드가 그 추출을 담당합니다.
💡 완전한 코드 샘플은 Dify의 GitHub 트리거 플러그인을 참고하세요.
class GithubTrigger(Trigger):
"""Handle GitHub webhook event dispatch."""
def _dispatch_event(self, subscription: Subscription, request: Request) -> EventDispatch:
webhook_secret = subscription.properties.get("webhook_secret")
if webhook_secret:
self._validate_signature(request=request, webhook_secret=webhook_secret)
event_type: str | None = request.headers.get("X-GitHub-Event")
if not event_type:
raise TriggerDispatchError("Missing GitHub event type header")
payload: Mapping[str, Any] = self._validate_payload(request)
response = Response(response='{"status": "ok"}', status=200, mimetype="application/json")
event: str = self._dispatch_trigger_event(event_type=event_type, payload=payload)
return EventDispatch(events=[event] if event else [], response=response)
이벤트 처리
이벤트가 추출되면, 해당 구현이 원래 HTTP 요청을 필터링하고 Dify 워크플로가 받아들일 입력 형식으로 변환해야 해요.
Issue 이벤트를 예시로, events/issues/issues.yaml에 이벤트를 정의하고 events/issues/issues.py에 구현을 넣어요. 이벤트의 출력은 issues.yaml의 output_schema 섹션에 정의하는데, tool 플러그인과 같은 JSON Schema 스펙을 따라요.
issues.yaml
identity:
name: issues
author: langgenius
label:
en_US: Issues
zh_Hans: 议题
ja_JP: イシュー
description:
en_US: Unified issues event with actions filter
zh_Hans: 带 actions 过滤的统一 issues 事件
ja_JP: アクションフィルタ付きの統合イシューイベント
output_schema:
type: object
properties:
action:
type: string
issue:
type: object
description: The issue itself
extra:
python:
source: events/issues/issues.py
issues.py
from collections.abc import Mapping
from typing import Any
from werkzeug import Request
from dify_plugin.entities.trigger import Variables
from dify_plugin.errors.trigger import EventIgnoreError
from dify_plugin.interfaces.trigger import Event
class IssuesUnifiedEvent(Event):
"""Unified Issues event. Filters by actions and common issue attributes."""
def _on_event(self, request: Request, parameters: Mapping[str, Any], payload: Mapping[str, Any]) -> Variables:
payload = request.get_json()
if not payload:
raise ValueError("No payload received")
allowed_actions = parameters.get("actions") or []
action = payload.get("action")
if allowed_actions and action not in allowed_actions:
raise EventIgnoreError()
issue = payload.get("issue")
if not isinstance(issue, Mapping):
raise ValueError("No issue in payload")
return Variables(variables={**payload})
이벤트 필터링
특정 이벤트를 걸러내려면(예: 특정 라벨이 붙은 Issue 이벤트만 보려면) issues.yaml의 이벤트 정의에 parameters를 추가하고, _on_event 메서드에서 설정한 조건에 맞지 않는 이벤트에 EventIgnoreError 예외를 던져요.
issues.yaml
parameters:
- name: added_label
label:
en_US: Added Label
zh_Hans: 添加的标签
ja_JP: 追加されたラベル
type: string
required: false
description:
en_US: "Only trigger if these specific labels were added (e.g., critical, priority-high, security, comma-separated). Leave empty to trigger for any label addition."
zh_Hans: "仅当添加了这些特定标签时触发(例如:critical, priority-high, security,逗号分隔)。留空则对任何标签添加触发。"
ja_JP: "これらの特定のラベルが追加された場合のみトリガー(例: critical, priority-high, security,カンマ区切り)。空の場合は任意のラベル追加でトリガー。"
issues.py
def _check_added_label(self, payload: Mapping[str, Any], added_label_param: str | None) -> None:
"""Check if the added label matches the allowed labels"""
if not added_label_param:
return
allowed_labels = [label.strip() for label in added_label_param.split(",") if label.strip()]
if not allowed_labels:
return
# The payload contains the label that was added
label = payload.get("label", {})
label_name = label.get("name", "")
if label_name not in allowed_labels:
raise EventIgnoreError()
def _on_event(self, request: Request, parameters: Mapping[str, Any], payload: Mapping[str, Any]) -> Variables:
# ...
# Apply all filters
self._check_added_label(payload, parameters.get("added_label"))
return Variables(variables={**payload})
OAuth 또는 API 키로 구독 생성
OAuth나 API 키로 자동 구독 생성을 켜려면 github.yaml과 github.py 파일을 수정해요.
github.yaml — 다음 필드를 추가해요.
subscription_constructor:
parameters:
- name: "repository"
label:
en_US: "Repository"
zh_Hans: "仓库"
ja_JP: "リポジトリ"
type: "dynamic-select"
required: true
placeholder:
en_US: "owner/repo"
zh_Hans: "owner/repo"
ja_JP: "owner/repo"
help:
en_US: "GitHub repository in format owner/repo (e.g., microsoft/vscode)"
zh_Hans: "GitHub 仓库,格式为 owner/repo(例如:microsoft/vscode)"
ja_JP: "GitHubリポジトリは owner/repo 形式で入力してください(例: microsoft/vscode)"
credentials_schema:
access_tokens:
help:
en_US: Get your Access Tokens from GitHub
ja_JP: GitHub からアクセストークンを取得してください
zh_Hans: 从 GitHub 获取您的 Access Tokens
label:
en_US: Access Tokens
ja_JP: アクセストークン
zh_Hans: Access Tokens
placeholder:
en_US: Please input your GitHub Access Tokens
ja_JP: GitHub のアクセストークンを入力してください
zh_Hans: 请输入你的 GitHub Access Tokens
required: true
type: secret-input
url: https://github.com/settings/tokens?type=beta
extra:
python:
source: provider/github.py
subscription_constructor는 구독이 어떻게 구성되는지 정의하기 위해 Dify가 추상화한 개념이에요. 다음 필드를 포함합니다.
parameters(선택): 구독을 만드는 데 필요한 파라미터를 정의해요. 예: 구독할 이벤트 유형이나 대상 GitHub 저장소.credentials_schema(선택): API 키나 액세스 토큰으로 구독을 만드는 데 필요한 자격증명을 선언해요. GitHub의access_tokens처럼요.oauth_schema(선택): OAuth로 구독을 만들 때 필요해요. 정의 방법은 도구 플러그인에 OAuth 지원 추가를 참고하세요.
github.py — 자동 구독 로직을 구현하려면 Constructor 클래스를 만들어요.
class GithubSubscriptionConstructor(TriggerSubscriptionConstructor):
"""Manage GitHub trigger subscriptions."""
def _validate_api_key(self, credentials: Mapping[str, Any]) -> None:
# ...
def _create_subscription(
self,
endpoint: str,
parameters: Mapping[str, Any],
credentials: Mapping[str, Any],
credential_type: CredentialType,
) -> Subscription:
repository = parameters.get("repository")
if not repository:
raise ValueError("repository is required (format: owner/repo)")
try:
owner, repo = repository.split("/")
except ValueError:
raise ValueError("repository must be in format 'owner/repo'") from None
events: list[str] = parameters.get("events", [])
webhook_secret = uuid.uuid4().hex
url = f"https://api.github.com/repos/{owner}/{repo}/hooks"
headers = {
"Authorization": f"Bearer {credentials.get('access_tokens')}",
"Accept": "application/vnd.github+json",
}
webhook_data = {
"name": "web",
"active": True,
"events": events,
"config": {"url": endpoint, "content_type": "json", "insecure_ssl": "0", "secret": webhook_secret},
}
try:
response = requests.post(url, json=webhook_data, headers=headers, timeout=10)
except requests.RequestException as exc:
raise SubscriptionError(f"Network error while creating webhook: {exc}", error_code="NETWORK_ERROR") from exc
if response.status_code == 201:
webhook = response.json()
return Subscription(
expires_at=int(time.time()) + self._WEBHOOK_TTL,
endpoint=endpoint,
parameters=parameters,
properties={
"external_id": str(webhook["id"]),
"repository": repository,
"events": events,
"webhook_secret": webhook_secret,
"active": webhook.get("active", True),
},
)
response_data: dict[str, Any] = response.json() if response.content else {}
error_msg = response_data.get("message", "Unknown error")
error_details = response_data.get("errors", [])
detailed_error = f"Failed to create GitHub webhook: {error_msg}"
if error_details:
detailed_error += f" Details: {error_details}"
raise SubscriptionError(
detailed_error,
error_code="WEBHOOK_CREATION_FAILED",
external_response=response_data,
)
이 두 파일을 수정하면 Dify 인터페이스에 Create with API Key 옵션이 보여요.
같은 Constructor 클래스는 OAuth로 자동 구독 생성도 지원해요. subscription_constructor 아래에 oauth_schema 필드를 추가하면 OAuth 인증이 켜집니다.
핵심 클래스 인터페이스
트리거 플러그인 개발에서 핵심 클래스들의 인터페이스 정의는 아래와 같아요. (자세한 코드는 원문 참고)
Trigger:_dispatch_event추상 메서드를 구현해 웹훅 요청을 검증하고(서명/HMAC 확인), 헤더나 본문에서 이벤트 유형을 추출한 뒤, 호출할 이벤트 목록과 적절한 HTTP 응답을 담은EventDispatch를 돌려줘요.TriggerSubscriptionConstructor:_create_subscription(구독 생성)과_delete_subscription(구독 해제)이 추상 메서드예요._validate_api_key, OAuth 관련 메서드(_oauth_get_authorization_url등),_fetch_parameter_options는 선택 구현이에요.Event:_on_event추상 메서드가 수신한 웹훅 요청을 **구조화된Variables**로 변환해요. 필터링 조건에 안 맞으면EventIgnoreError를, 페이로드가 잘못됐으면ValueError를 던져요.