Back to KB
Difficulty
Intermediate
Read Time
9 min

Set Up Webhook Tech Stack Changes Alerts via API

By Codcompass Team··9 min read

Event-Driven Infrastructure Monitoring: Building a Reliable Tech Stack Alert System

Current Situation Analysis

Engineering teams, security analysts, and competitive intelligence units frequently need to track technology shifts across external domains. Whether it's a rival adopting a new frontend framework, a client removing critical security headers, or a prospect integrating a payment processor, these signals carry operational value. The traditional approach to capturing this data relies on scheduled polling: cron jobs or CI/CD pipelines that periodically hit an API, fetch a snapshot, and diff it against a local cache.

This pull-based model is fundamentally inefficient. It forces developers to burn API quota on domains that haven't changed, while simultaneously creating blind spots between scan intervals. A competitor deploying a new analytics stack at 2:15 PM won't be detected until the next scheduled run, which could be 6, 12, or 24 hours later. The operational overhead compounds quickly: you must manage retry logic, handle rate limits, store historical snapshots, and write diff algorithms. Most teams underestimate the maintenance burden until their polling infrastructure starts failing silently or exhausting API limits.

The industry has largely overlooked this inefficiency because polling feels familiar. Developers default to timers because they're easy to conceptualize, even though event-driven architectures are the standard for modern observability. Data from infrastructure monitoring practices shows that over 90% of periodic scans return identical payloads. You're paying for compute and API calls to confirm status quo. Shifting to a push-based webhook model flips the architecture: the scanning provider handles the heavy lifting, maintains the schedule, and only triggers a network request when a material change occurs or a full analysis completes. This eliminates wasted requests, guarantees sub-second delivery, and reduces your operational surface area to a single verified endpoint.

WOW Moment: Key Findings

The architectural shift from polling to event-driven webhooks isn't just a convenience upgrade; it fundamentally changes how you allocate engineering time and API budget. The following comparison demonstrates the operational divergence between the two approaches when tracking a portfolio of 20 domains.

ApproachAPI Request Volume (Daily)Detection LatencyOperational OverheadCost Efficiency
Scheduled Polling80-120 requests4-24 hoursHigh (cron management, diff logic, error handling)Low initial, High long-term
Webhook-Driven~0 baseline, spikes on change<1 secondLow (event routing, signature verification)High initial setup, Low long-term

This finding matters because it decouples monitoring frequency from infrastructure cost. With polling, doubling your tracking list doubles your API consumption and compute load. With webhooks, you pay for the scanning service tier, but your own infrastructure only processes events when they actually occur. This enables real-time competitive intelligence, continuous security compliance tracking, and automated sales signal routing without burning through rate limits or maintaining complex state machines.

Core Solution

Building a production-ready webhook receiver requires three distinct phases: provisioning the subscription, activating scheduled scanning, and implementing a verified event pipeline. The following implementation uses TypeScript with Node.js and Express, prioritizing security, idempotency, and clean separation of concerns.

Step 1: Provision the Webhook Subscription

You begin by registering a domain and callback URL with the DetectZeStack API. The endpoint returns a unique subscription ID and a one-time HMAC secret. This secret is critical; it's used to cryptographically verify that incoming payloads originate from the scanning service.

import axios from 'axios';

interface WebhookConfig {
  domain: string;
  callbackUrl: string;
  apiKey: string;
}

async function registerSubscription(config: WebhookConfig) {
  const response = await axios.post(
    'https://detectzestack.p.rapidapi.com/webhooks',
    {
      domain: config.domain,
      webhook_url: config.callbackUrl
    },
    {
      headers: {
        'x-rapidapi-key': config.apiKey,
        'x-rapidapi-host': 'detectzestack.p.rapidapi.com',
        'Content-Type': 'application/json'
      }
    }
  );

  return response.data as {
    id: number;
    domain: string;
    webhook_url: string;
    hmac_secret: string;
  };
}

Architecture Decision: We isolate the registration logic into a dedicated async function rather than embedding it in route handlers. This allows you to run provisioning as a one-time setup script or CI step, keeping runtime code clean. The hmac_secret must be persisted in a secure vault or environment configuration immediately upon receipt, as the API never returns it again.

Step 2: Activate Scheduled Monitoring

Registration alone doesn't trigger automatic scans. You must explicitly set the monitoring interval via a PATCH request. The API supports daily or weekly cycles. Choosing the right interval depends on your use case: daily for security compliance and active competitive tracking, weekly for long-term trend analysis.

async function activateMonitoring(subscriptionId: number, interval: 'daily' | 'weekly', apiKey: string) {
  await axios.patch(
    `https://detectzestack.p.rapidapi.com/webhooks/${subscriptionId}`,
    { monitor_interval: interval },
    {
      headers: {
        'x-rapidapi-key': apiKey,
        'x-rapidapi-host': 'detectzestack.p.rapidapi.com',
        'Content-Type': 'application/json'
      }
    }
  );
}

Step 3: Build the Verified Event Pipeline

The receiver endpoint must validate the HMAC signature before processing any payload. Parsing JSON prematurely will alter the raw byte sequence, causing signature verification to fail. The implementation below uses Express middleware to handle verification, then routes the event to a processor.

import express, { Request, Response, NextFunction } from 'express';
import crypto from 'crypto';

const app = express();
app.use(express.raw({ type: 'application/json' }));

function verifyHmacMiddleware(req: Request, res: Response, next: NextFunction) {
  const signatureHeader = req.headers['x-web

hook-signature'] as string; const secret = process.env.WEBHOOK_HMAC_SECRET;

if (!signatureHeader || !secret) { return res.status(401).json({ error: 'Missing authentication headers' }); }

const expectedPrefix = 'sha256='; if (!signatureHeader.startsWith(expectedPrefix)) { return res.status(401).json({ error: 'Invalid signature format' }); }

const providedSignature = signatureHeader.slice(expectedPrefix.length); const computedSignature = crypto .createHmac('sha256', secret) .update(req.body) .digest('hex');

if (!crypto.timingSafeEqual( Buffer.from(providedSignature, 'hex'), Buffer.from(computedSignature, 'hex') )) { return res.status(403).json({ error: 'Signature mismatch' }); }

next(); }

app.post('/api/techstack-events', verifyHmacMiddleware, async (req: Request, res: Response) => { res.status(200).send('OK');

const payload = JSON.parse(req.body.toString()); await processTechStackEvent(payload); });


**Architecture Decision:** We use `express.raw()` instead of `express.json()` to preserve the exact byte sequence for HMAC verification. The `crypto.timingSafeEqual` method prevents timing attacks. Responding with `200 OK` immediately before processing ensures the scanning service marks the delivery as successful, preventing unnecessary retries.

### Step 4: Enrich Events with the Change Feed

Webhook payloads contain the current state and metadata, but structured diffs require a separate API call. The `/changes` endpoint returns added, removed, or version-shifted technologies. We fetch this context asynchronously after acknowledging the webhook.

```typescript
async function processTechStackEvent(payload: any) {
  const { domain, event } = payload;
  
  if (event === 'tech_stack.analyzed') {
    const changes = await fetchDomainChanges(domain);
    await routeAlertToSlack(domain, changes);
  }
}

async function fetchDomainChanges(domain: string) {
  const response = await axios.get(
    `https://detectzestack.p.rapidapi.com/changes`,
    {
      params: { domain },
      headers: {
        'x-rapidapi-key': process.env.RAPIDAPI_KEY!,
        'x-rapidapi-host': 'detectzestack.p.rapidapi.com'
      }
    }
  );
  return response.data;
}

Pitfall Guide

Building webhook pipelines seems straightforward until production conditions expose edge cases. The following mistakes are common in early implementations and their production-grade fixes.

1. Parsing JSON Before HMAC Verification

Explanation: Express's json() middleware parses the request body into an object, which alters whitespace, key ordering, and encoding. The HMAC is computed against the raw byte stream, so verification will always fail if you parse first. Fix: Use express.raw() or express.text() for the webhook route, verify the signature against the raw buffer, then parse JSON only after validation passes.

2. Ignoring Idempotency Requirements

Explanation: The scanning service retries failed deliveries up to three times with exponential backoff (2 seconds, then 4 seconds). If your endpoint crashes mid-processing or returns a 5xx status, you'll receive duplicate payloads. Processing them twice can trigger duplicate Slack alerts or database writes. Fix: Implement an idempotency layer using the event timestamp, domain, and a hash of the payload. Store processed event IDs in Redis or a database with a short TTL, and skip processing if the key already exists.

3. Hardcoding or Committing HMAC Secrets

Explanation: Developers often paste the hmac_secret directly into source code or .env files committed to version control. This exposes cryptographic verification keys to anyone with repository access. Fix: Inject secrets via a dedicated vault (AWS Secrets Manager, HashiCorp Vault, or CI/CD secret stores). Load them at runtime and never log or expose them in error responses.

4. Confusing tech_stack.analyzed and tech_stack.changed Events

Explanation: Every scan fires tech_stack.analyzed with the full technology list. Only material shifts trigger tech_stack.changed. Treating both identically causes noise and redundant processing. Fix: Route analyzed events to logging or baseline storage. Reserve changed events for alerting pipelines. If you need diffs, always query the /changes endpoint regardless of event type.

5. Using Private or Localhost Callback URLs

Explanation: The API explicitly rejects HTTP endpoints and private IP ranges (127.0.0.1, 10.x.x.x, 192.168.x.x). Webhooks require publicly routable HTTPS URLs to prevent credential leakage and ensure delivery. Fix: Deploy the receiver behind a public load balancer or use a tunneling service like ngrok or Cloudflare Tunnel during development. Ensure TLS termination is properly configured.

6. Blocking the Response Thread with Heavy Processing

Explanation: If your route handler performs database writes, API calls, or Slack notifications synchronously, the response takes longer than the scanning service's timeout window. This triggers retries and degrades delivery reliability. Fix: Acknowledge the webhook immediately with 200 OK, then offload processing to a message queue (RabbitMQ, SQS, BullMQ) or an async worker pool.

7. Over-Provisioning Monitoring Intervals

Explanation: Setting every domain to daily monitoring when only a few require real-time tracking wastes API quota and increases noise. The pricing tiers cap subscriptions (Basic: 5, Pro: 20, Ultra: 50, Mega: 100), so inefficient allocation hits limits faster. Fix: Classify domains by sensitivity. Use daily for security-critical assets and active competitors. Use weekly for long-term trend tracking. Disable automatic scanning for low-priority domains and trigger manual scans via API when needed.

Production Bundle

Action Checklist

  • Provision API key and select appropriate tier (Pro $9/mo for 20 subs, Ultra $29/mo for 50, Mega $79/mo for 100)
  • Register target domains via POST /webhooks and securely store the returned hmac_secret
  • Deploy a publicly accessible HTTPS endpoint with TLS termination
  • Implement HMAC-SHA256 verification middleware using raw request bodies
  • Configure monitoring intervals (daily or weekly) via PATCH /webhooks/{id}
  • Add idempotency checks to prevent duplicate processing on retries
  • Integrate /changes endpoint queries for structured diff context
  • Route verified events to alerting channels (Slack, PagerDuty, email) via async workers

Decision Matrix

ScenarioRecommended ApproachWhyCost Impact
Security compliance trackingDaily monitoring + immediate Slack alertsVulnerabilities like dropped CSP headers require <24h responseHigher API usage, justified by risk reduction
Competitive intelligenceDaily monitoring + change feed enrichmentFramework shifts and CDN migrations signal strategic movesModerate cost, high strategic value
Long-term trend analysisWeekly monitoring + database loggingInfrastructure evolution rarely requires daily granularityLower API consumption, minimal alert noise
Budget-constrained trackingManual triggers + Basic tier ($0/mo)Free tier supports 5 subscriptions but lacks auto-scanningZero recurring cost, requires manual intervention
High-volume portfolioMega tier ($79/mo) + async event queue100 subscription limit accommodates large tracking listsHigher tier cost, scales efficiently with queue architecture

Configuration Template

// webhook-server.ts
import express from 'express';
import crypto from 'crypto';
import { processEvent } from './event-processor';

const app = express();
const PORT = process.env.PORT || 3000;

// Preserve raw body for HMAC verification
app.use(express.raw({ type: 'application/json', limit: '1mb' }));

app.post('/hooks/techstack', async (req, res) => {
  const signature = req.headers['x-webhook-signature'] as string;
  const secret = process.env.WEBHOOK_HMAC_SECRET;

  if (!signature?.startsWith('sha256=') || !secret) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  const provided = signature.slice(7);
  const computed = crypto.createHmac('sha256', secret).update(req.body).digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(provided, 'hex'), Buffer.from(computed, 'hex'))) {
    return res.status(403).json({ error: 'Invalid signature' });
  }

  // Acknowledge immediately to prevent retries
  res.status(200).send('Accepted');

  // Process asynchronously
  const payload = JSON.parse(req.body.toString());
  await processEvent(payload);
});

app.listen(PORT, () => {
  console.log(`Webhook receiver listening on port ${PORT}`);
});

Quick Start Guide

  1. Create an account on DetectZeStack and generate an API key. Select a tier that matches your domain count (Pro at $9/mo covers 20 domains with daily/weekly scanning).
  2. Register your first domain by sending a POST request to /webhooks with your target domain and a publicly routable HTTPS callback URL. Save the returned hmac_secret in your environment variables.
  3. Deploy the receiver using the configuration template above. Ensure your server is accessible over HTTPS and responds to POST requests at the registered path.
  4. Activate monitoring by patching the subscription with {"monitor_interval": "daily"} or {"monitor_interval": "weekly"}. The service will begin scanning according to your schedule.
  5. Test the pipeline by manually triggering a scan via the API or waiting for the first scheduled run. Verify that your endpoint receives the payload, passes HMAC validation, and routes the event to your alerting system.