Webhooks
Webhooks send HTTP POST requests to your endpoint when events occur in Gaffer. Use them to integrate test results with your monitoring tools, trigger deployments, update dashboards, or build custom notification flows.
Features
Section titled “Features”- Test run and coverage events — receive notifications for either or both event types
- Branch filtering — only trigger for specific branches using glob patterns
- Configurable triggers — fire on all events, failures only, or after consecutive failures
- HMAC-SHA256 signing — verify that payloads are from Gaffer
- Test delivery — send a test payload from the dashboard to verify your endpoint
Creating a Webhook
Section titled “Creating a Webhook”- Navigate to Settings > Webhooks in your Gaffer dashboard
- Click “Add Webhook”
- Enter a name and HTTPS endpoint URL
- Configure notification settings (event types, trigger mode, branch filters)
- Click “Create”
Gaffer generates an HMAC signing secret automatically when you create a webhook. The signing secret is displayed once at creation — store it securely, as it cannot be retrieved later. You can regenerate the secret from the webhook settings if needed.
Configuration
Section titled “Configuration”Event Types
Section titled “Event Types”Choose which events trigger the webhook:
- Test runs — fires when a test run completes
- Coverage reports — fires when a coverage report is uploaded
At least one event type must be enabled.
Notification Triggers
Section titled “Notification Triggers”Control when test run webhooks fire:
- All test runs — fire on every completed test run
- Failures only — only fire when tests fail
- Consecutive failures — only fire after N consecutive failures (configurable, 1-10). Useful for filtering out one-off flakes
Coverage events always fire when enabled — trigger settings only apply to test runs.
Branch Filters
Section titled “Branch Filters”Restrict which branches trigger the webhook using glob patterns:
main— exact matchrelease/*— matchesrelease/v1.0,release/2024-01, etc.feature-*— matchesfeature-auth,feature-dashboard, etc.
Leave branch filters empty to receive events for all branches.
Project Scope
Section titled “Project Scope”Webhooks can be scoped to a specific project or apply to all projects in your organization. Organization-wide webhooks receive events from every project.
Payload Format
Section titled “Payload Format”Test Run Event
Section titled “Test Run Event”{ "event": "test_run", "timestamp": "2026-01-25T14:30:00.000Z", "delivery_id": "550e8400-e29b-41d4-a716-446655440000", "data": { "test_run_id": "tr_abc123", "project_id": "prj_def456", "project_name": "my-project", "organization_id": "org_ghi789", "branch": "main", "commit_sha": "a1b2c3d4e5f6", "report_format": "ctrf", "passed_count": 142, "failed_count": 3, "skipped_count": 2, "total_count": 147, "status": "failed", "url": "https://app.gaffer.sh/projects/prj_def456/test-runs/tr_abc123" }}Coverage Event
Section titled “Coverage Event”{ "event": "coverage", "timestamp": "2026-01-25T14:30:00.000Z", "delivery_id": "660e8400-e29b-41d4-a716-446655440000", "data": { "coverage_report_id": "cov_jkl012", "project_id": "prj_def456", "project_name": "my-project", "organization_id": "org_ghi789", "branch": "main", "commit_sha": "a1b2c3d4e5f6", "format": "lcov", "lines": { "covered": 1200, "total": 1500, "percent": 80.0 }, "branches": { "covered": 300, "total": 500, "percent": 60.0 }, "functions": { "covered": 95, "total": 120, "percent": 79.2 }, "url": "https://app.gaffer.sh/projects/prj_def456/coverage" }}Test Event
Section titled “Test Event”When you click “Test” in the dashboard, Gaffer sends a test payload:
{ "event": "test", "timestamp": "2026-01-25T14:30:00.000Z", "delivery_id": "770e8400-e29b-41d4-a716-446655440000", "test": true, "data": { "message": "This is a test webhook from Gaffer", "webhook_id": "whk_abc123", "webhook_name": "My Webhook", "organization_id": "org_ghi789" }}HTTP Headers
Section titled “HTTP Headers”Every webhook request includes these headers:
| Header | Description |
|---|---|
Content-Type | application/json |
X-Gaffer-Event | Event type: test_run, coverage, or test |
X-Gaffer-Signature | HMAC-SHA256 signature of the request body |
X-Gaffer-Delivery | Unique delivery ID (UUID) for idempotency |
User-Agent | Gaffer-Webhooks/1.0 |
Verifying Signatures
Section titled “Verifying Signatures”Every webhook is signed with your webhook’s secret using HMAC-SHA256. Always verify the signature before processing the payload to confirm it came from Gaffer.
Node.js
Section titled “Node.js”const crypto = require('crypto');
function verifySignature(payload, signature, secret) { const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) );}
// In your request handler:const payload = JSON.stringify(req.body);const signature = req.headers['x-gaffer-signature'];if (!verifySignature(payload, signature, process.env.GAFFER_WEBHOOK_SECRET)) { return res.status(401).send('Invalid signature');}Python
Section titled “Python”import hmacimport hashlib
def verify_signature(payload: bytes, signature: str, secret: str) -> bool: expected = 'sha256=' + hmac.new( secret.encode(), payload, hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected)
# In your request handler:payload = request.get_data()signature = request.headers.get('X-Gaffer-Signature')if not verify_signature(payload, signature, os.environ['GAFFER_WEBHOOK_SECRET']): abort(401)Retry Behavior
Section titled “Retry Behavior”Gaffer retries webhook deliveries when your endpoint returns a 5xx status code or a network error occurs. Deliveries that receive a 4xx response are not retried — these indicate a permanent issue with the request (e.g., your endpoint rejected it).
Each delivery has a unique X-Gaffer-Delivery ID. Use this for idempotency — if you receive the
same delivery ID twice, you can safely skip the duplicate.
Testing Webhooks
Section titled “Testing Webhooks”- Create your webhook in Settings > Webhooks
- Click the “Test” button next to your webhook
- Gaffer sends a test payload to your endpoint
- Check the result — the dashboard shows whether the delivery succeeded or failed, including the HTTP status code
For local development, use a tunnel service like ngrok to expose your local server to the internet.
Plan Limits
Section titled “Plan Limits”| Plan | Webhooks |
|---|---|
| Free | 1 |
| Pro | 5 |
| Team | Unlimited |
Troubleshooting
Section titled “Troubleshooting”Not receiving webhooks?
Section titled “Not receiving webhooks?”- Check the webhook is enabled — disabled webhooks don’t fire
- Verify branch filters — if configured, only matching branches trigger the webhook
- Check notification trigger — “failures only” won’t fire for passing test runs
- Check event types — make sure the relevant event type (test runs or coverage) is enabled
- Check “Last Sent” and error status — the webhooks list shows the last delivery time and any errors
Receiving a 401 from signature verification?
Section titled “Receiving a 401 from signature verification?”- Use the raw request body — verify against the exact bytes received, not a re-serialized version
- Check the secret — make sure you’re using the correct secret for this webhook
- Regenerate if needed — edit the webhook and check “Regenerate secret” to get a new one
Endpoint URL rejected?
Section titled “Endpoint URL rejected?”- HTTPS required — webhook URLs must use HTTPS
- No private IPs — localhost, 127.0.0.1, and private IP ranges (10.x, 172.16-31.x, 192.168.x) are blocked for security
Security
Section titled “Security”- HTTPS only — webhook URLs must use HTTPS to protect payloads in transit
- SSRF protection — private IP ranges and localhost are blocked
- Encrypted secrets — signing secrets are encrypted at rest using AES-256-GCM
- HMAC-SHA256 signing — every payload is signed so you can verify authenticity