slackzilla

GitHub Webhook and Deployment Guide

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.

Request Contract

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>

Request payload

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:

The webhook currently uses ref and after. The other payload fields are retained as metadata but are not required for deployment.

HMAC Verification

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.

Event and Branch Filtering

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.

Deployment Lifecycle

Once a valid push for the configured branch is received, the webhook:

  1. Records the deployment as queued.
  2. Stores the commit from payload.after.
  3. Sends an early 202 response to the caller.
  4. Broadcasts the queued deployment state to connected admin clients.
  5. Runs the configured deployment script.
  6. Records the final deployment state.
  7. Broadcasts the result to connected admin clients.
  8. 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.

Successful 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.

Failed deployment

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.

GitHub Actions Configuration

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:

  1. GitHub push
  2. GitHub Actions workflow
  3. Construct deployment payload
  4. Calculate HMAC-SHA256
  5. POST /webhook
  6. Slackzilla verifies signature
  7. Check event
  8. Check branch
  9. Run deployment

Reverse Proxy Requirements

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.

Local Testing

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.

Response Codes

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.

Failure Diagnosis

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.

Deployment Script

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.

Deployment Process

The script performs the following operations.

1. Initialise the deployment environment

The script:

2. Verify the repository

The script:

3. Check for changes

If the local and remote commits are identical, deployment finishes successfully without rebuilding or restarting the application.

If the commits differ, deployment continues.

4. Synchronise the project

When changes are detected, the script:

5. Restart services

The script:

The webhook restart is scheduled separately so the active deployment process can finish before the webhook service itself is restarted.

6. Report the result

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.

Deployment Behaviour

No changes

If the local repository already matches the remote branch:

Successful deployment

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.

Failed deployment

If the script fails:

Webhook service restart

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.

Summary

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:

  1. GitHub Actions
  2. POST /webhook
  3. Verify HMAC signature
  4. Verify push event
  5. Verify deployment branch
  6. Queue deployment
  7. Run deploy.sh
  8. Update deployment state
  9. Broadcast result

This allows Slackzilla to automatically deploy changes pushed to the configured branch without requiring a manual deployment after every commit.