Back to Blog
TutorialJanuary 4, 20267 min read

Integrating Security Alerts Into Your Engineering Workflow via Webhooks

Step-by-step tutorial for routing PentestCheck security alerts to Slack, Discord, Telegram, and custom endpoints — with payload examples, severity filtering, and a GitHub Actions integration pattern.

Security findings that don't reach engineers in time aren't security findings — they're future incidents. This tutorial shows you how to wire PentestCheck's alert system directly into the tools your team already uses.

Webhook Architecture Overview

PentestCheck fires webhooks on three trigger types:

TriggerDescription
finding.createdNew vulnerability found during scan
scan.completedFull scan finished with summary
asset.discoveredNew internet-facing asset found by EASM
asset.changedExisting asset changed (new port, cert expiry, etc.)
score.thresholdThreat Score crossed a configured threshold

Each webhook delivers a JSON payload to your configured endpoint. You can configure multiple endpoints with different severity filters.

Payload Format

{
  "event": "finding.created",
  "timestamp": "2026-04-09T14:23:11Z",
  "organization": "acme-corp",
  "payload": {
    "id": "find_7g3kx9",
    "severity": "CRITICAL",
    "cvss": 9.1,
    "category": "injection",
    "owasp": "A03",
    "title": "SQL Injection — /api/v1/search (parameter: q)",
    "asset": {
      "hostname": "app.acmecorp.com",
      "ip": "104.18.22.45",
      "port": 443
    },
    "remediation": "Use parameterized queries. Do not interpolate user input directly into SQL strings.",
    "evidence_url": "https://app.pentestcheck.com/findings/find_7g3kx9",
    "scan_id": "scan_p4r7k1"
  }
}

Step 1: Configure Your Endpoint

In PentestCheck dashboard → Settings → Webhooks → Add Endpoint.

Configure:

Step 2: Slack Integration

Option A: Direct Slack Incoming Webhook (simplest)

Create a Slack Incoming Webhook in your workspace and set it as the PentestCheck endpoint URL directly. Slack accepts raw JSON but ignores fields it doesn't understand.

For a formatted Slack message, set up a lightweight transform function (e.g., Cloudflare Worker, AWS Lambda, Vercel Edge Function):

// transform.js — Cloudflare Worker
export default {
  async fetch(request) {
    const payload = await request.json();
    
    // Verify HMAC signature
    const signature = request.headers.get('X-PentestCheck-Signature');
    if (!verifySignature(await request.text(), signature, SECRET)) {
      return new Response('Unauthorized', { status: 401 });
    }
    
    const { payload: finding } = payload;
    const severityEmoji = {
      CRITICAL: '🔴', HIGH: '🟠', MEDIUM: '🟡', LOW: '🟢', INFO: '⚪'
    }[finding.severity] || '⚪';
    
    const slackPayload = {
      text: `${severityEmoji} *${finding.severity}* finding on ${finding.asset.hostname}`,
      blocks: [
        {
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: `${severityEmoji} *${finding.title}*\n*Asset:* ${finding.asset.hostname}\n*CVSS:* ${finding.cvss} | *OWASP:* ${finding.owasp}`
          }
        },
        {
          type: 'actions',
          elements: [{
            type: 'button',
            text: { type: 'plain_text', text: 'View Finding' },
            url: finding.evidence_url,
            style: finding.severity === 'CRITICAL' ? 'danger' : 'primary'
          }]
        }
      ]
    };
    
    await fetch(SLACK_WEBHOOK_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(slackPayload)
    });
    
    return new Response('OK');
  }
};

Step 3: Verify Webhook Signatures

Every PentestCheck webhook includes an X-PentestCheck-Signature header for payload verification:

X-PentestCheck-Signature: sha256=a8f3b2c...

Verify it before processing:

import hmac, hashlib

def verify_signature(payload_bytes: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode('utf-8'),
        payload_bytes,
        hashlib.sha256
    ).hexdigest()
    received = signature.removeprefix('sha256=')
    return hmac.compare_digest(expected, received)

Never process webhook payloads without verifying the signature. An attacker who knows your endpoint URL could inject false findings.

Step 4: GitHub Actions Integration

For CI/CD blocking — fail the pipeline if a critical finding is discovered:

# .github/workflows/security-scan.yml
name: Security Scan

on:
  push:
    branches: [main, staging]
  pull_request:

jobs:
  pentest-scan:
    runs-on: ubuntu-latest
    steps:
      - name: Trigger PentestCheck Scan
        id: scan
        run: |
          SCAN_ID=$(curl -s -X POST \
            -H "Authorization: Bearer ${{ secrets.PENTESTCHECK_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{"target": "${{ vars.STAGING_URL }}", "mode": "deep"}' \
            https://api.pentestcheck.com/v1/scans | jq -r '.scan_id')
          echo "scan_id=$SCAN_ID" >> $GITHUB_OUTPUT

      - name: Wait for Scan Completion
        run: |
          while true; do
            STATUS=$(curl -s \
              -H "Authorization: Bearer ${{ secrets.PENTESTCHECK_API_KEY }}" \
              https://api.pentestcheck.com/v1/scans/${{ steps.scan.outputs.scan_id }} \
              | jq -r '.status')
            [ "$STATUS" = "completed" ] && break
            sleep 30
          done

      - name: Check Results
        run: |
          CRITICAL=$(curl -s \
            -H "Authorization: Bearer ${{ secrets.PENTESTCHECK_API_KEY }}" \
            "https://api.pentestcheck.com/v1/scans/${{ steps.scan.outputs.scan_id }}/findings?severity=CRITICAL,HIGH" \
            | jq '.count')
          if [ "$CRITICAL" -gt "0" ]; then
            echo "::error::$CRITICAL critical/high security findings. Blocking deployment."
            exit 1
          fi

Step 5: Severity-Based Routing

Different severities should reach different teams:

SeverityDestinationResponse Time
CRITICAL#security-critical + PagerDutyImmediate
HIGH#security-alerts + Jira auto-ticket< 1 hour
MEDIUM#security-weekly digestNext sprint
LOWJira backlogNext quarter

Configure separate webhook endpoints with different severity filters for each destination.

Avoiding Alert Fatigue

The most common webhook implementation failure: routing every finding to the same channel at the same priority. Within a week, the channel is muted.

Rules for a sustainable alert configuration:

  1. CRITICAL/HIGH go to a channel that your team actually monitors and treats as urgent
  2. MEDIUM findings go to a weekly digest, not individual alerts
  3. asset.discovered events go to a separate channel (infrastructure awareness, not security response)
  4. Set up a 30-day review of suppressed findings to prevent systematic blind spots

A security alert system that engineers ignore provides less value than no system at all.


PentestCheck's webhook system supports Slack, Discord, Telegram, Microsoft Teams, and custom HTTPS endpoints. Configure alerts in under 2 minutes from your dashboard.

Scan your attack surface today

Free plan includes 3 targets and 10 scans/month. No credit card required.

Start Free Scan