Chapter 27Lesson 05~235 minutes

Checkpoint Lab — GitHub Apps, OAuth Apps, Webhooks, Checks API, and Event-Driven Integrations

Operate a signed-delivery checkpoint: verify, deduplicate, inject failure, simulate token expiry/redelivery, write a least-privilege App policy, and optionally publish a check.

CheckpointHMACDeduplicationToken expiryRunbook

Learning objectives

  • Prove HMAC rejection and delivery-ID deduplication with durable evidence.
  • Simulate installation-token expiry and classify retry versus authorization failure.
  • Produce an App permission/event/reliability policy.
  • Model explicit failed-delivery recovery and same-ID redelivery.
  • Optionally publish and independently verify a least-privilege check run.

1. Checkpoint mission

You will operate a small event-driven integration boundary end to end: signed synthetic webhook deliveries enter a local receiver, the receiver verifies and deduplicates them, and one safe derived action is emitted in dry-run mode. You will replay the delivery, inject a bad signature, simulate an expired App token, write an App permission/event matrix, and optionally publish a real check result only if you intentionally created a disposable GitHub App.

Required assumptions: GitHub Free, a disposable public personal repository, Python 3, Git, and GitHub CLI. Mandatory work creates no real App key, PAT, installation token, webhook secret, public endpoint, organization, or paid resource.

2. Preflight and predictions

gh auth status
gh repo view OWNER/atlas-c27-integration-lab --json nameWithOwner,visibility,defaultBranchRef
cd atlas-c27-integration-lab
git status --short
git rev-parse HEAD

Record these predictions before acting:

  1. The first valid delivery ID will create exactly one durable receiver record and one DRY_RUN derived-action log.
  2. Re-sending the same delivery ID will return 2XX but will not create another record/action.
  3. A payload with a bad signature will be rejected before the business path and will not alter the database.
  4. An expired installation-token fixture will require “refresh then retry a safe read,” while a 403 fixture will require permission/policy diagnosis rather than token refresh.

3. Build the local receiver/sender from Lesson 2

Use the exact receiver.py and send_fixture.py implementations from Lesson 2. Keep the synthetic secret local:

export CH27_WEBHOOK_SECRET='chapter27-local-only'
python receiver.py

In a second shell:

export CH27_WEBHOOK_SECRET='chapter27-local-only'
python send_fixture.py --delivery 27272727-2222-4333-8444-555555555555
python send_fixture.py --delivery 27272727-2222-4333-8444-555555555555
python send_fixture.py --delivery 27272727-2222-4333-8444-555555555555 --bad-signature

Expected observations: 202 accepted, then 200 duplicate ignored, then 401 invalid signature.

4. Prove the predictions independently

python - <<'PY'
import sqlite3, json
with sqlite3.connect('chapter27-deliveries.db') as db:
    rows=db.execute('SELECT delivery_id,event,action,payload_sha256 FROM deliveries ORDER BY accepted_at').fetchall()
print(json.dumps(rows, indent=2))
assert len(rows) == 1, rows
assert rows[0][0] == '27272727-2222-4333-8444-555555555555'
print('PASS: one durable event despite redelivery + rejected spoof')
PY

Do not count console lines as the only proof. The database uniqueness constraint is the independent state that explains why a redelivery does not create a second action.

5. Simulate installation-token expiry and classify retry

Create metadata only—never a token value:

{
  "installation_id": 990027,
  "repository": "learner-example/atlas-c27-integration-lab",
  "permissions": {"issues": "read", "checks": "write"},
  "expires_at": "2000-01-01T00:00:00Z",
  "last_api_result": {"status": 401, "message": "Bad credentials"}
}

Your runbook should classify this as: preserve request ID/status → confirm installation still exists → mint/retrieve a new short-lived installation token using the App's trusted server-side credential path → retry an idempotent read once → verify. Now change the fixture status to 403; the correct runbook must not simply refresh. It must inspect App permissions, repository selection, organization policy, and endpoint requirements.

6. Produce the permission/event matrix

Save INTEGRATION_POLICY.md with at least the following:

# Chapter 27 integration permission/event policy

## Identity
- Preferred service identity: GitHub App installation.
- Installation: selected disposable repositories by default.
- No machine user or human PAT for the production service.

## Events
- issues: subscribe only if issue-open/update behavior is required.
- check_run/check_suite: only if the App supports reruns/requested actions.
- No organization events unless a reviewed org-level feature requires them.

## Repository permissions
- Issues: read for issue-policy evaluation.
- Contents: read only when source inspection is required.
- Checks: write only for the optional check publisher.
- Administration: none.

## Reliability
- Verify X-Hub-Signature-256 on raw bytes.
- Store X-GitHub-Delivery as durable idempotency key.
- Acknowledge quickly, process asynchronously in production.
- Reconcile current API state for order-sensitive decisions.
- Redelivery is explicit; same delivery GUID must not duplicate effects.

## Credential lifecycle
- App JWT <= 10 minutes.
- Installation token expires after 1 hour.
- Private key/webhook-secret rotation is documented and tested.
- Credential leak response begins with revoke/rotate.

This file is not a GitHub-enforced permission itself. It is the reviewed design contract against which an App registration/installation can later be audited.

7. Model real delivery failure and redelivery

Write a recovery note with this sequence: receiver unavailable → GitHub records failure after timeout/failed response → alert operator → restore receiver → inspect recent delivery evidence → explicitly redeliver only failed relevant delivery → same delivery GUID reaches receiver → dedupe/state machine determines whether business work already committed. GitHub does not automatically perform that redelivery, and current GitHub.com delivery records/redelivery are available for the recent three-day window.

Race rule: Do not assume arrival order. If the event could overwrite newer state, reconcile the current GitHub resource before applying the derived action.

8. Optional live extension: publish one check with least privilege

Only perform this if you intentionally registered a disposable GitHub App, installed it on only the lab repository, granted Checks: write, and securely hold a current installation access token. Creating the App private key/token is security-sensitive and outside mandatory learning.

SHA=$(git rev-parse HEAD)
cat > check-run.json <<EOF
{
  "name": "chapter27-checkpoint",
  "head_sha": "$SHA",
  "status": "completed",
  "conclusion": "success",
  "output": {
    "title": "Chapter 27 integration checkpoint",
    "summary": "Signed fixture accepted; duplicate suppressed; policy matrix reviewed."
  }
}
EOF

# Run only with an ephemeral App installation token supplied securely to curl.
curl -L --request POST \
  --url "https://api.github.com/repos/OWNER/REPO/check-runs" \
  --header "Accept: application/vnd.github+json" \
  --header "Authorization: Bearer INSTALLATION_ACCESS_TOKEN" \
  --header "X-GitHub-Api-Version: 2026-03-10" \
  --data @check-run.json

Independently verify with your normal read-capable GitHub CLI identity:

gh api -H "X-GitHub-Api-Version: 2026-03-10" \
  repos/OWNER/REPO/commits/$SHA/check-runs \
  --jq '.check_runs[] | select(.name=="chapter27-checkpoint") | {id,name,status,conclusion,head_sha,app:.app.slug}'

If you do not perform the optional live path, use the request/response fixture from Lesson 2 and document why “Checks write” remains outside mandatory permissions.

9. Final production operating model

Control Checkpoint evidence Production requirement
Identity Permission matrix chooses GitHub App App owner, installation scope, permissions/events reviewed
Ingress authenticity Bad signature rejected HMAC-SHA256 raw-body validation; HTTPS; secret rotation
Replay/idempotence Duplicate delivery produces one DB row Durable delivery state machine/unique key
Ordering Runbook requires current-state reconcile Do not trust delivery order or immediacy
Credential lifecycle Synthetic expiry decision JWT max window + installation-token refresh/revocation handling
Feedback Optional check fixture/live check Checks write only where rich check feedback is required
Recovery Explicit redelivery model Delivery monitoring, bounded redelivery, same-ID dedupe, audit logs

10. Cleanup/rollback

  • Stop receiver.py and remove the local SQLite DB if no longer needed.
  • Commit INTEGRATION_POLICY.md only if you want to retain the training design; do not commit any secret/token/key.
  • If a real App was created: uninstall it from the disposable repository, revoke/delete private keys, rotate/remove the webhook secret, revoke active installation tokens where applicable, and verify the App no longer has repository access.
  • Archive/delete the disposable repository only if you intentionally want to end the lab; repository deletion itself is destructive and not required.

Knowledge check

The first delivery succeeded, but an operator redelivers it while investigating an outage. What prevents a duplicate action?

A webhook is correctly signed but refers to an installation/repository your service should not manage. May it proceed?

A short-lived installation token returns 401 after one hour. What is the safe retry pattern?

Why is Checks write excluded from the mandatory permission matrix?

What is the key difference between handling 401 and 403 for App API calls?

What Chapter 27 adds to the production GitHub operating model

You can now model external GitHub automation as a production service: dedicated App identity, selected installation scope, minimal permission/event matrix, cryptographically verified webhook ingress, durable replay/idempotency control, current-state reconciliation, short-lived token lifecycle, explicit failed-delivery recovery, and structured check feedback. Chapter 28 moves from one integration's authorization boundary to organization-wide teams, roles, repository access, enterprise policies, and delegated administration.

Next chapter

Organizations, Teams, Roles, Repository Access, Enterprise Policies, and Delegated Administration: Concepts, Architecture, and Mental Model

Official references

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.