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.

Sequence diagram of a GitHub pull_request webhook across Developer, GitHub, Bot, Worker and GitHub API: the signed delivery, a 202 Accepted or 401, an installation access token, a check run created in progress and then completed.

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

  1. GitHub posts the event with X-GitHub-Event: pull_request, an X-GitHub-Delivery id and X-Hub-Signature-256. The body has action: "opened", the head SHA and the installation id.
  2. Your app checks the signature: an HMAC-SHA256 of the raw body with the webhook secret, hex encoded and prefixed with sha256=.
  3. Your app queues the work and answers 202 right away, or 401 if the signature is wrong.
  4. A worker gets an installation token from POST /app/installations/{id}/access_tokens, authenticating as the app with a short-lived JWT.
  5. The worker creates a check run on the head SHA, then updates it with a conclusion when 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: write permission. 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

Related

Make this GitHub pull_request webhook diagram yours.

Open it in the editor, change it to match your integration and share the link. Free, with no account.