Để dùng GitHub webhook kích hoạt AI coding agent, bạn hãy trỏ webhook của kho lưu trữ (repository) về một endpoint tiếp nhận do mình quản lý, xác thực header X-Hub-Signature-256 với raw body, phản hồi mã 202 trong khung thời gian 10 giây của GitHub và đẩy sự kiện vào hàng đợi (queue). Sau đó, worker sẽ lọc sự kiện, ánh xạ sự kiện đó vào một luồng (thread – kết hợp giữa repo và số PR) rồi khởi chạy hoặc tiếp tục cung cấp ngữ cảnh cho agent.
Phần còn lại của hướng dẫn này sẽ trình bày chi tiết từng bước bằng code và cách để coding agent của bạn tự kiểm thử endpoint bằng Apidog CLI.
Tại sao nên kích hoạt AI coding agent bằng GitHub webhook thay vì cơ chế polling
Kích hoạt bằng webhook bản chất là một lệnh gọi HTTP mà GitHub gửi tới máy chủ của bạn ngay khi có sự kiện phát sinh trong kho lưu trữ: một bình luận mới, một lượt review code hay một workflow vừa chạy xong. Agent của bạn sẽ ở trạng thái "ngủ" cho đến khi có sự kiện được gửi đến (delivery), nhờ đó không tiêu tốn bất kỳ token hay lệnh gọi API nào khi không có hoạt động gì, đồng thời phản hồi chỉ trong vài giây thay vì phải chờ tới lần poll tiếp theo.
Việc thăm dò (polling) mỗi phút vừa làm hao phí giới hạn tần suất (rate limit) cho các phản hồi rỗng, vừa gây ra độ trễ lên tới một phút. Bạn có thể tìm hiểu chi tiết hơn trong bài viết về webhooks vs polling.
Đội ngũ DeepSeek Harness cũng đưa ra quan điểm tương tự trong bài viết “Exploring Super-agent Design” đăng ngày 1 tháng 11 năm 2026. Họ gọi các đầu vào này là Triggers (Bộ kích hoạt): agent chỉ theo dõi các sự kiện và giữ im lặng trong mọi khoảng thời gian còn lại. Bài luận này mô tả một kiến trúc và một plugin nguyên mẫu (prototype) chứ không phải là tính năng đã phát hành chính thức của DeepSeek. Chúng tôi đã phân tích toàn bộ thiết kế này trong bài viết cách xây dựng một super-agent; còn bài viết này sẽ tập trung xây dựng riêng lớp trigger cho bất kỳ coding agent nào.

Những sự kiện GitHub webhook nào nên kích hoạt agent
Bạn chỉ nên đăng ký nhận các sự kiện mà một người đồng đội thực tế sẽ cần phản hồi. Mỗi sự kiện dư thừa đều đồng nghĩa với một delivery bạn phải xác thực, lưu trữ và lọc bỏ.
| Sự kiện | Hành động cần theo dõi | Nhiệm vụ của agent |
|---|---|---|
issue_comment |
created |
Trả lời một bình luận trong issue hoặc trong luồng trao đổi PR, ví dụ: “@bot fix the failing test” |
pull_request_review_comment |
created |
Phản hồi bình luận trên một dòng cụ thể trong bản diff |
pull_request |
opened, synchronize |
Review một PR mới hoặc review lại sau khi có push mới |
workflow_run |
completed with conclusion: failure |
Đọc log thất bại và đề xuất bản vá khắc phục |
check_run |
completed with a failing conclusion |
Tương tự như trên, áp dụng cho các check do các app báo cáo |
Hai chi tiết từ tài liệu tham khảo GitHub webhook events reference sẽ giúp bạn tiết kiệm thời gian debug. Thứ nhất, issue_comment được kích hoạt cho cả bình luận trên issue lẫn pull request; một bình luận PR sẽ chứa đối tượng issue.pull_request, và đây chính là dấu hiệu để bạn phân biệt chúng. Thứ hai, sự kiện workflow_run với action completed sẽ kích hoạt bất kể lần chạy đó thành công hay thất bại, do đó bạn phải tự kiểm tra giá trị của workflow_run.conclusion.
Kích thước payload được giới hạn tối đa ở mức 25 MB; GitHub không bao giờ gửi payload lớn hơn mức này.
Xác thực chữ ký X-Hub-Signature-256 trên raw body
Khi bạn thiết lập secret cho webhook, GitHub sẽ ký từng delivery bằng mã HMAC SHA-256 của raw request body và gửi mã băm hex dưới dạng X-Hub-Signature-256: sha256=<digest>. Endpoint của bạn sẽ tính toán lại mã băm này và so sánh hai giá trị trong thời gian hằng số (constant time).
Ba nguyên tắc từ tài liệu hướng dẫn validating webhook deliveries của GitHub:
- Băm (hash) trực tiếp các byte thô (raw bytes), không dùng đối tượng JSON đã qua tuần tự hóa lại (re-serialize). Quá trình parse rồi stringify lại sẽ làm thay đổi khoảng trắng và khiến chữ ký không khớp.
- So sánh bằng một hàm chạy trong thời gian hằng số như
crypto.timingSafeEqualcủa Node, tuyệt đối không dùng==. - Sử dụng
X-Hub-Signature-256. Header cũ hơn làX-Hub-Signaturesử dụng SHA-1 và chỉ tồn tại nhằm mục đích tương thích ngược với các hệ thống cũ.
Dưới đây là intake service viết bằng Node kết hợp với 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);
Hàm xử lý này chỉ thực hiện bốn tác vụ nhẹ nhàng và không có bước nào gây tắc nghẽn. Để tìm hiểu các cơ chế chữ ký của các nhà cung cấp khác nhau, xem thêm bài viết về webhook signature verification.
Phản hồi mã 2xx trong vòng 10 giây và đẩy tác vụ của agent vào hàng đợi
Tài liệu webhook best practices của GitHub khuyến nghị máy chủ của bạn phải phản hồi mã 2xx trong vòng 10 giây. Nếu vượt quá mốc này, GitHub sẽ ngắt kết nối và ghi nhận lượt delivery đó là thất bại.
Một phiên chạy của agent thường kéo dài nhiều phút, do đó quá trình này tuyệt đối không được thực hiện trực tiếp bên trong request. Hãy đẩy sự kiện vào một hàng đợi (Redis, SQS, một bảng Postgres hoặc bất kỳ giải pháp lưu trữ bền bỉ nào) và trả về mã 202 Accepted ngay lập tức. Một tiến trình worker riêng biệt sẽ lấy dữ liệu từ hàng đợi, thực hiện việc lọc và khởi động agent.
Các trường hợp lỗi cần được lưu tâm kỹ: GitHub không tự động gửi lại (redeliver) các delivery bị lỗi. Bạn chỉ có thể gửi lại chúng thủ công hoặc qua API trong tối đa 3 ngày. Do đó, nếu hàm xử lý bị chậm, nó sẽ âm thầm đánh mất trigger của bình luận đó mà không ai hay biết trừ khi có người kiểm tra.
Lọc qua hai giai đoạn: luồng nhanh bằng code, luồng chậm bằng mô hình
Phần lớn các delivery không bao giờ cần phải chạm tới mô hình. Bài luận của DeepSeek chia quá trình lọc thành hai phần: một luồng nhanh (fast path) được viết bằng code và một luồng chậm (slow path), nơi LLM áp dụng các chính sách được định nghĩa dưới dạng prompt. Cách phân tách này giúp chi phí gắn liền với công việc thực tế thay vì bị lãng phí vào các sự kiện không cần thiết trong kho lưu trữ.

Luồng nhanh (fast path – xử lý bằng code, tính bằng micro giây):
- Allowlist kho lưu trữ: chỉ cho phép các kho lưu trữ mà bạn đã chủ động bật.
- Quy tắc người gửi: bỏ qua tài khoản bot của chính agent, nếu không bạn sẽ tạo ra một vòng lặp vô tận khi bot tự phản hồi chính nó.
- Chế độ nhắc tên (@mention): chỉ khởi chạy khi bình luận có chứa
@your-bot. Bài luận liệt kê ba chế độ rất đáng học hỏi: chỉ phản hồi các yêu cầu tường minh, coi như ứng viên đưa vào luồng chậm, hoặc bỏ qua hoàn toàn.
Luồng chậm (slow path – xử lý bằng LLM, áp dụng cho những sự kiện vượt qua vòng trước):
Cung cấp cho mô hình nội dung bình luận kèm theo một prompt chính sách ngắn gọn, chẳng hạn: “Đây có phải là yêu cầu thay đổi mã nguồn trong PR này không? Hãy trả lời có hoặc không kèm một dòng giải thích lý do.” Chỉ câu trả lời “có” mới kích hoạt agent. Cách này giúp loại bỏ những bình luận như “thanks @bot, looks good” – dù vượt qua điều kiện mention nhưng lại không cần can thiệp xử lý.
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";
}
Định tuyến các phản hồi tiếp theo theo thread id để cùng một agent xử lý
Một luồng (thread) là chuỗi các sự kiện liên tiếp diễn ra trên cùng một PR. Hãy tạo thread_id từ repo và số thứ tự của PR hoặc issue, tương tự như cách hàm xử lý bên trên đã làm với acme/api#42. Trong thiết kế của bài luận, worker được tạo ra cho sự kiện đầu tiên sẽ đăng ký theo dõi thread đó, và các sự kiện tiếp theo sẽ được chuyển thẳng đến nó mà không cần thêm một bước định tuyến nào khác.
Trong thực tế, đây là một bảng tra cứu (lookup table) từ thread_id sang session id của agent. Khi một delivery mới đến ứng với thread đang có phiên làm việc trực tiếp, bạn chỉ cần gắn thêm bình luận vào phiên đó. Nhờ vậy, agent vẫn giữ nguyên được ngữ cảnh: bản diff nó đã đọc, bài test nó đã chạy. Nếu không có cơ chế này, ba bình luận trên cùng một PR sẽ tạo ra ba agent riêng lẻ và dễ đưa ra các xử lý mâu thuẫn lẫn nhau.
Bài luận còn bổ sung thêm một chi tiết rất đáng áp dụng: các trigger gửi đến trong lúc router đang xử lý dở một lượt (mid-turn) sẽ được gom lại (batch) cho lần gọi kế tiếp. Nhờ đó, một đợt gửi dồn dập gồm năm bình luận chỉ tiêu tốn một lượt đưa ra quyết định thay vì năm lượt.
Khử trùng lặp bằng X-GitHub-Delivery
Mỗi delivery đều mang theo một GUID trong header X-GitHub-Delivery. Tài liệu của GitHub chỉ ra rằng một lượt gửi lại (redelivery) sẽ tái sử dụng chính giá trị ban đầu, khiến nó trở thành idempotency key chuẩn xác nhất. Hãy lưu từng ID này (dùng unique index trong cơ sở dữ liệu) và coi các yêu cầu gửi lại như một thao tác no-op nhưng vẫn trả về mã 2xx để GitHub không đánh dấu là thất bại.
Cơ chế này cũng ngăn chặn các cuộc tấn công phát lại (replay attack): một delivery bị bắt lén dù có chữ ký hợp lệ nhưng ID của nó đã tồn tại trong hệ thống nên sẽ bị chặn lại.
Để coding agent tự kiểm thử trigger endpoint bằng Apidog CLI
Bạn không nên tính toán và kiểm thử chữ ký thủ công bằng tay. Hãy để chính coding agent đã viết dịch vụ tiếp nhận tự viết và chạy các bài kiểm thử đó với Apidog CLI. Bạn chỉ cần thiết lập một lần:
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
Sau đó, đưa cho Claude Code, Codex hoặc Cursor một prompt duy nhất:
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 sẽ đọc từng schema, xác thực payload, tạo các tài nguyên cần thiết và khởi chạy chúng:
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
Bộ báo cáo cli sẽ in kết quả pass hoặc fail cho từng assertion, còn báo cáo json sẽ được ghi vào ./apidog-reports, giúp agent thấy rõ kiểm tra nào bị lỗi (nhận mã 200 trong khi mong đợi 401 thường là do thiếu middleware xử lý raw-body).
Dòng lệnh apidog run tương tự có thể đưa vào CI cùng cờ -r junit; xem thêm Apidog CLI in GitHub Actions. Để biết thêm chi tiết về cách thiết lập agent, hãy đọc Apidog CLI in Claude Code, và để nắm được chiến lược kiểm thử webhook toàn diện hơn, tham khảo how to test webhooks. Gói Free của Apidog hỗ trợ đội ngũ lên tới 4 người dùng.
Trước khi bạn bật webhook
- Đã thiết lập secret; chữ ký bị thiếu hoặc sai sẽ nhận mã 401.
- Phản hồi 202 trong thời gian ngắn hơn nhiều so với 10 giây, công việc của agent đã được đưa vào hàng đợi.
- Delivery ID được lưu với ràng buộc duy nhất (unique constraint).
- Có tác vụ tự động phát lại (replay) các delivery bị lỗi trong vòng 3 ngày qua.
Nếu bạn muốn sử dụng runner của chính GitHub, Claude Code GitHub Actions là phương án hosted phù hợp; còn cơ chế tiếp nhận qua webhook sẽ bổ sung thêm khả năng lọc và định tuyến luồng. Về cách xử lý retry ở phía bạn, xem how to design reliable webhooks.
FAQ
Nên dùng repository webhook hay GitHub App? Webhook cấp repository hoặc organization là đã đủ dùng cho một team. Trong khi đó, GitHub App cung cấp cho agent danh tính bot riêng biệt, quyền hạn chi tiết hơn và khả năng cài đặt trên nhiều organization.
Điều gì xảy ra nếu endpoint của tôi bị sập? GitHub sẽ ghi nhận lượt gửi đó là thất bại và không thử gửi lại. Bạn có thể gửi lại các delivery trong vòng 3 ngày qua trong phần cài đặt webhook hoặc thông qua REST API.
Làm thế nào để ngăn agent tự trả lời chính nó? Hãy loại bỏ ngay bất kỳ delivery nào có sender là tài khoản bot của bạn trong bộ lọc luồng nhanh (fast-path), trước khi chạy bất kỳ logic nào khác.
Tại sao lại trả về mã 202 thay vì 200? Cả hai đều được GitHub tính là thành công. Tuy nhiên, 202 Accepted báo hiệu rõ ràng cho bất kỳ ai đọc log rằng công việc đã được đưa vào hàng đợi chứ chưa hoàn thành.
Thiết kế super-agent của DeepSeek có phải là tính năng có sẵn để bật lên không? Không. Bài luận chỉ mô tả một kiến trúc, và plugin nguyên mẫu của họ không phải là một sản phẩm chính thức từ DeepSeek. Bạn sẽ tự xây dựng lớp trigger như hướng dẫn ở trên.
