Webhooks
Document metadata
- Type
- Guide
- Status
- stable
- Description
- Which GitHub App events DocMyRepos handles, how a delivery is verified and answered, and why redelivering or repeating an event is safe.
- Sources
- packages/server/src/webhooks/endpoint.ts
- packages/server/src/webhooks/verify.ts
- packages/server/src/webhooks/router.ts
- packages/server/src/webhooks/service.ts
- Generated
- Verified
- Stale after
- (in 972 years)
Webhooks
The GitHub App has one webhook. It tells DocMyRepos when a repository's default branch changes and when repositories are added to or removed from the installation, so content stays fresh without waiting for the scheduled sync. The URL is https://<your-host>/api/webhooks/github. How to configure it is covered in GitHub App setup.
Events
DocMyRepos handles four events. Every handled event must carry the configured installation id; events for another installation of the same App are ignored.
| Event | Action | Result |
|---|---|---|
push |
to the default branch, not a branch deletion | Sync that repository, if it is selected by the config and not archived. |
repository |
created, edited, transferred, publicized, unarchived |
Sync the repository if it is selected and not archived. |
repository |
renamed |
Sync it if the new name is selected, otherwise remove it. |
repository |
privatized |
Mark it private at once, then sync it. |
repository |
deleted, archived |
Remove the stored repository. |
installation_repositories |
added |
Sync each added repository that is selected. A full sync when the selection is all or more than 10 were added. |
installation_repositories |
removed |
Remove each listed repository. |
installation |
deleted, suspend |
Mark every repository error and keep all content. |
installation |
unsuspend, created, new_permissions_accepted |
Start a full sync. |
ping |
Answer 200, do nothing. |
Any other event, any other action, and any payload whose used fields do not validate is acknowledged and ignored.
How a delivery is processed
flowchart TD
R["POST /api/webhooks/github"] --> SEC{"Webhook secret configured?"}
SEC -- no --> E500["500 misconfigured"]
SEC -- yes --> CT{"Content type is application/json?"}
CT -- no --> E415["415"]
CT -- yes --> SZ{"Body within 25 MiB?"}
SZ -- no --> E413["413"]
SZ -- yes --> SIG{"Signature valid?"}
SIG -- no --> E401["401"]
SIG -- yes --> EV{"Event headers and JSON object valid?"}
EV -- no --> E400["400"]
EV -- yes --> PING{"Event is ping?"}
PING -- yes --> OK200["200"]
PING -- no --> D["Dispatch to the event router"]
D --> OK202["202, work continues in the background"]
The order matters: nothing is parsed and nothing touches the database before the signature checks out.
Signature
GitHub signs each delivery with HMAC-SHA256 over the exact bytes of the body, keyed with the webhook secret, and sends X-Hub-Signature-256: sha256=<64 lowercase hex characters>. DocMyRepos hashes the raw bytes it received and compares in constant time. Uppercase hex, a wrong length, a repeated header and the legacy X-Hub-Signature (SHA-1) header are all rejected. The webhook secret must be at least 16 characters when you save it. With no webhook secret saved at /admin/workspace, nothing verifies and every delivery gets the same fixed 401 as a bad signature.
To test a deployment without GitHub, sign a ping yourself:
payload='{"zen":"test"}'
sig="sha256=$(printf '%s' "$payload" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -hex | sed 's/^.* //')"
curl -i -X POST http://127.0.0.1:3000/api/webhooks/github \
-H "Content-Type: application/json" \
-H "X-GitHub-Event: ping" \
-H "X-Hub-Signature-256: $sig" \
-d "$payload"
Expect 200 with {"ok":true,"event":"ping"}. Change one character of the payload after signing and expect 401.
Answering quickly
The handler waits up to 5 seconds for the router, well inside GitHub's 10 second delivery timeout. Syncs and removals are never awaited: the router starts them in the background and answers 202. Only two single-statement updates are awaited, because they must not be lost: marking a repository private on privatized, and marking repositories error when the installation is deleted or suspended. If one of those fails, or the router throws within the wait, the answer is 500 and GitHub records a failed delivery that you can redeliver.
Idempotency and repeated events
Processing is idempotent, and deliveries are not deduplicated by X-GitHub-Delivery:
- Syncing an unchanged repository fetches no tree and no blobs: after the installation listing and one conditional branch request, the stored tree SHA and ETag short-circuit the work.
- A push that arrives while the repository is syncing is coalesced: it sets a rerun marker, and the running sync runs again when it finishes. See Coalescing.
- GitHub's Redeliver button reuses the delivery id, so deduplicating on it would make a manual redelivery do nothing.
- Order is not assumed. A sync reads the installation listing first, so a late
pushfor a repository that was deleted finds nothing and creates nothing.
A webhook removal waits up to 30 seconds for a running sync of the same repository. If it gives up, the next full sync removes the repository.
When nothing happens
If a delivery shows 202 but the portal does not change, the event was ignored by design: another branch, a repository outside repos or exclude, another installation id, or an unhandled event. The webhook.decision log line says which, with a reason. The troubleshooting table in GitHub App setup lists the other causes, and Operations describes the log fields.