Volley / Templates / GitHub pull_request webhook
Webhooks template
GitHub webhook sequence diagram
When a pull request opens, GitHub posts the event to your app, and your app reports a check back on the commit. Each arrow carries the real headers and body, from the signed delivery to the finished check run.
Click any arrow in the editor to see its request and response as a table or the raw body. Change the hosts, ids and fields to match your integration, then share a link or embed it in your README.
Step by step
- GitHub posts the event with
X-GitHub-Event: pull_request, anX-GitHub-Deliveryid andX-Hub-Signature-256. The body hasaction: "opened", the head SHA and the installation id. - Your app checks the signature: an HMAC-SHA256 of the raw body with the webhook secret, hex encoded and prefixed with
sha256=. - Your app queues the work and answers 202 right away, or 401 if the signature is wrong.
- A worker gets an installation token from
POST /app/installations/{id}/access_tokens, authenticating as the app with a short-lived JWT. - The worker creates a check run on the head SHA, then updates it with a
conclusionwhen the tests finish.
Easy to get wrong
- GitHub stops waiting after 10 seconds and marks the delivery failed. Answer before doing the work.
- Compare the signature in constant time, over the raw bytes. A re-serialized body will not match.
- Check runs need a GitHub App and the
checks: writepermission. A personal token can only set commit statuses.
The source
The whole diagram is this plain text. Copy it into your repo, or open it in the editor.
github-webhook.volley
title: GitHub pull_request webhook with a check run
order: Developer, GitHub, Bot, Worker, GitHub API
Developer -> GitHub: Open pull request #128
GitHub -> Bot: POST /webhooks/github (pull_request)
in: Delivery
```http
POST /webhooks/github HTTP/1.1
Content-Type: application/json
User-Agent: GitHub-Hookshot/7a3f2c1
X-GitHub-Event: pull_request
X-GitHub-Delivery: 72d3162e-cc78-11e3-81ab-4c9367dc0958
X-GitHub-Hook-ID: 292430182
X-Hub-Signature-256: sha256=<redacted>
{
"action": "opened",
"number": 128,
"pull_request": {
"id": 2093847561,
"number": 128,
"state": "open",
"title": "Retry failed invoice runs",
"head": { "ref": "fix/invoice-retry", "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e" },
"base": { "ref": "main" }
},
"repository": { "id": 1296269, "full_name": "octo-org/billing" },
"installation": { "id": 4815162 },
"sender": { "login": "octocat" }
}
```
Bot -> Bot: Verify X-Hub-Signature-256 over the raw body
note: Compute an HMAC-SHA256 of the raw body with the webhook secret, hex encode it, add `sha256=` and compare in constant time.
if: Signature is valid
Bot ->> Worker: Check commit 6dcb09b
Bot --> GitHub: 202 Accepted
else: Signature is invalid
Bot --> GitHub: 401 Unauthorized
end
note: GitHub gives up after 10 seconds, so answer first. Store `X-GitHub-Delivery` to skip redeliveries.
=: {fab-github} Reporting back to the pull request
Worker -> GitHub API: POST /app/installations/4815162/access_tokens
in: Headers
Authorization: Bearer <redacted>
Accept: application/vnd.github+json
X-GitHub-Api-Version: 2022-11-28
note: This bearer token is a JWT the app signs with its private key. It lasts at most 10 minutes.
GitHub API --> Worker: 201 Created
out: Installation token
```json
{ "token": "ghs_16C7e42F292c6912E7710c838347Ae178B4a", "expires_at": "2026-10-19T15:00:00Z", "permissions": { "checks": "write", "pull_requests": "read" } }
```
Worker -> GitHub API: POST /repos/octo-org/billing/check-runs
in: Check run
```http
POST /repos/octo-org/billing/check-runs HTTP/1.1
Authorization: Bearer <redacted>
Accept: application/vnd.github+json
X-GitHub-Api-Version: 2022-11-28
{ "name": "billing-ci", "head_sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", "status": "in_progress" }
```
GitHub API --> Worker: 201 Created
out: Check run
```json
{ "id": 4182736450, "name": "billing-ci", "status": "in_progress", "html_url": "https://github.com/octo-org/billing/runs/4182736450" }
```
...: {fas-tools} Tests run
Worker -> GitHub API: PATCH /repos/octo-org/billing/check-runs/4182736450
in: Result
```json
{ "status": "completed", "conclusion": "success", "output": { "title": "214 tests passed", "summary": "All checks passed in 3m 12s." } }
```
GitHub API --> Worker: 200 OK
GitHub -> Developer: Green check on pull request #128