Skip to content

Commit f789158

Browse files
committed
handle ping
1 parent 3ea1a2d commit f789158

2 files changed

Lines changed: 51 additions & 4 deletions

File tree

app/api/webhook/github/route.ts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ type IssueCommentPayload = {
7373
}
7474
}
7575

76+
type PingPayload = {
77+
zen?: string
78+
hook_id?: number
79+
hook?: {
80+
id?: number
81+
}
82+
}
83+
7684
export async function POST(request: NextRequest) {
7785
const event = request.headers.get("x-github-event")
7886
if (!event) {
@@ -105,6 +113,15 @@ export async function POST(request: NextRequest) {
105113
return NextResponse.json({ error: "Invalid JSON payload" }, { status: 400 })
106114
}
107115

116+
if (event === "ping") {
117+
const pingPayload = payload as PingPayload
118+
return NextResponse.json({
119+
message: "Webhook ping received",
120+
zen: pingPayload.zen ?? null,
121+
hookId: pingPayload.hook_id ?? pingPayload.hook?.id ?? null,
122+
})
123+
}
124+
108125
const baseUrl = getBaseUrl(request)
109126

110127
if (event === "installation") {
@@ -567,8 +584,8 @@ export async function GET(request: NextRequest) {
567584
}
568585

569586
function verifyWebhookRequest(rawPayload: string, signatureHeader: string | null) {
570-
const secret = process.env.GITHUB_WEBHOOK_SECRET
571-
if (!secret) {
587+
const configuredSecret = process.env.GITHUB_WEBHOOK_SECRET
588+
if (!configuredSecret) {
572589
if (process.env.NODE_ENV === "production") {
573590
return NextResponse.json(
574591
{ error: "GITHUB_WEBHOOK_SECRET is not configured" },
@@ -578,6 +595,7 @@ function verifyWebhookRequest(rawPayload: string, signatureHeader: string | null
578595
return null
579596
}
580597

598+
const secret = normalizeWebhookSecret(configuredSecret)
581599
if (!signatureHeader) {
582600
if (process.env.NODE_ENV === "production") {
583601
return NextResponse.json({ error: "Missing x-hub-signature-256 header" }, { status: 401 })
@@ -588,15 +606,32 @@ function verifyWebhookRequest(rawPayload: string, signatureHeader: string | null
588606
const valid = verifyGitHubWebhookSignature({
589607
secret,
590608
payload: rawPayload,
591-
signatureHeader,
609+
signatureHeader: signatureHeader.trim(),
592610
})
593611
if (!valid) {
594-
return NextResponse.json({ error: "Invalid webhook signature" }, { status: 401 })
612+
return NextResponse.json(
613+
{
614+
error:
615+
"Invalid webhook signature. Ensure GITHUB_WEBHOOK_SECRET exactly matches the GitHub App webhook secret.",
616+
},
617+
{ status: 401 }
618+
)
595619
}
596620

597621
return null
598622
}
599623

624+
function normalizeWebhookSecret(secret: string): string {
625+
const trimmed = secret.trim()
626+
if (
627+
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
628+
(trimmed.startsWith("'") && trimmed.endsWith("'"))
629+
) {
630+
return trimmed.slice(1, -1)
631+
}
632+
return trimmed
633+
}
634+
600635
function getBaseUrl(request: NextRequest): string {
601636
const url = new URL(request.url)
602637
return `${url.protocol}//${url.host}`

tests/e2e/api-suite.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,18 @@ test("Webhook: installation suspend and unsuspend toggles active state", async (
12411241
assertEqual(orgAfterUnsuspend.org.isActive, true, "org is active after unsuspend")
12421242
})
12431243

1244+
test("Webhook: ping event is acknowledged", async (baseUrl) => {
1245+
await resetDb(baseUrl)
1246+
const { res, data } = await sendWebhook(baseUrl, "ping", {
1247+
zen: "Keep it logically awesome.",
1248+
hook_id: 123,
1249+
})
1250+
assertEqual(res.status, 200, "ping accepted")
1251+
assertEqual(data.message, "Webhook ping received", "ping message")
1252+
assertEqual(data.zen, "Keep it logically awesome.", "zen echoed")
1253+
assertEqual(data.hookId, 123, "hook id echoed")
1254+
})
1255+
12441256
test("Webhook: duplicate delivery id is deduplicated", async (baseUrl) => {
12451257
await resetDb(baseUrl)
12461258

0 commit comments

Comments
 (0)