A lightweight Rust service that enables multiple n8n workflows to share a single inbound webhook for Slack, Jira, GitHub, and Zoom. It receives all events at one endpoint per service and intelligently routes them to matching n8n workflows based on their trigger configurations.
n8n's built-in trigger nodes (Slack Trigger, Jira Trigger, GitHub Trigger) create unique webhooks for each workflow. External services like Slack, Jira, and GitHub only support a single event subscription URL per app/instance, which forces organizations to:
- Slack: Create separate Slack apps for each workflow, manage multiple OAuth credentials, and deal with complex app approval processes
- Jira: Register separate webhooks in Jira for each workflow, leading to webhook sprawl and management overhead
- GitHub: Register separate webhooks per repository per workflow, causing webhook sprawl across repositories
- Zoom: Configure a single Event Subscriptions URL per Zoom app, but need many workflows with Zoom Trigger nodes — and account-wide events differ from Slack's bot-scoped visibility
This is administratively unworkable for organizations with multiple event-triggered workflows.
Unihook acts as a router between external services and n8n:
- Single Webhook: Register one URL per service (Slack, Jira, GitHub, Zoom)
- Dynamic Discovery: Automatically discovers n8n workflows with matching triggers via the n8n API
- Smart Routing: Forwards events only to workflows whose trigger configuration matches the event
- Zero Execution Waste: Events that don't match any trigger never reach n8n
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Slack Events │────▶│ │────▶│ n8n Workflow A │
│ API │ │ │────▶│ n8n Workflow B │
└─────────────────┘ │ │ └─────────────────┘
│ │
┌─────────────────┐ │ Unihook │ ┌─────────────────┐
│ Jira Webhooks │────▶│ Router │────▶│ n8n Workflow C │
│ │ │ │────▶│ n8n Workflow D │
└─────────────────┘ │ │ └─────────────────┘
│ │
┌─────────────────┐ │ │ ┌─────────────────┐
│ GitHub Webhooks │────▶│ │────▶│ n8n Workflow E │
│ │ │ │────▶│ n8n Workflow F │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│
▼
┌──────────────────┐
│ n8n API │
│ (discover │
│ triggers) │
└──────────────────┘
- Docker and Docker Compose (recommended)
- OR Rust 1.86+ for local development
- n8n instance with API access enabled
- n8n API key
- Clone the repository:
git clone https://github.com/your-org/n8n-unihook.git
cd n8n-unihook- Create a
.envfile:
# Required
N8N_API_KEY=your-n8n-api-key
ZOOM_WEBHOOK_SECRET=your-zoom-secret-token
ZOOM_ALLOWED_EVENTS=meeting.started,meeting.ended,recording.completed
# Optional (defaults shown)
N8N_API_URL=http://n8n:5678
REFRESH_INTERVAL_SECS=60
RUST_LOG=n8n_slack_unihook=info
# Inbound webhook signature verification (optional but recommended for GitHub)
# GITHUB_WEBHOOK_SECRET=your-github-webhook-secret- Start the service:
docker-compose up -ddocker build -t n8n-unihook .
docker run -d \
--name n8n-unihook \
-p 3000:3000 \
-e N8N_API_KEY=your-n8n-api-key \
-e N8N_API_URL=http://your-n8n-host:5678 \
n8n-unihook# Set environment variables
export N8N_API_KEY=your-n8n-api-key
export N8N_API_URL=http://localhost:5678
export ZOOM_WEBHOOK_SECRET=your-zoom-secret-token
export ZOOM_ALLOWED_EVENTS=meeting.started,meeting.ended,recording.completed
# Build and run
cargo run| Environment Variable | Required | Default | Description |
|---|---|---|---|
N8N_API_KEY |
Yes | - | Your n8n API key |
N8N_API_URL |
No | http://localhost:5678 |
n8n instance URL |
LISTEN_ADDR |
No | 0.0.0.0:3000 |
Address to bind the HTTP server |
REFRESH_INTERVAL_SECS |
No | 60 |
How often to refresh trigger configs |
N8N_ENDPOINT_WEBHOOK |
No | webhook |
n8n production webhook path segment |
N8N_ENDPOINT_WEBHOOK_TEST |
No | webhook-test |
n8n test webhook path segment |
GITHUB_WEBHOOK_SECRET |
No | - | Shared secret for verifying inbound GitHub webhooks (HMAC-SHA256 via X-Hub-Signature-256) |
ZOOM_WEBHOOK_SECRET |
Yes | - | Zoom app Secret Token for URL validation and inbound signature verification |
ZOOM_ALLOWED_EVENTS |
Yes | - | Comma-separated Zoom event types Unihook may forward (platform allowlist) |
ZOOM_PRIVILEGED_USERS |
No | - | Comma-separated emails whose personal-project Zoom workflows receive all allowlisted events (bypasses host routing) |
ZOOM_PRIVILEGED_WORKFLOW_IDS |
No | - | Comma-separated n8n workflow IDs whose Zoom triggers receive all allowlisted events (bypasses host routing; use for team-project admin catch-alls) |
RUST_LOG |
No | n8n_slack_unihook=info |
Log level |
-
Create or configure your Slack App at api.slack.com/apps
-
Enable Event Subscriptions:
- Go to "Event Subscriptions"
- Toggle "Enable Events" to On
- Set the Request URL to:
https://your-domain.com/slack/events - Slack will send a verification challenge — Unihook handles this automatically
-
Subscribe to Events:
- Add the bot events your workflows need:
message.channels— New messages in public channelsapp_mention— When your bot is @mentionedreaction_added— Reactions added to messagesfile_shared— Files shared in channelschannel_created— New channels createdteam_join— New users joining the workspace
- Add the bot events your workflows need:
-
Install the App to your workspace
Unihook queries the n8n API to discover workflows with Slack Trigger nodes. For each trigger, it extracts:
- Event type (message, reaction, mention, etc.)
- Channel filter (specific channels or workspace-wide)
- Watch Whole Workspace setting
When an event arrives:
- Extract the event type and channel from the Slack payload
- Match against all discovered triggers
- Forward to workflows where:
- Event type matches, AND
- Channel matches (or trigger watches whole workspace)
| Slack Event | n8n Trigger Setting |
|---|---|
message |
"New Message Posted to Channel" |
app_mention |
"Bot/App Mention" |
reaction_added |
"Reaction Added" |
file_shared |
"File Shared" |
file_public |
"File Made Public" |
channel_created |
"New Public Channel Created" |
team_join |
"New User Created" |
* |
"Any Event" |
You need to understand this before setting up Jira workflows.
n8n's Jira Trigger node unconditionally calls the Jira REST API to register its own webhooks whenever a workflow is activated (and deregister them on deactivation). This is hardcoded in the node's lifecycle hooks (
JiraTrigger.node.tswebhookMethods.default.checkExists/create/delete) and there is no environment variable or configuration option in n8n to disable it. If these API calls fail, workflow activation fails entirely — n8n does not gracefully degrade.This creates two problems when using Unihook:
- Duplicate webhooks — You register a webhook in Jira pointing at Unihook (
/jira/events), but n8n also registers its own webhooks pointing directly at n8n, bypassing Unihook entirely.- Activation failures — If n8n cannot reach the Jira API (e.g. network restrictions, user permissions), workflows with Jira Trigger nodes cannot be activated at all.
The workaround is to configure the Jira credential in n8n with a domain that does not point to your real Jira instance. Instead, point it at a lightweight service that returns the minimal API responses n8n expects. This satisfies n8n's webhook registration calls without creating real webhooks in Jira or causing activation failures. Unihook handles all actual event routing — the credential is only needed to keep n8n happy.
See Jira Credential Workaround below for the exact setup.
Note: The Slack Trigger node does not have this problem — it does not call out to Slack during workflow activation, so no workaround is needed for Slack.
-
Register a webhook in your Jira instance:
- Go to Settings → System → WebHooks (Jira Server/Data Center) or use the Jira REST API
- Set the URL to:
https://your-domain.com/jira/events - If you use n8n's
authenticateWebhookfeature, append the same query parameter to this URL (see Query Parameter Forwarding) - Select the events you want to forward (or select all)
-
Set up the Jira credential workaround in n8n (see below)
-
Create n8n workflows with Jira Trigger nodes:
- Add a "Jira Trigger" node to your workflow
- Configure the trigger's Events to match the events you want (e.g.
jira:issue_created,comment_created, or*for all) - Attach the workaround Jira credential (not a real one)
- Activate the workflow
-
Unihook routes events to matching workflows based on the
webhookEventfield in the Jira payload.
Because n8n's Jira Trigger node unconditionally registers webhooks via the Jira REST API during workflow activation (see above), you need a service that responds to those API calls. Unihook includes built-in mock endpoints for this purpose — no separate mock server is required.
What n8n calls during the Jira Trigger lifecycle:
| When | Method | Endpoint | Expected Response |
|---|---|---|---|
| Credential validation | GET |
/rest/api/2/myself |
200 with a JSON user object |
| Workflow activation | GET |
/rest/webhooks/1.0/webhook |
200 with [] (empty array) |
| Workflow activation | POST |
/rest/webhooks/1.0/webhook |
201 with a JSON webhook object containing a self URL |
| Workflow deactivation | DELETE |
/rest/webhooks/1.0/webhook/{id} |
204 |
Unihook serves all of these endpoints natively (see src/routes/provider_jira.rs).
Create the Jira credential in n8n with its domain pointing at Unihook:
| Field | Value | Notes |
|---|---|---|
| Type | Jira Software Cloud API |
|
| Domain | http://your-unihook-host:3000 |
Points at Unihook, not real Jira |
noop@example.com |
Arbitrary — the mock accepts anything | |
| API Token | noop |
Arbitrary — the mock accepts anything |
Attach this credential to your Jira Trigger nodes. When n8n activates the workflow, its webhook registration calls hit Unihook's mock endpoints and succeed silently. Unihook handles all actual event delivery from Jira.
Unihook discovers workflows with Jira Trigger nodes via the n8n API. For each trigger, it extracts:
- Event types — The list of Jira event types the trigger listens for (e.g.
jira:issue_created,comment_updated,*)
When a Jira webhook event arrives at /jira/events:
- Extract the
webhookEventfield from the payload - Match against all discovered Jira triggers
- Forward to workflows where the event type matches (exact match or wildcard
*)
| Category | Events |
|---|---|
| Issues | jira:issue_created, jira:issue_updated, jira:issue_deleted |
| Comments | comment_created, comment_updated, comment_deleted |
| Boards | board_created, board_updated, board_deleted, board_configuration_changed |
| Sprints | sprint_created, sprint_started, sprint_updated, sprint_closed, sprint_deleted |
| Projects | project_created, project_updated, project_deleted |
| Versions | jira:version_created, jira:version_updated, jira:version_released, jira:version_unreleased, jira:version_moved, jira:version_deleted |
| Users | user_created, user_updated, user_deleted |
| Worklogs | worklog_created, worklog_updated, worklog_deleted |
| Issue Links | issuelink_created, issuelink_deleted |
| Options | option_voting_changed, option_watching_changed, option_unassigned_issues_changed, option_subtasks_changed, option_attachments_changed, option_issuelinks_changed, option_timetracking_changed |
| Wildcard | * (matches all events) |
You need to understand this before setting up GitHub workflows.
Like Jira, n8n's GitHub Trigger node calls the GitHub REST API to register its own webhooks whenever a workflow is activated. This is hardcoded in the node's lifecycle hooks (
GithubTrigger.node.ts). If these API calls fail, workflow activation fails entirely.Additionally, n8n's GitHub Trigger generates a random HMAC secret during webhook registration and verifies every incoming payload against it using the
X-Hub-Signature-256header. This verification cannot be disabled — if the signature is missing or invalid, n8n returns401 Unauthorized.Since the user's GitHub webhook (pointing at Unihook) uses a different secret than the one n8n generated, the original signature from GitHub won't pass n8n's verification. Unihook solves this by re-signing each forwarded payload with n8n's per-workflow secret, which it reads from the workflow's
staticDatavia the n8n API.See ADR-001: GitHub Webhook Payload Re-signing for the full technical rationale.
The workaround for webhook registration is the same pattern as Jira: point the GitHub credential in n8n at a mock service instead of real GitHub.
Note: The Slack Trigger node does not have this problem — it does not call out to Slack during workflow activation, so no workaround is needed for Slack.
-
Create a webhook on your GitHub repository (or organisation):
- Go to Settings → Webhooks → Add webhook
- Set the Payload URL to:
https://your-domain.com/github/events - Set Content type to
application/json - (Recommended) Set a secret — Unihook can verify inbound signatures when
GITHUB_WEBHOOK_SECRETis set (see Inbound Signature Verification) - Select the events you want to forward (or select "Send me everything")
-
Set up the GitHub credential workaround in n8n (see below)
-
Create n8n workflows with GitHub Trigger nodes:
- Add a "GitHub Trigger" node to your workflow
- Configure the Owner and Repository to match the repository the webhook is on
- Configure the trigger's Events to match the events you want (e.g.
push,issues, or*for all) - Attach the workaround GitHub credential (not a real one)
- Activate the workflow
-
Unihook routes events to matching workflows based on the
X-GitHub-Eventheader and the repository owner/name in the payload.
Because n8n's GitHub Trigger node registers webhooks via the GitHub REST API during workflow activation (see above), you need a service that responds to those API calls. Unihook includes built-in mock endpoints for this purpose — no separate mock server is required. As a bonus, Unihook's GitHub mock intercepts the HMAC secret that n8n generates during webhook creation and stores it in its SQLite database, enabling automatic payload re-signing without relying on n8n's staticData.
What n8n calls during the GitHub Trigger lifecycle:
| When | Method | Endpoint | Expected Response |
|---|---|---|---|
| Credential validation | GET |
/user |
200 with a JSON user object |
| Webhook check | GET |
/repos/{owner}/{repo}/hooks |
200 with [] (empty array) |
| Webhook creation | POST |
/repos/{owner}/{repo}/hooks |
201 with a JSON webhook object containing id |
| Webhook deletion | DELETE |
/repos/{owner}/{repo}/hooks/{id} |
204 |
Unihook serves all of these endpoints natively (see src/routes/provider_github.rs). When POST /repos/{owner}/{repo}/hooks is called, Unihook extracts the webhook_id from config.url and the secret from config.secret, storing both in the database for later re-signing.
Create the GitHub credential in n8n with its server pointing at Unihook:
| Field | Value | Notes |
|---|---|---|
| Type | GitHub API |
|
| Server | http://your-unihook-host:3000 |
Points at Unihook, not real GitHub |
| User | noop |
Arbitrary — the mock accepts anything |
| Access Token | noop |
Arbitrary — the mock accepts anything |
Attach this credential to your GitHub Trigger nodes. When n8n activates the workflow, its webhook registration calls hit Unihook's mock endpoints. Unihook captures the HMAC secret and handles all actual event delivery from GitHub, including re-signing payloads for n8n's signature verification.
Unihook discovers workflows with GitHub Trigger nodes via the n8n API. For each trigger, it extracts:
- Event types — The list of GitHub event types the trigger listens for (e.g.
push,issues,*) - Owner — The repository owner (e.g.
n8n-io) - Repository — The repository name (e.g.
n8n) - Webhook secret — The HMAC secret from n8n's
staticData(used for re-signing)
When a GitHub webhook event arrives at /github/events:
- Extract the event type from the
X-GitHub-Eventheader - Extract the owner and repository from the payload
- Match against all discovered GitHub triggers where:
- Event type matches (exact match or wildcard
*), AND - Owner matches (case-insensitive), AND
- Repository matches (case-insensitive)
- Event type matches (exact match or wildcard
- For each matching trigger, re-sign the payload with that workflow's webhook secret and forward
| Category | Events |
|---|---|
| Code | push, create, delete |
| Pull Requests | pull_request, pull_request_review, pull_request_review_comment |
| Issues | issues, issue_comment |
| CI/CD | check_run, check_suite, deployment, deployment_status |
| Releases | release |
| Repository | repository, repository_import, repository_vulnerability_alert, public, fork, star, watch |
| Organisation | organization, org_block, membership, member, team, team_add |
| GitHub Apps | github_app_authorization, installation, installation_repositories |
| Other | commit_comment, deploy_key, gollum, label, marketplace_purchase, milestone, page_build, project, project_card, project_column, security_advisory, status |
| Special | ping (handled automatically — acknowledged but not routed) |
| Wildcard | * (matches all events) |
Zoom differs from Slack: webhook delivery is app-level, not bot-scoped. A privileged admin configuring Event Subscriptions for a shared Zoom app can receive account-wide events. Unihook adds a platform allowlist so sensitive event types are not forwarded even if subscribed in Zoom or matched by a wildcard trigger.
- Install the community node
n8n-nodes-unihook-zoom-triggerin n8n - Deploy Unihook with
ZOOM_WEBHOOK_SECRETandZOOM_ALLOWED_EVENTS
-
Configure Unihook with your Zoom app Secret Token and an allowlist:
ZOOM_WEBHOOK_SECRET=your-secret-token ZOOM_ALLOWED_EVENTS=meeting.started,meeting.ended,recording.completed
-
Configure your Zoom app at marketplace.zoom.us:
- Enable Event Subscriptions
- Set the notification URL to:
https://your-domain.com/zoom/events - Subscribe only to events on your allowlist
- Use the narrowest Event notification receiver scope that meets your needs
-
Create n8n workflows with Zoom Trigger nodes:
- Install
n8n-nodes-unihook-zoom-trigger - Select event types (or
*for all allowlisted events) - Activate the workflow (prefer personal projects so host routing can resolve the workflow owner)
- Install
-
For workflows that call Zoom APIs: attach n8n's Zoom OAuth credential to downstream action nodes
-
Optional admin catch-alls:
ZOOM_PRIVILEGED_USERS=admin@company.com— all Zoom workflows in that user's personal project receive every allowlisted eventZOOM_PRIVILEGED_WORKFLOW_IDS=abc123— named workflows receive every allowlisted event (for team-project catch-alls)
Note: Like Slack, the Zoom Trigger node does not call the Zoom API during workflow activation — no provider API mock is needed.
API key scopes: The Unihook service API key needs
workflow:list,user:list, andproject:listto resolve workflow owner emails for host routing.
Unihook discovers workflows with n8n-nodes-unihook-zoom-trigger.zoomTrigger (or CUSTOM.zoomTrigger) nodes and extracts the event parameter (array of event types, or *).
When an event arrives:
- Verify
x-zm-signatureusingZOOM_WEBHOOK_SECRET - Drop events not on
ZOOM_ALLOWED_EVENTS(200 to Zoom, not forwarded) - Match remaining events against trigger configurations (event type / wildcard)
- Host filter: forward only when
payload.object.host_email(ormeeting_host_email) matches the workflow owner's n8n email, unless the trigger is on a privileged allowlist - Forward raw body and
x-zm-*headers to matching n8n webhooks
Events without a host field are forwarded only to privileged triggers (not to regular employee workflows).
| Layer | Controls |
|---|---|
| Zoom admin subscription | Which events Zoom sends; receiver scope (account vs users who installed app) |
Unihook ZOOM_ALLOWED_EVENTS |
Which event types are forwarded (ingress gate) |
| Unihook host routing | Personal-project workflows receive only meetings they hosted (host_email match) |
ZOOM_PRIVILEGED_USERS |
Named owner emails receive all allowlisted events in their personal workflows |
ZOOM_PRIVILEGED_WORKFLOW_IDS |
Named workflow IDs receive all allowlisted events (team-project admin catch-alls) |
Zoom Trigger event parameter |
Which forwarded events start each workflow |
| n8n Zoom OAuth on action nodes | What API operations a workflow can perform |
Why host routing matters: recording.completed includes sensitive fields (share password, download token). Without host routing, any employee with a Zoom trigger workflow could receive recordings from meetings they did not host.
Team projects: n8n does not expose per-user workflow creators for team projects. Host routing does not apply; use ZOOM_PRIVILEGED_WORKFLOW_IDS only for intentional admin workflows.
The Zoom Trigger node supports * to receive all events that Unihook forwards. Wildcard triggers cannot bypass ZOOM_ALLOWED_EVENTS.
Unihook supports optional HMAC-SHA256 verification of incoming webhook payloads. When enabled, events that fail verification are rejected with 401 Unauthorized before any routing occurs.
| Service | Env Var | Header Verified | Signing Standard |
|---|---|---|---|
| GitHub | GITHUB_WEBHOOK_SECRET |
X-Hub-Signature-256 |
GitHub webhook security |
| Zoom | ZOOM_WEBHOOK_SECRET |
x-zm-signature, x-zm-request-timestamp |
Zoom webhooks — v0:{timestamp}:{body} |
How it works: GitHub computes HMAC-SHA256(body, secret) and sends it as sha256=<hex_digest> in the X-Hub-Signature-256 header. Unihook recomputes the HMAC using the configured env var and compares using constant-time equality.
Opt-in: If the env var is not set, verification is skipped entirely and the endpoint accepts any well-formed request (backward-compatible with existing deployments).
Note: Inbound verification is independent of GitHub's outbound re-signing (see ADR-001). The inbound secret is the one you configure on the webhook pointing at Unihook; the outbound secret is the one n8n generates internally.
See ADR-002: Inbound Webhook Signature Verification for the full technical rationale.
n8n's Jira Trigger node has an optional authenticateWebhook parameter that validates incoming requests using an httpQueryAuth credential — a query parameter appended to the webhook URL.
Unihook supports this transparently by forwarding any query parameters from the inbound Jira request URL to the n8n webhook URL. To use it:
- Enable
authenticateWebhookon your Jira Trigger node in n8n and configure thehttpQueryAuthcredential (e.g. name=secret, value=abc123). - When registering your Jira webhook, append the same query parameter to the Unihook URL:
https://your-domain.com/jira/events?secret=abc123 - Jira sends events to the URL with the query parameter. Unihook captures it and appends it to the n8n webhook URL when forwarding, so n8n's credential validation passes.
No additional environment variables are required.
| Endpoint | Method | Description |
|---|---|---|
/slack/events |
POST | Receives Slack events (configure in Slack app) |
/jira/events |
POST | Receives Jira webhook events (configure in Jira) |
/github/events |
POST | Receives GitHub webhook events (configure in GitHub) |
/zoom/events |
POST | Receives Zoom webhook events (configure in Zoom app Event Subscriptions) |
/health |
GET | Health check — reports loaded trigger counts (slack_triggers_loaded, jira_triggers_loaded, github_triggers_loaded, zoom_triggers_loaded) |
server {
listen 443 ssl;
server_name your-domain.com;
# SSL configuration...
location /slack/events {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /jira/events {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /github/events {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /zoom/events {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}# Full test run (starts Docker, runs tests, stops Docker)
./scripts/run-integration-tests.sh
# Keep Docker running after tests (useful for debugging)
./scripts/run-integration-tests.sh --keep-running
# Skip Docker startup (when containers are already running)
./scripts/run-integration-tests.sh --skip-docker
# Run specific tests
./scripts/run-integration-tests.sh --filter test_jiraThe integration test environment uses Unihook's built-in provider API mock endpoints (the same credential workarounds described above for Jira and GitHub). The test setup script creates "dud" credentials in n8n with their API endpoints pointing at http://n8n-unihook:3000 on the Docker test network. When n8n activates trigger workflows, its webhook registration calls are intercepted by Unihook, which stores any secrets (e.g. GitHub HMAC) in its SQLite database. See docker-compose.test.yml and scripts/run-integration-tests.sh for the implementation.
- Check the health endpoint:
curl http://localhost:3000/health - Verify triggers are loaded: The health response shows
slack_triggers_loaded,jira_triggers_loaded,github_triggers_loaded, andzoom_triggers_loadedcounts - Check logs:
docker logs n8n-unihook - Ensure workflows are active in n8n (inactive workflows only receive test webhook events)
- Ensure the service is publicly accessible
- Check that
/slack/eventsreturns 200 for POST requests - The service handles URL verification automatically
- Verify the
webhookEventfield is present in the Jira payload - Check that the workflow's Jira Trigger node is configured for the correct event types
- Use wildcard (
*) events during debugging to match everything
- Verify the
X-GitHub-Eventheader is present (GitHub always sends this) - Check that the workflow's GitHub Trigger node is configured for the correct owner and repository — these must match the repository sending events (case-insensitive)
- Check that the event type matches (e.g. the trigger listens for
pushbut GitHub is sendingissues) - Use wildcard (
*) events during debugging to match all event types for a given repo
- This means the payload re-signing failed or the webhook secret is stale
- Ensure the workflow has been activated at least once (so
staticDatais populated with the webhook secret) - Trigger a refresh by restarting Unihook or waiting for the next refresh interval
- Check logs for
"No webhook secret available"warnings
- Verify the top-level
eventfield is present in the Zoom payload - Check that the workflow's Zoom Trigger node is configured for the correct event types
- Ensure the event is on Unihook's
ZOOM_ALLOWED_EVENTSallowlist - Use wildcard (
*) on the trigger to match all allowlisted events during debugging
- Verify
ZOOM_WEBHOOK_SECRETmatches the Secret Token in your Zoom app and n8n credentials - Ensure
x-zm-signatureandx-zm-request-timestampheaders are forwarded if using a reverse proxy
- Verify
N8N_API_URLis correct and accessible from the container - Check that your
N8N_API_KEYhas read access to workflows
MIT