หากต้องการใช้ GitHub webhook เพื่อทริกเกอร์ AI coding agent ให้ตั้งค่า webhook ของ repository ไปยัง intake endpoint ของคุณเอง ตรวจสอบความถูกต้องของส่วนหัว X-Hub-Signature-256 เทียบกับ raw body และตอบกลับด้วยรหัส 202 ภายในช่วงเวลา 10 วินาทีตามที่ GitHub กำหนด พร้อมทั้งพุชอีเวนต์เข้าสู่คิว จากนั้น worker จะกรองอีเวนต์ แมปเข้ากับเธรด (repository บวกกับหมายเลข PR) แล้วจึงเริ่มต้นหรือส่งต่อข้อมูลให้ agent ทำงาน
เนื้อหาส่วนที่เหลือของคู่มือนี้จะแสดงโค้ดในแต่ละขั้นตอน และวิธีให้ coding agent ของคุณทดสอบ endpoint ด้วย Apidog CLI
ทำไมจึงควรทริกเกอร์ AI coding agent ด้วย GitHub webhook แทนการ Polling
การทริกเกอร์ด้วย webhook คือการเรียกใช้ HTTP ที่ GitHub ส่งไปยังเซิร์ฟเวอร์ของคุณทันทีที่มีเหตุการณ์เกิดขึ้นใน repository ไม่ว่าจะเป็นคอมเมนต์ การรีวิว หรือเวิร์กโฟลว์ที่ทำงานเสร็จสิ้น โดย agent ของคุณจะอยู่ในสถานะสลีปจนกว่าจะมีข้อมูลส่งมา ทำให้ไม่เสียโทเคนและไม่ต้องเรียกใช้ API เลยแม้แต่ครั้งเดียวในช่วงที่ไม่มีความเคลื่อนไหว อีกทั้งยังตอบสนองได้ในเวลาเพียงไม่กี่วินาทีแทนที่จะต้องรอรอบการ poll ถัดไป
การทำ polling ทุกนาทีจะผลาญโควตา rate limit ไปกับคำตอบที่ไม่มีการเปลี่ยนแปลง และยังเกิดความล่าช้าได้สูงสุดถึงหนึ่งนาที คุณสามารถอ่านรายละเอียดเชิงลึกได้ใน webhooks vs polling
ทีม DeepSeek Harness ได้หยิบยกประเด็นเดียวกันนี้ไว้ในบทความ “Exploring Super-agent Design” ที่เผยแพร่เมื่อวันที่ 1 พฤศจิกายน 2026 พวกเขาเรียกอินพุตเหล่านี้ว่า Triggers กล่าวคือ agent จะคอยตรวจจับอีเวนต์และอยู่เฉยๆ เมื่อไม่มีเหตุการณ์ใดๆ บทความดังกล่าวนำเสนอแนวคิดทางสถาปัตยกรรมและปลั๊กอินต้นแบบ ไม่ใช่ฟีเจอร์ที่เปิดตัวอย่างเป็นทางการของ DeepSeek เราได้เจาะลึกการออกแบบฉบับเต็มไว้ใน how to build a super-agent แล้ว ส่วนโพสต์นี้จะเน้นเฉพาะการสร้างเลเยอร์ส่วน trigger สำหรับ coding agent ทุกรูปแบบ

อีเวนต์ GitHub webhook ใดบ้างที่ควรสั่งให้ agent เริ่มทำงาน
ให้ติดตาม (subscribe) เฉพาะอีเวนต์ที่มนุษย์คาดหวังให้เพื่อนร่วมทีมเข้ามาจัดการเท่านั้น เพราะทุกอีเวนต์ที่เพิ่มเข้ามาหมายถึงข้อมูลส่งที่คุณต้องตรวจสอบ จัดเก็บ และคัดกรองเพิ่มขึ้น
| อีเวนต์ | Action ที่ต้องเฝ้าดู | หน้าที่ของ Agent |
|---|---|---|
issue_comment |
created |
ตอบกลับคอมเมนต์บน issue หรือบทสนทนาใน PR เช่น “@bot fix the failing test” |
pull_request_review_comment |
created |
ตอบกลับคอมเมนต์บนบรรทัดที่ระบุของ diff |
pull_request |
opened, synchronize |
รีวิว PR ใหม่ หรือรีวิวซ้ำหลังจากมีการ push |
workflow_run |
completed with conclusion: failure |
อ่านล็อกที่ล้มเหลวและเสนอวิธีแก้ไข |
check_run |
completed with a failing conclusion |
เหมือนกับด้านบน สำหรับการตรวจสอบที่รายงานโดยแอป |
รายละเอียด 2 ประการจาก เอกสารอ้างอิงอีเวนต์ GitHub webhook จะช่วยประหยัดเวลาในการดีบักของคุณ ข้อแรกคือ issue_comment จะทำงานทั้งกับคอมเมนต์บน issue และ pull request โดยคอมเมนต์ของ PR จะมีออบเจกต์ issue.pull_request แนบมาด้วย ซึ่งช่วยให้คุณแยกความแตกต่างระหว่างสองกรณีนี้ได้ ข้อที่สองคือ workflow_run ที่มี action เป็น completed จะทริกเกอร์เสมอไม่ว่าการรันจะสำเร็จหรือล้มเหลว คุณจึงต้องตรวจสอบ workflow_run.conclusion ด้วยตัวเอง
เพย์โหลดถูกจำกัดขนาดไว้ที่ 25 MB และ GitHub จะไม่ส่งข้อมูลที่มีขนาดใหญ่กว่านี้อย่างแน่นอน
ตรวจสอบลายเซ็น X-Hub-Signature-256 บน Raw Body
เมื่อคุณกำหนด secret ให้กับ webhook ทาง GitHub จะลงนามในทุกการส่งข้อมูลด้วย HMAC SHA-256 ของ raw request body และส่งผลลัพธ์ hex digest มาในรูปแบบ X-Hub-Signature-256: sha256=<digest> โดย endpoint ของคุณจะต้องคำนวณ digest ขึ้นมาใหม่และเปรียบเทียบทั้งสองค่าโดยใช้เวลาคงที่ (constant time)
กฎ 3 ข้อจากคู่มือ การตรวจสอบความถูกต้องของ webhook delivery ของ GitHub มีดังนี้:
- แฮชไบต์ข้อมูลดิบ (raw bytes) ไม่ใช่แฮชออบเจกต์ JSON ที่ถูก serialize ใหม่ เนื่องจากการแปลงเป็นออบเจกต์แล้วแปลงกลับเป็นสตริงจะเปลี่ยนตำแหน่งช่องว่าง (whitespace) และทำให้ผลการตรวจสอบไม่ตรงกัน
- เปรียบเทียบค่าด้วยฟังก์ชันแบบ constant-time เช่น
crypto.timingSafeEqualของ Node และอย่าใช้==โดยเด็ดขาด - ใช้
X-Hub-Signature-256เสมอ เนื่องจากส่วนหัวX-Hub-Signatureแบบเดิมใช้ SHA-1 และคงไว้สำหรับการเชื่อมต่อกับระบบเก่า (legacy) เท่านั้น
ตัวอย่างโค้ดของ intake service ใน Node ด้วย Express:
import express from "express";
import crypto from "node:crypto";
const app = express();
const SECRET = process.env.GITHUB_WEBHOOK_SECRET;
const seen = new Set(); // use Redis or a DB table in production
function validSignature(raw, header) {
if (!header) return false;
const expected = "sha256=" +
crypto.createHmac("sha256", SECRET).update(raw).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(header);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post("/triggers/github", express.raw({ type: "application/json" }), (req, res) => {
if (!validSignature(req.body, req.get("X-Hub-Signature-256"))) {
return res.status(401).json({ error: "bad signature" });
}
const deliveryId = req.get("X-GitHub-Delivery");
if (seen.has(deliveryId)) {
return res.status(200).json({ duplicate: true, trigger_id: deliveryId });
}
seen.add(deliveryId);
const event = req.get("X-GitHub-Event");
const payload = JSON.parse(req.body);
const prNumber = payload.pull_request?.number ?? payload.issue?.number;
const threadId = `${payload.repository.full_name}#${prNumber}`;
queue.push({ deliveryId, event, threadId, payload }); // your queue client
res.status(202).json({ trigger_id: deliveryId, thread_id: threadId });
});
app.listen(3000);
handler นี้ทำงานเบาๆ เพียง 4 อย่างและไม่มีส่วนใดที่ทำงานช้า หากต้องการศึกษารูปแบบลายเซ็นของผู้ให้บริการรายอื่นๆ โปรดดูที่ webhook signature verification
ตอบกลับ 2xx ภายใน 10 วินาทีและนำงานของ Agent เข้าคิว
แนวทางปฏิบัติที่ดีที่สุดสำหรับ webhook ของ GitHub ระบุว่าเซิร์ฟเวอร์ของคุณควรตอบกลับด้วยรหัส 2xx ภายใน 10 วินาที หากใช้เวลานานกว่านั้น GitHub จะตัดการเชื่อมต่อและบันทึกว่าการส่งข้อมูลล้มเหลว
การทำงานของ agent ใช้เวลานานหลายนาที จึงไม่สามารถทำในระหว่างประมวลผลคำขอได้ ให้พุชอีเวนต์เข้าคิว (เช่น Redis, SQS, ตาราง Postgres หรือระบบจัดเก็บใดๆ ที่คงทนถาวร) แล้วตอบกลับ 202 Accepted ทันที จากนั้น worker process ที่แยกต่างหากจะดึงงานออกจากคิว เรียกใช้ตัวกรอง และเริ่มการทำงานของ agent
ความล้มเหลวเป็นเรื่องที่มองข้ามไม่ได้ เพราะ GitHub จะไม่ส่งข้อมูลที่ล้มเหลวซ้ำให้โดยอัตโนมัติ คุณสามารถ ส่งข้อมูลซ้ำ ได้ด้วยตัวเองหรือผ่าน API ย้อนหลังได้สูงสุด 3 วัน ดังนั้น handler ที่ทำงานช้าจะทำให้ทริกเกอร์ของคอมเมนต์นั้นสูญหายไปอย่างเงียบๆ เว้นแต่จะมีคนสังเกตเห็น
กรองข้อมูล 2 ระดับ: Fast Path ในโค้ด และ Slow Path ด้วยโมเดล
ข้อมูลที่ส่งมาส่วนใหญ่ไม่ควรต้องไปถึงโมเดล บทความของ DeepSeek จึงแบ่งการกรองออกเป็น fast path ที่เขียนด้วยโค้ด และ slow path ที่ให้ LLM บังคับใช้นโยบายที่เขียนขึ้นในรูปแบบพรอมต์ การแยกส่วนเช่นนี้ช่วยจำกัดค่าใช้จ่ายไว้เฉพาะกับงานที่ต้องทำจริงๆ แทนที่จะสิ้นเปลืองไปกับสัญญาณรบกวนใน repository

Fast path (โค้ด ประมวลผลในระดับไมโครวินาที):
- Repo allowlist: อนุญาตเฉพาะ repository ที่คุณกำหนดไว้เท่านั้น
- กฎสำหรับผู้ส่ง (Sender rules): ละเว้นบัญชีบอตของตัว agent เอง มิฉะนั้นจะเกิดลูปไม่รู้จบจากการที่บอตตอบกลับตัวเอง
- โหมด Mention: เริ่มการทำงานเฉพาะเมื่อคอมเมนต์มี
@your-botเท่านั้น โดยบทความได้ระบุ 3 โหมดที่น่าสนใจ ได้แก่ ตอบกลับเฉพาะคำขอที่ชัดเจน, ส่งต่อเป็นตัวเลือกสำหรับ slow path หรือละเว้นการทำงาน
Slow path (LLM ประมวลผลสิ่งที่ผ่านขั้นตอนแรกมาได้):
ส่งข้อความคอมเมนต์พร้อมพรอมต์นโยบายสั้นๆ ให้กับโมเดล เช่น “นี่คือคำขอให้แก้ไขโค้ดใน PR นี้ใช่หรือไม่ ตอบแค่ใช่หรือไม่ และระบุเหตุผลหนึ่งบรรทัด” และจะสั่งให้ agent เริ่มทำงานเฉพาะเมื่อได้คำตอบว่า “ใช่” เท่านั้น วิธีนี้ช่วยดักจับคอมเมนต์อย่าง “thanks @bot, looks good” ซึ่งแม้จะผ่านกฎ mention มาได้ แต่ก็ไม่มีงานที่ต้องดำเนินการต่อ
function fastPath({ event, payload }) {
if (payload.sender?.type === "Bot") return "ignore";
if (!ALLOWED_REPOS.has(payload.repository.full_name)) return "ignore";
if (event === "workflow_run")
return payload.workflow_run.conclusion === "failure" ? "run" : "ignore";
const body = payload.comment?.body ?? "";
return body.includes("@your-bot") ? "candidate" : "ignore";
}
จัดเส้นทางการติดตามงานด้วย Thread ID เพื่อให้ Agent ตัวเดิมตอบกลับ
เธรด (thread) คือลำดับของอีเวนต์ที่เกิดขึ้นต่อเนื่องกันใน PR หนึ่งๆ ให้สร้าง thread_id จาก repository ร่วมกับหมายเลข PR หรือ issue เช่นเดียวกับที่ handler ด้านบนทำไว้เป็น acme/api#42 โดยในการออกแบบของบทความ worker ที่ถูกสร้างขึ้นสำหรับอีเวนต์แรกจะ subscribe เธรดนั้น และอีเวนต์ที่ตามมาหลังจากนั้นจะถูกส่งตรงไปยัง worker ตัวดังกล่าวโดยไม่ต้องผ่านกระบวนการเราต์ซ้ำอีก
ในทางปฏิบัติ กระบวนการนี้คือตารางค้นหา (lookup table) จาก thread_id ไปยัง session id ของ agent เมื่อมีข้อมูลส่งเข้ามายังเธรดที่มีเซสชันกำลังทำงานอยู่ ก็เพียงแค่เพิ่มคอมเมนต์นั้นเข้าไปในเซสชันดังกล่าว ทำให้ agent สามารถรักษาบริบทเดิมไว้ได้ ไม่ว่าจะเป็น diff ที่เคยอ่าน หรือการทดสอบที่เคยรัน หากไม่มีขั้นตอนนี้ คอมเมนต์ 3 รายการบน PR เดียวกันจะสร้าง agent ขึ้นมา 3 ตัว ซึ่งอาจให้คำตอบที่ขัดแย้งกันเอง
บทความยังได้เสริมรายละเอียดอีกข้อที่ควรนำไปปรับใช้ นั่นคือ ทริกเกอร์ที่เข้ามาขณะที่ router กำลังประมวลผลอยู่จะถูกรวมเป็นชุด (batch) เข้ากับการเรียกครั้งถัดไป ดังนั้นคอมเมนต์ที่เข้ามาพร้อมกัน 5 รายการจะเสียค่าใช้จ่ายในการตัดสินใจเพียงครั้งเดียว ไม่ใช่ 5 ครั้ง
ตัดข้อมูลซ้ำด้วย X-GitHub-Delivery
ข้อมูลที่ส่งมาทุกรายการจะมี GUID ของ X-GitHub-Delivery แนบมาด้วย ซึ่งเอกสารของ GitHub ระบุว่าการส่งซ้ำจะยังคงใช้ค่าเดิมของครั้งแรก ทำให้ค่านี้เหมาะเป็นอย่างยิ่งสำหรับการใช้เป็น idempotency key ให้คุณจัดเก็บแต่ละ id เอาไว้ (สามารถใช้ unique index ในฐานข้อมูลได้) และจัดการกับคำขอที่ซ้ำเดิมโดยไม่ต้องประมวลผลใดๆ (no-op) แต่ยังคงตอบกลับรหัส 2xx เพื่อไม่ให้ GitHub บันทึกว่าล้มเหลว
นอกจากนี้ยังช่วยป้องกันการโจมตีแบบ replay attack ได้ด้วย เพราะแม้ว่าข้อมูลที่ถูกดักจับจะมีลายเซ็นที่ถูกต้อง แต่ id ดังกล่าวก็ถูกบันทึกไว้เรียบร้อยแล้ว
ให้ Coding Agent ทดสอบ Trigger Endpoint ด้วย Apidog CLI
คุณไม่ควรต้องมานั่งคำนวณและทดสอบลายเซ็นด้วยตัวเอง ปล่อยให้ coding agent ที่เขียน intake service เป็นผู้เขียนชุดทดสอบและรันด้วย Apidog CLI โดยเริ่มต้นตั้งค่าเพียงครั้งเดียว:
npm install -g apidog-cli@latest
apidog auth login --with-token <YOUR_TOKEN>
npx -y skills add https://apidog.com # pick your agent when prompted
จากนั้นป้อนพรอมต์นี้ให้กับ Claude Code, Codex หรือ Cursor เพียงครั้งเดียว:
In Apidog project <projectId>, create the endpoint POST /triggers/github from the
sample issue_comment payload in fixtures/issue_comment.json. Then create test cases
against the staging environment:
1. valid X-Hub-Signature-256 -> 202 with trigger_id and a thread_id of "<repo>#<number>"
2. a second comment on the same PR -> same thread_id
3. bad signature -> 401
4. same X-GitHub-Delivery sent twice -> second call returns duplicate: true, no new job
Sign the bodies with the staging webhook secret in a pre-request script.
Run them and summarize any failures.
agent จะอ่านแต่ละ schema ตรวจสอบความถูกต้องของเพย์โหลด สร้างทรัพยากร และสั่งรัน:
apidog cli-schema get endpoint-create
apidog cli-schema validate endpoint-create --file endpoint.json
apidog endpoint create --project <projectId> --file endpoint.json
apidog cli-schema get test-case-create
apidog cli-schema validate test-case-create --file case-valid-signature.json
apidog test-case create --project <projectId> --file case-valid-signature.json
apidog run --test-case <caseId> --project <projectId> -e <stagingEnvId> -r cli,json
reporter ของ cli จะแสดงผลว่าผ่านหรือล้มเหลวในแต่ละ assertion และรายงาน json จะถูกจัดเก็บไว้ใน ./apidog-reports ทำให้ agent มองเห็นว่าการตรวจสอบจุดใดมีปัญหา (การได้รหัส 200 ทั้งที่คุณคาดหวัง 401 มักจะหมายความว่าคุณลืมใส่ raw-body middleware)
คำสั่ง apidog run เดียวกันนี้สามารถนำไปใช้งานใน CI ร่วมกับ -r junit ได้ โดยดูเพิ่มเติมได้ที่ Apidog CLI in GitHub Actions สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการตั้งค่า agent ให้อ่านได้ที่ Apidog CLI in Claude Code และหากต้องการศึกษากลยุทธ์การทดสอบ webhook ให้กว้างขึ้น ดูได้ที่ how to test webhooks ทั้งนี้ แผน Free ของ Apidog รองรับทีมที่มีผู้ใช้ไม่เกิน 4 คน
สิ่งที่ต้องตรวจสอบก่อนเปิดใช้งาน Webhook
- ตั้งค่า Secret แล้ว หากไม่มีลายเซ็นหรือลายเซ็นไม่ถูกต้องจะได้รับรหัส 401
- ตอบกลับ 202 ภายในเวลาไม่ถึง 10 วินาที และนำงานของ agent เข้าคิวเรียบร้อย
- จัดเก็บ Delivery ID พร้อมข้อกำหนดความไม่ซ้ำกัน (unique constraint)
- มีงาน (job) ที่ส่งข้อมูลซ้ำสำหรับรายการที่ล้มเหลวย้อนหลัง 3 วัน
หากคุณสะดวกใช้ runner ของทาง GitHub เองมากกว่า Claude Code GitHub Actions ถือเป็นช่องทางแบบ hosted ส่วนการใช้ webhook intake จะช่วยเสริมการกรองและการจัดเส้นทางเธรด และสำหรับการจัดการ retry ในฝั่งของคุณ สามารถศึกษาได้ที่ how to design reliable webhooks
คำถามที่พบบ่อย (FAQ)
ควรใช้ Repository Webhook หรือ GitHub App ดี? Webhook ระดับ repository หรือ organization ก็เพียงพอแล้วสำหรับการใช้งานภายในทีมเดียว แต่ GitHub App จะทำให้ agent มีตัวตนบอตเป็นของตัวเอง มีสิทธิ์การเข้าถึงที่ละเอียดรอบคอบยิ่งขึ้น และสามารถติดตั้งครอบคลุมหลายองค์กรได้
จะเกิดอะไรขึ้นหาก Endpoint ของฉันล่มหรือไม่ตอบสนอง? GitHub จะบันทึกว่าการส่งข้อมูลล้มเหลวและจะไม่พยายามส่งใหม่โดยอัตโนมัติ คุณสามารถสั่งส่งข้อมูลซ้ำย้อนหลัง 3 วันได้จากการตั้งค่า webhook หรือผ่านทาง REST API
จะป้องกันไม่ให้ Agent ตอบกลับตัวเองได้อย่างไร? ให้ทิ้ง (drop) ข้อมูลใดๆ ก็ตามที่มี sender เป็นบัญชีบอตของคุณในขั้นตอนตัวกรอง fast-path ก่อนที่ขั้นตอนอื่นจะเริ่มทำงาน
ทำไมจึงควรตอบกลับด้วย 202 แทนที่จะเป็น 200? ทั้งสองรหัสนับว่าสำเร็จสำหรับ GitHub แต่ 202 Accepted จะช่วยสื่อให้ทุกคนที่อ่านล็อกทราบว่างวดงานถูกนำเข้าคิวไว้แล้ว ไม่ใช่ทำเสร็จสิ้นทันที
สถาปัตยกรรม Super-agent ของ DeepSeek เป็นสิ่งที่เราสามารถกดเปิดใช้งานได้เลยหรือไม่? ไม่ได้ เพราะบทความดังกล่าวอธิบายเพียงแนวคิดทางสถาปัตยกรรม และปลั๊กอินต้นแบบก็ไม่ใช่ผลิตภัณฑ์อย่างเป็นทางการของ DeepSeek คุณจะต้องสร้างเลเยอร์สำหรับทริกเกอร์ขึ้นมาด้วยตนเองตามที่แสดงไว้ข้างต้น
