Build an n8n workflow that receives webhook payloads and automatically creates formatted GitHub issues with labels and assignments.
Nodes used: Webhook, Edit Fields (Set), IF, GitHub
A workflow that receives incoming webhook payloads (for example, from a monitoring tool or internal service), filters them by severity, and automatically creates labeled GitHub issues for events that need attention. The result is an automated triage pipeline that turns alerts into trackable issues without manual intervention.
Engineering teams deal with alerts from many sources: uptime monitors, CI pipelines, error trackers, and custom services. Manually creating GitHub issues from these alerts is tedious and error-prone. This workflow standardizes the process and ensures nothing falls through the cracks.
Open n8n, click Add workflow, and name it "Webhook to GitHub Issues."
Click Add first step and select Webhook. Configure:
alert-intake.After saving, n8n generates two webhook URLs:
Your production URL follows the pattern:
https://<your-instance>.app.n8n.cloud/webhook/alert-intake
The test URL uses /webhook-test/ and is active only while the canvas is open.
Your production URL follows the pattern:
https://<your-domain>/webhook/alert-intake
If you are running n8n behind a reverse proxy, make sure the WEBHOOK_URL environment variable is set to your externally reachable base URL. Otherwise, the generated URLs will reference localhost and external services will not be able to reach them.
Info: Test vs. production URLs
Use the test URL while building the workflow. Switch to the production URL only after you activate the workflow.
Click + on the Webhook output and add an Edit Fields (Set) node. Define the following fields by pulling values from the incoming payload using expressions:
| Field Name | Expression |
|---|---|
title |
{{ $json.body.title }} |
body |
{{ $json.body.description }} |
severity |
{{ $json.body.severity }} |
source |
{{ $json.body.source }} |
Set Keep Only Set to true so downstream nodes receive a clean, predictable object.
Add an IF node after Edit Fields. Configure a condition:
{{ $json.severity }}criticalThis routes only critical alerts to GitHub. Non-critical payloads exit through the false branch, which you can leave unconnected or attach to a logging node later.
Tip: Multiple severity levels
If you need to handle more than two levels, replace the IF node with a Switch node and define separate outputs for
critical,warning, andinfo.
Connect the true output of the IF node to a new GitHub node. Configure:
{{ $json.title }}**Source:** {{ $json.source }}
**Severity:** {{ $json.severity }}
{{ $json.body }}
bug or alert. You can also set this dynamically from the payload.With the workflow canvas open (so the test URL is active), send a test payload:
curl -X POST \
'<your-test-webhook-url>' \
-H 'Content-Type: application/json' \
-d '{
"title": "Database connection timeout",
"description": "Connection pool exhausted on db-primary. Average query latency exceeded 5s.",
"severity": "critical",
"source": "monitoring-agent"
}'
Check the n8n canvas to see data flow through each node, then verify the issue was created in your GitHub repository.
Toggle the Active switch. From this point on, the production webhook URL is live and ready to receive payloads from your monitoring tools or services.
Send payloads with different severity values (critical, warning, info) and confirm that only critical alerts produce GitHub issues. Open the Executions log in n8n to trace each run and verify the IF node routed data correctly.
team field from the payload to specific GitHub usernames using a Switch or Code node.source field in the payload.The workflow above uses an open webhook with no authentication. This is fine for testing, but a production webhook exposed to the internet should always require authentication to prevent unauthorized payloads.
X-Webhook-Secret) and a strong random value.curl -X POST \
'<your-production-webhook-url>' \
-H 'Content-Type: application/json' \
-H 'X-Webhook-Secret: your-secret-value-here' \
-d '{ "title": "Test", "severity": "critical", "source": "monitor" }'
Requests missing the header or with an incorrect value receive a 403 Forbidden response.
Warning: Why this matters
An unauthenticated production webhook can be discovered by bots scanning common paths. Without auth, anyone can trigger your workflow and create spurious GitHub issues. Header Auth adds a shared secret that only your authorized callers know.
Issue: Webhook returns 404 Not Found.
The workflow is not active, or the calling service is using the test URL instead of the production URL. Toggle the workflow to Active and verify the external service is configured with the production URL (/webhook/alert-intake, not /webhook-test/alert-intake). On self-hosted instances, also confirm the WEBHOOK_URL environment variable is set to your public-facing base URL.
Issue: GitHub node fails with "Resource not found" or "Validation Failed".
Check that the repository owner and repository name are correct and that your GitHub credential has the repo scope. If you are using labels, verify the label already exists on the repository -- the Create Issue operation does not create labels automatically.
Issue: IF node sends all items to the False branch even though severity is "critical".
String comparisons are case-sensitive. If the payload sends "Critical" or "CRITICAL", the condition equals "critical" will not match. Use an expression with .toLowerCase() in the Edit Fields node to normalize the severity value before the IF node: {{ $json.body.severity.toLowerCase() }}.
I build production n8n and Cloudflare automation for teams — the same engineering behind HarperFlow. Fixed-price, escrow-protected, US-based.