WebhookView on GitHub
PR Review Notifier
Receives GitHub PR webhooks, summarizes changes with AI, and posts to Slack.
aihttpswitch
Source
/**
* Receives GitHub PR webhook events, summarizes changes with AI,
* and posts a review summary to Slack.
*/
secret pr_config {
vars: [SLACK_WEBHOOK_URL]
}
webhook github_pr {
label: "GitHub PR Event"
schema: @json {
{
"type": "object",
"required": ["action", "pull_request"],
"properties": {
"action": { "type": "string" },
"pull_request": {
"type": "object",
"properties": {
"title": { "type": "string" },
"body": { "type": "string" },
"user": { "type": "object" },
"html_url": { "type": "string" },
"additions": { "type": "number" },
"deletions": { "type": "number" },
"changed_files": { "type": "number" }
}
}
}
}
}
}
graph notify_pr {
label: "Notify PR"
root {
type: code
label: "Extract PR data"
code: @ts {
const input = context.nodes.root.input
const pr = input.pull_request || {}
return {
action: input.action,
title: pr.title || "Untitled",
body: pr.body || "",
author: pr.user ? pr.user.login : "unknown",
url: pr.html_url || "",
additions: pr.additions || 0,
deletions: pr.deletions || 0,
changed_files: pr.changed_files || 0
}
}
outputSchema: @json {
{
"type": "object",
"properties": {
"action": { "type": "string" },
"title": { "type": "string" },
"body": { "type": "string" },
"author": { "type": "string" },
"url": { "type": "string" },
"additions": { "type": "number" },
"deletions": { "type": "number" },
"changed_files": { "type": "number" }
}
}
}
}
node route_action {
type: switch
label: "Filter action"
cases: ["opened", "ignore"]
router: @ts {
const action = context.nodes.root.output.action
if (action === "opened" || action === "ready_for_review") return "opened"
return "ignore"
}
}
node summarize {
type: ai
label: "Summarize PR"
kind: text
model: "google/gemini-2.5-flash"
prompt: @ts {
const pr = context.nodes.root.output
return "Write a 1-2 sentence summary of this pull request for a Slack notification.\n\nTitle: " + pr.title + "\nDescription: " + pr.body + "\nFiles changed: " + pr.changed_files + " (+" + pr.additions + " -" + pr.deletions + ")"
}
maxTokens: 150
}
node post_slack {
type: http
label: "Post to Slack"
method: "POST"
url: @ts { return context.secrets.pr_config.SLACK_WEBHOOK_URL }
body: @ts {
const pr = context.nodes.root.output
const summary = context.nodes.summarize.output
return JSON.stringify({
text: "*New PR:* " + pr.title + " by " + pr.author + "\n" + summary + "\n<" + pr.url + "|View PR>"
})
}
secrets: {
pr_config: [SLACK_WEBHOOK_URL]
}
}
node skip {
type: code
label: "Skip"
code: @ts {
return { skipped: true, action: context.nodes.root.output.action }
}
}
flow {
root -> route_action
route_action -["opened"]-> summarize
summarize -> post_slack
route_action -["ignore"]-> skip
}
}
trigger on_pr {
webhook:github_pr -> notify_pr
enabled: true
}
Flow
Trigger → graph
Graph nodes