Chapter 29Lesson 05~370 minutes

Checkpoint Lab — REST API, GraphQL API, Webhooks, System Hooks, Pagination, Rate Limits, and Automation

Build and validate a small resilient automation workflow that paginates reads, handles GraphQL cursors/errors, verifies and deduplicates a signed webhook, performs an idempotent mutation, and cleans up safely.

CheckpointAutomation clientDeduplicationEvidenceCleanup

Learning objectives

  • Build a small automation checkpoint that reads complete GitLab state and preserves sanitized HTTP evidence.
  • Paginate GraphQL with cursors and explicitly inspect errors.
  • Authenticate, replay, and tamper-test a synthetic signed webhook locally.
  • Run the same desired-state mutation twice and prove duplicate safety.
  • Clean up the disposable mutation/credentials and document a production runbook.
Availability baseline — verified 2026-08-22 against GitLab 19.3. REST, GraphQL, glab api, and project webhooks have Free-compatible paths across GitLab.com, Self-Managed, and Dedicated. Group webhooks require Premium/Ultimate. System hooks are instance-wide administrator controls documented for Self-Managed and Dedicated, while the current System Hooks REST API reference is Self-Managed-specific. New webhooks should prefer the GitLab 19.1+ HMAC-SHA256 signing-token mechanism. The mandatory chapter path uses a GitLab Free disposable project, a local synthetic receiver, and one harmless label mutation; it does not require a public webhook endpoint, paid tier, administrator access, or production token.

1. Checkpoint scenario and preflight

Your team is building an integration worker for a disposable project. It needs a complete project inventory read, an issue read model, fast event intake, and one managed marker label. The mandatory exercise remains GitLab Free and local-only for webhook delivery. Use a project where deleting the lab label cannot affect real automation.

glab auth status
glab api projects/:fullpath | jq '{id,path_with_namespace,visibility}'
mkdir -p ch29-checkpoint/evidence && cd ch29-checkpoint
Preflight: confirm the current GitLab host/project, your role is sufficient for labels, no real integration depends on automation-managed, and no token/signing key will be written to evidence or Git.

2. Predict four state transitions before execution

  1. REST pagination will retrieve every label even if the first page contains only two.
  2. GraphQL continuation will stop only when hasNextPage=false.
  3. Replaying the same authenticated webhook ID will produce no second downstream action.
  4. Running the label reconciler twice will leave exactly one desired label and the second run will be a no-op.

Write these predictions to evidence/predictions.md.

3. REST read and sanitized response evidence

glab api -i "projects/:fullpath/labels?per_page=2" > evidence/labels-first-page.txt
# Remove any accidental authorization-related header before retaining evidence.
sed -i.bak -E '/^(PRIVATE-TOKEN|Authorization|JOB-TOKEN):/Id' evidence/labels-first-page.txt
rm -f evidence/labels-first-page.txt.bak

glab api projects/:fullpath/labels --paginate --output ndjson   | jq '{id,name,color}' > evidence/labels-all.ndjson

Record the project ID/path and observed pagination mechanism. If you implement this outside glab, follow Link rel="next" exactly; do not invent the next URL.

4. GraphQL cursor evidence

glab api graphql --paginate --output ndjson   -f fullPath='training/api-automation-lab'   -f query='query($fullPath: ID!, $endCursor: String) {
    project(fullPath: $fullPath) {
      issues(first: 2, after: $endCursor) {
        nodes { iid title state }
        pageInfo { hasNextPage endCursor }
      }
    }
  }' > evidence/issues.ndjson

# Inspect error fields if your wrapper stores the full GraphQL envelopes.

Do not store more issue text than the exercise needs. Production logging should use identifiers/status and redact fields that can contain confidential issue content.

5. Signed webhook: accept, replay, then tamper

Reuse the verified receiver.py and send_webhook.py from Lesson 2. Generate a fresh ephemeral signing token and fixed event ID. Send the same signed event twice: one process action, one duplicate no-op. Then create a negative test by changing one body byte after calculating the signature; the receiver must return 401 and perform no action.

export WEBHOOK_SIGNING_TOKEN="$(python - <<'PYCODE'
import base64, os
print('whsec_' + base64.b64encode(os.urandom(32)).decode())
PYCODE
)"
export WEBHOOK_ID='ch29-checkpoint-001'
# terminal 1: python receiver.py
# terminal 2: python send_webhook.py; python send_webhook.py
# Expected receiver evidence: duplicate false, then true.
# Negative test: mutate the exact body after signing; expected HTTP 401.

Preserve only event ID, timestamps, verification result, duplicate result, and selected event kind. Never save the signing token.

6. Desired-state label: converge twice

#!/usr/bin/env bash
set -euo pipefail
LABEL="automation-managed"
DESC="Chapter 29 disposable automation marker"
COLOR="#428BCA"

current="$(glab api projects/:fullpath/labels --paginate --output ndjson   | jq -s --arg n "$LABEL" '[.[] | select(.name == $n)] | first')"

if [ "$current" = "null" ]; then
  echo "create: label absent"
  glab api --method POST projects/:fullpath/labels     -f name="$LABEL" -f color="$COLOR" -f description="$DESC" >/dev/null
else
  id="$(jq -r '.id' <<<"$current")"
  actual="$(jq -r '.description // ""' <<<"$current")"
  if [ "$actual" != "$DESC" ]; then
    echo "update: label exists but differs"
    glab api --method PUT "projects/:fullpath/labels/$id"       -f description="$DESC" -f color="$COLOR" >/dev/null
  else
    echo "no-op: desired state already present"
  fi
fi

glab api projects/:fullpath/labels --paginate --output ndjson   | jq --arg n "$LABEL" 'select(.name == $n) | {id,name,color,description}'
chmod +x ensure-label.sh
./ensure-label.sh | tee evidence/label-run-1.txt
./ensure-label.sh | tee evidence/label-run-2.txt

glab api projects/:fullpath/labels --paginate --output ndjson  | jq -s '[.[] | select(.name=="automation-managed")] | {count:length,items:.}'  | tee evidence/label-final.json
# PASS requires count == 1 and the second run to be no-op.

7. Write the retry policy before productionizing

Condition Checkpoint rule
2xx read Process and continue pagination.
401/403 Stop; diagnose identity/role/scope. Do not retry.
404 Confirm path/resource visibility before assuming absence.
409 Read current state; decide whether conflict already represents desired state.
429 Honor Retry-After, add jitter, reduce concurrency; re-read before writes.
5xx/network failure on GET Bounded exponential retry with jitter.
5xx/network failure on mutation Treat outcome as uncertain; read current state before replay.

8. Optional real webhook and system-hook governance

If you already have a private, disposable, HTTPS receiver you control, a project Maintainer/Owner may optionally create a project webhook and select only the needed events. Prefer a signing token, test once, capture the hook/delivery IDs, then delete the hook by ID and verify it is absent. The mandatory path never requires a public tunnel.

For system hooks, write a governance fixture rather than changing an instance: owner = platform administrators; events = only required instance lifecycle events; signing token stored in a secret manager; receiver ACL/monitoring; rotation runbook; retention of sanitized delivery IDs/status; documented Self-Managed/Dedicated/API support assumption.

9. Cleanup and residual-state verification

Delete only the checkpoint label and any optional hook/token you explicitly created. Then independently verify absence.

id="$(glab api projects/:fullpath/labels --paginate --output ndjson   | jq -r 'select(.name == "automation-managed") | .id' | head -n1)"
[ -n "$id" ] && glab api --method DELETE "projects/:fullpath/labels/$id" >/dev/null

if glab api projects/:fullpath/labels --paginate --output ndjson  | jq -e 'select(.name == "automation-managed")' >/dev/null; then
  echo 'FAIL: lab label remains' >&2; exit 1
fi
unset WEBHOOK_SIGNING_TOKEN WEBHOOK_ID
# Scan evidence for obvious credential patterns before retaining it.
if grep -RniE 'PRIVATE-TOKEN:|Authorization: Bearer|JOB-TOKEN:|whsec_[A-Za-z0-9+/=]{20,}' evidence; then
  echo 'FAIL: review evidence for credential leakage' >&2; exit 1
fi
echo 'PASS: mutation removed and evidence scan clean'

10. Verification checklist

  • REST collection completion uses --paginate or server-provided next links.
  • GraphQL query accepts/uses a cursor and errors are checked.
  • Webhook HMAC and timestamp are verified before parse/action.
  • Same webhook ID is harmless on replay; tampered payload is rejected.
  • Same desired-state label automation run twice leaves one object.
  • 429/write-uncertainty policy distinguishes reads from mutations.
  • Evidence contains resource/request/event IDs and outcomes but no credentials.
  • Disposable mutation and optional integration resources are removed and read-back verified.

11. What Chapter 29 adds to the production GitLab operating model

GitLab is now an integration platform in your model, not just a UI/CI host. Production automation has explicit host/project identity, least-privilege credentials, complete pagination, GraphQL error semantics, authenticated/deduplicated event intake, bounded rate-limit handling, desired-state mutation, and audit-friendly evidence. Chapter 30 moves to Self-Managed administration—configuration, email, object storage, backup, restore, and maintenance—where automation must respect an even larger blast radius.

Knowledge check

What proves REST list completeness?

Why replay the same webhook ID?

Why is a tampered-body test important?

What proves the label mutation is idempotent?

Should a 429 on a mutation trigger the same retry as a GET?

What is the Chapter 30 bridge?

12. Chapter close

You have completed the GitLab automation foundation: documented machine interfaces, robust collection traversal, verified event intake, and duplicate-safe mutations. Keep these invariants as the course moves into administrator-controlled infrastructure.

Primary sources and version notes

These lessons were finalized against current official GitLab documentation on 2026-08-22. API fields, rate limits, webhook event schemas, CLI flags, and tier/offering availability can change, so production clients should pin/document assumptions and re-check the API/CLI documentation for the deployed GitLab version.

Next chapter

Self-Managed Administration: Configuration, Email, Object Storage, Backups, Restore, and Maintenance

Apply the same evidence-first automation discipline to administrator-level GitLab infrastructure and recovery operations.

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
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.