Skip to main content
Webhooks enable Heimdall to automatically run security scans whenever code is pushed to your repositories.

Overview

Heimdall supports webhooks from:
  • GitHub (HMAC-SHA256 signature verification)
  • GitLab (token-based verification)
When a webhook is received, Heimdall:
  1. Verifies the signature/token
  2. Checks if the repository is registered
  3. Creates a new scan at the pushed commit SHA
  4. Triggers the full scan pipeline
  5. Returns a 200 OK response with scan details

Setting Up Webhooks

Generate a Webhook Secret

First, generate a secure random secret:
openssl rand -hex 20
Add it to your .env file:
WEBHOOK_SECRET=your_generated_secret_here
The webhook secret is shared between Heimdall and your Git provider. Keep it secure and never commit it to version control.

Configure Your Git Provider

  1. Go to Repository SettingsWebhooksAdd webhook
  2. Set Payload URL: https://heimdall.example.com/webhooks/github
  3. Set Content type: application/json
  4. Set Secret: Your WEBHOOK_SECRET value
  5. Select Just the push event
  6. Ensure Active is checked
  7. Click Add webhook

Signature Verification

All webhook requests are verified before processing.

GitHub (HMAC-SHA256)

GitHub signs webhooks with HMAC-SHA256:
  1. GitHub generates: HMAC-SHA256(secret, payload)
  2. Sends signature in header: X-Hub-Signature-256: sha256=<hex_digest>
  3. Heimdall recomputes the signature using the payload and WEBHOOK_SECRET
  4. Signatures are compared in constant-time to prevent timing attacks
Implementation: src/routes/webhooks.rs:327-343
fn verify_github_signature(secret: &str, body: &[u8], signature_header: &str) -> bool {
    use hmac::{Hmac, Mac};
    use sha2::Sha256;
    
    let Some(hex_sig) = signature_header.strip_prefix("sha256=") else {
        return false;
    };
    let Ok(expected) = hex::decode(hex_sig) else {
        return false;
    };
    let Ok(mut mac) = HmacSha256::new_from_slice(secret.as_bytes()) else {
        return false;
    };
    mac.update(body);
    mac.verify_slice(&expected).is_ok()
}

GitLab (Token)

GitLab uses simple token-based verification:
  1. GitLab sends the secret token in header: X-Gitlab-Token: <secret>
  2. Heimdall compares the token to WEBHOOK_SECRET (exact string match)
  3. Mismatch returns 401 Unauthorized
Implementation: src/routes/webhooks.rs:156-165
GitHub’s HMAC approach is more secure as it prevents replay attacks. For GitLab, ensure your WEBHOOK_SECRET is sufficiently long (at least 40 characters).

Supported Events

Push Events

Both GitHub and GitLab push events trigger full scans. GitHub payload (simplified):
{
  "ref": "refs/heads/main",
  "after": "abc123...",
  "repository": {
    "full_name": "owner/repo",
    "clone_url": "https://github.com/owner/repo.git"
  }
}
GitLab payload (simplified):
{
  "object_kind": "push",
  "ref": "refs/heads/main",
  "after": "abc123...",
  "project": {
    "path_with_namespace": "owner/repo",
    "http_url": "https://gitlab.com/owner/repo.git"
  }
}

Ignored Events

Other event types are accepted but ignored:
  • Pull requests / Merge requests
  • Issues
  • Comments
  • Releases
Heimdall responds with:
{
  "status": "ok",
  "data": {
    "status": "ignored",
    "reason": "event type 'pull_request' is not handled"
  }
}

Webhook Response

Successful Scan Trigger

Status: 200 OK Body:
{
  "status": "ok",
  "data": {
    "status": "scan_triggered",
    "scan_id": "550e8400-e29b-41d4-a716-446655440000",
    "repo_id": "660e8400-e29b-41d4-a716-446655440000",
    "commit_sha": "abc123def456...",
    "ref": "refs/heads/main"
  }
}

Repository Not Found

Status: 404 Not Found Body:
{
  "status": "error",
  "code": 404,
  "message": "No registered repo matches URL: https://github.com/owner/repo.git"
}
Solution: Ensure the repository is added to Heimdall before setting up the webhook.

Invalid Signature

Status: 401 Unauthorized Body:
{
  "status": "error",
  "code": 401,
  "message": "Invalid signature"
}
Solution: Verify WEBHOOK_SECRET matches between Heimdall and your Git provider.

Troubleshooting

Webhook Secret Not Configured

Symptom: Server logs show “WEBHOOK_SECRET is not configured” Solution:
# Generate a secret
openssl rand -hex 20

# Add to .env
echo "WEBHOOK_SECRET=<generated_secret>" >> .env

# Restart Heimdall
docker compose restart heimdall

Signature Verification Fails

GitHub symptoms:
  • Webhook delivery shows “401 Unauthorized”
  • Heimdall logs: “GitHub webhook signature verification failed”
Solutions:
  1. Verify the secret in GitHub webhook settings matches WEBHOOK_SECRET
  2. Check for extra whitespace in .env
  3. Ensure Content-Type: application/json is set in GitHub
GitLab symptoms:
  • Webhook test shows “401 Unauthorized”
  • Heimdall logs: “GitLab webhook token verification failed”
Solutions:
  1. Verify the token in GitLab webhook settings matches WEBHOOK_SECRET exactly
  2. Check for trailing newlines or spaces in the secret

Repository Not Found

Symptom: Webhook delivers but returns 404 Not Found Solutions:
  1. Verify the repository is added to Heimdall
  2. Check the remote_url in the database matches the webhook payload:
    SELECT id, name, remote_url FROM repos;
    
  3. Re-add the repository if URLs don’t match

Webhook Delays

Symptom: Scans start several minutes after push Cause: Background worker polling interval Solutions:
  1. Check WORKER_POLL_INTERVAL_SECS in .env (default: 5 seconds)
  2. Reduce for faster scan starts:
    WORKER_POLL_INTERVAL_SECS=2
    
  3. For development, disable worker mode for instant inline scans:
    WORKER_ENABLED=false
    
See src/routes/webhooks.rs:265-312 for worker vs. inline execution logic.

Firewall / Network Issues

Symptom: Webhooks never arrive at Heimdall Solutions:
  1. Verify your webhook endpoint is publicly accessible:
    curl -X POST https://heimdall.example.com/webhooks/github \
      -H "Content-Type: application/json" \
      -d '{}'
    # Should return 400 (bad request) not connection refused
    
  2. Check firewall rules allow inbound HTTPS
  3. Verify DNS resolves correctly
  4. For GitHub, check the IP allowlist: GitHub IP ranges

Security Best Practices

  1. Always verify signatures: Never disable signature verification
  2. Use HTTPS: Webhooks should only be sent to HTTPS endpoints in production
  3. Rotate secrets periodically: Update WEBHOOK_SECRET and reconfigure webhooks
  4. Limit webhook scope: Only subscribe to push events
  5. Monitor failed deliveries: Set up alerts for repeated webhook failures

Testing Webhooks Locally

For local development, use a tunnel service like ngrok:
# Start Heimdall locally
cargo run --bin heimdall

# In another terminal, start ngrok
ngrok http 8080

# Use the ngrok URL for webhook configuration
# Example: https://abc123.ngrok.io/webhooks/github
ngrok URLs are public. Don’t use production secrets in local development.

API Reference

EndpointMethodAuthDescription
/webhooks/githubPOSTHMAC-SHA256GitHub webhook receiver
/webhooks/gitlabPOSTTokenGitLab webhook receiver
See API Reference for full details.

Build docs developers (and LLMs) love