Repository navigation
webhook
Slackzilla exposes a deployment webhook at POST /webhook, handled by server/server.js. The webhook service shares port 9000 with the Slackzilla dashboard and API.
The webhook is used by the GitHub Actions deployment workflow to trigger a deployment when changes are pushed to the configured deployment branch.
The GitHub Actions workflow sends a POST request to https://rylvion.hackclub.app/webhook with a JSON payload containing the branch and commit information.
The request uses these headers:
Content-Type: application/json
X-GitHub-Event: push
X-GitHub-Delivery: <github-run-id>-<github-run-attempt>
X-Hub-Signature-256: sha256=<hmac-sha256>Slackzilla does not receive GitHub's full native push event payload. The deployment workflow constructs a smaller payload containing only the information required by the webhook:
{
"ref": "refs/heads/main",
"after": "<commit-sha>",
"repository": {
"full_name": "<owner>/<repository>",
"default_branch": "main"
},
"sender": {
"login": "github-actions"
}
}The values are generated by the GitHub Actions workflow:
-
refcomes fromGITHUB_REF. -
aftercomes fromGITHUB_SHA. -
repository.full_namecomes fromGITHUB_REPOSITORY. -
repository.default_branchis currently hardcoded tomain. -
sender.loginis currently hardcoded togithub-actions.
The webhook currently uses ref and after. The other payload fields are retained as metadata but are not required for deployment.
The workflow signs the exact JSON payload before sending it:
sha256=HMAC-SHA256(WEBHOOK_SECRET, raw request body)Slackzilla reads the raw request body before parsing JSON so that the signature is calculated against exactly the same bytes that were signed. The server calculates its own signature:
const expected = `sha256=${crypto.createHmac("sha256", webhookSecret).update(body).digest("hex")}`;The supplied and calculated signatures are compared using crypto.timingSafeEqual(). If the signature is missing, malformed, or does not match, the request receives:
401 Invalid signatureThe webhook secret used by GitHub Actions must match the WEBHOOK_SECRET configured for the Slackzilla webhook service. When rotating the secret, update both locations and restart the webhook service.
The webhook validates the event after verifying the signature. Only requests with: X-GitHub-Event: push are processed. Other event types receive 202 Ignored non-push event and do not trigger a deployment. The deployment branch is controlled by the server's BRANCH configuration and defaults to main.
The webhook expects: payload.ref = refs/heads/main (or the configured branch). If the push is to another branch that doesnt match the .env configuration, the request receives 202 Ignored push for refs/heads/<branch> and does not trigger a deployment. For example, when BRANCH=main, refs/heads/main triggers deployment. A push to another branch is ignored with 202 Ignored push for refs/heads/<branch> and the deployment script is not executed.
Once a valid push for the configured branch is received, the webhook:
- Records the deployment as
queued. - Stores the commit from
payload.after. - Sends an early
202response to the caller. - Broadcasts the queued deployment state to connected admin clients.
- Runs the configured deployment script.
- Records the final deployment state.
- Broadcasts the result to connected admin clients.
- Refreshes and broadcasts the current Slackzilla status.
The deployment runs after the HTTP response has been sent, so the webhook does not keep the GitHub Actions request waiting for the entire deployment.
A successful deployment records the deployment status, timestamp, commit, output, and a result of success. The deployment output is stored and broadcast to connected admin clients. The webhook also refreshes the runtime status and broadcasts the updated state.
A failed deployment records the deployment status, timestamp, commit, error, and a result of failed. The deployment error is also appended to the deployment log.
The deployment workflow requires these secrets:
WEBHOOK_URL
WEBHOOK_SECRETWEBHOOK_URL should point to the Slackzilla webhook endpoint: https//rylvion.hackclub.app/webhook.
WEBHOOK_SECRET must contain the same secret configured for the Slackzilla webhook service.
The workflow constructs the payload, calculates its HMAC signature, and sends the request with curl.
The workflow process is:
- GitHub push
- GitHub Actions workflow
- Construct deployment payload
- Calculate HMAC-SHA256
- POST /webhook
- Slackzilla verifies signature
- Check event
- Check branch
- Run deployment
If the webhook is exposed through a reverse proxy , the proxy must forward the request body and webhook headers unchanged. In particular, the following header must reach Slackzilla:
X-Hub-Signature-256The request body must also remain byte-for-byte equivalent to the body that was signed.
Do not parse, re-serialise, or otherwise modify the JSON body before it reaches Slackzilla, as this can cause HMAC verification to fail.
An unsigned request should be rejected:
curl -i -X POST http://localhost:9000/webhook -H "X-GitHub-Event: push" -H "Content-Type: application/json" -d "{\"ref\":\"refs/heads/main\"}"Expected response:
HTTP/1.1 401 Unauthorized
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Content-Type: text/plain; charset=utf-8
Date: Tue, 06 Oct 2026 18:12:51 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 18
Invalid signatureTo test a valid request, calculate an HMAC-SHA256 signature over the exact request body using the configured WEBHOOK_SECRET.
For example:
payload='{"ref":"refs/heads/test","after":"0000000000000000000000000000000000000000","repository":{"full_name":"test/test","default_branch":"main"},"sender":{"login":"github-actions"}}'
signature="sha256=$(printf '%s' "$payload" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -hex | awk '{print $2}')"
curl -i -X POST http://localhost:9000/webhook \
-H "Content-Type: application/json" \
-H "X-GitHub-Event: push" \
-H "X-GitHub-Delivery: local-test" \
-H "X-Hub-Signature-256: $signature" \
--data "$payload"Using a non-deployment branch is recommended when testing the webhook so that the deployment script is not invoked.
| Status | Response | Meaning |
|---|---|---|
202 |
Accepted deploy for refs/heads/<branch> |
Valid deployment request accepted and deployment started asynchronously. |
202 |
Ignored non-push event |
The request was authenticated but was not a push event. |
202 |
Ignored push for <ref> |
The request was authenticated but targeted another branch. |
400 |
Invalid JSON payload |
The request body could not be parsed as JSON. |
401 |
Invalid signature |
The HMAC signature was missing or invalid. |
405 |
Method not allowed |
The request was not a POST request. |
| Symptom | Likely cause |
|---|---|
401 Invalid signature |
Secret mismatch, modified request body, missing signature header, or proxy configuration issue. |
400 Invalid JSON payload |
The request body is not valid JSON. |
202 Ignored non-push event |
X-GitHub-Event is not push. |
202 Ignored push for ... |
payload.ref does not match the configured deployment branch. |
| Deployment is marked failed | Inspect the deployment output and logs, then check Git, permissions, paths, dependencies, and deployment commands. |
GitHub receives 202 but deployment later fails |
Expected behaviour. The webhook acknowledges the request before the deployment script finishes. |
The webhook service user must have access to the Slackzilla working tree, Git repository, deployment script, required commands, and any files or services used by the deployment process.
The deployment script automates updating Slackzilla from its remote Git repository.
It can be invoked by the webhook after a valid GitHub push or directly through the admin dashboard's redeploy action.
The exact deployment behaviour is defined by server/deploy.sh.
The script performs the following operations.
The script:
- Enables
set -eso command failures stop the deployment. - Loads configuration from the
.envfile alongside the script. - Determines the project directory, target branch, and service names.
- Uses configured values where available.
- Defaults the deployment branch to
main. - Defaults the main service to
slackzilla. - Defaults the webhook service to
slackzilla-webhook.
The script:
- Changes to the configured project directory.
- Updates the Git remote using
REPO_URL. - Fetches the latest commits from the configured branch.
- Compares the local
HEADcommit with the fetched remote commit.
If the local and remote commits are identical, deployment finishes successfully without rebuilding or restarting the application.
If the commits differ, deployment continues.
When changes are detected, the script:
- Resets the working tree to the fetched remote branch.
- Installs dependencies with
npm ci. - Builds the React client with
npm run build.
The script:
- Restarts the configured Slackzilla systemd service.
- Restarts Nginx when required by the deployment configuration.
- Schedules a delayed restart of the webhook service using
systemd-run.
The webhook restart is scheduled separately so the active deployment process can finish before the webhook service itself is restarted.
A successful deployment prints the deployed commit and exits with status 0.
If a command fails, the script exits with a non-zero status. The calling process then records the deployment as failed.
If the local repository already matches the remote branch:
- No dependencies are reinstalled.
- The client is not rebuilt.
- Services are not restarted.
- The deployment still finishes successfully.
After the script completes successfully, the webhook records the deployment as successful, stores the deployment output, refreshes runtime status, and broadcasts the updated state to connected admin clients.
If the script fails:
- The failing command stops the deployment.
- The webhook records the deployment as failed.
- The deployment error is stored.
- The error is appended to the deployment log.
- The updated failure state is broadcast to connected admin clients.
The webhook service restart is scheduled separately rather than performed directly inside the active deployment process.
See Webhook Service for the webhook service's request handling and deployment lifecycle.
Slackzilla's deployment webhook provides an authenticated bridge between the GitHub Actions workflow and the Slackzilla deployment system.
A successful workflow run follows this process:
- GitHub Actions
- POST /webhook
- Verify HMAC signature
- Verify push event
- Verify deployment branch
- Queue deployment
- Run deploy.sh
- Update deployment state
- Broadcast result
This allows Slackzilla to automatically deploy changes pushed to the configured branch without requiring a manual deployment after every commit.