Skip to content
Join Now Login

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.

  • 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
  1. Navigate to Settings > Webhooks in your Gaffer dashboard
  2. Click “Add Webhook”
  3. Enter a name and HTTPS endpoint URL
  4. Configure notification settings (event types, trigger mode, branch filters)
  5. 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.

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.

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.

Restrict which branches trigger the webhook using glob patterns:

  • main — exact match
  • release/* — matches release/v1.0, release/2024-01, etc.
  • feature-* — matches feature-auth, feature-dashboard, etc.

Leave branch filters empty to receive events for all branches.

Webhooks can be scoped to a specific project or apply to all projects in your organization. Organization-wide webhooks receive events from every project.

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
{
"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"
}
}

When you click “Test” in the dashboard, Gaffer sends a test payload:

test event
{
"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"
}
}

Every webhook request includes these headers:

HeaderDescription
Content-Typeapplication/json
X-Gaffer-EventEvent type: test_run, coverage, or test
X-Gaffer-SignatureHMAC-SHA256 signature of the request body
X-Gaffer-DeliveryUnique delivery ID (UUID) for idempotency
User-AgentGaffer-Webhooks/1.0

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.

verify-signature.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');
}
verify_signature.py
import hmac
import 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)

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.

  1. Create your webhook in Settings > Webhooks
  2. Click the “Test” button next to your webhook
  3. Gaffer sends a test payload to your endpoint
  4. 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.

PlanWebhooks
Free1
Pro5
TeamUnlimited
  • 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
  • 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
  • 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