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:
ref comes from GITHUB_REF.after comes from GITHUB_SHA.repository.full_name comes from GITHUB_REPOSITORY.repository.default_branch is currently hardcoded to main.sender.login is currently hardcoded to github-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 signature
The 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:
queued.payload.after.202 response to the caller.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_SECRET
WEBHOOK_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:
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-256
The 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 signature
To 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:
set -e so command failures stop the deployment..env file alongside the script.main.slackzilla.slackzilla-webhook.The script:
REPO_URL.HEAD commit 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:
npm ci.npm run build.The script:
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:
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 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:
This allows Slackzilla to automatically deploy changes pushed to the configured branch without requiring a manual deployment after every commit.