GenlyticDocs
API Reference/Webhooks
genlytic.app ↗
Get started

Webhooks

Receive real-time events from Genlytic via signed HTTP POST.

Genlytic delivers webhook events as signed HTTP POST requests. Configure endpoints in Settings → API → Webhooks.

Available events

Event typeFired when
scan.completedA scan batch finishes processing
visibility.dropVisibility score drops 5+ points vs previous scan
visibility.spikeVisibility score rises 5+ points vs previous scan
citation.lostA previously cited domain is no longer cited
citation.gainedA new domain is cited for the first time
action.generatedNew optimize actions are created after a scan
agent.action.completedA GEO Agent coordinator cycle dispatches tasks
agent.run.completedA GEO Agent coordinator run finishes
shadow_competitor.detectedA shadow competitor crosses the detection threshold

Payload structure

All webhook payloads use this envelope:

{
  "id": "evt_a1b2c3d4e5f6...",
  "type": "scan.completed",
  "created": 1735689600,
  "data": {}
}

The data field is event-specific.

Signature verification

Every delivery includes three headers:

HeaderValue
X-Genlytic-EventEvent type string
X-Genlytic-TimestampUnix timestamp (seconds)
X-Genlytic-Signaturesha256=<hmac_hex>

The HMAC-SHA256 signature is computed over ${timestamp}.${rawBody} using your webhook secret (the whsec_xxx value shown when you created the endpoint) as the key directly.

import crypto from 'crypto';

function verifyWebhook(
  rawBody: string,
  headers: Record<string, string>,
  webhookSecret: string,   // the whsec_xxx value from Settings → API → Webhooks
): boolean {
  const timestamp = headers['x-genlytic-timestamp'];
  const received  = headers['x-genlytic-signature']?.replace('sha256=', '');
  if (!timestamp || !received) return false;

  // Reject stale events (replay attack prevention)
  const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10);
  if (age > 300) return false; // 5 minutes

  const expected = crypto
    .createHmac('sha256', Buffer.from(webhookSecret, 'utf8'))
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  // Constant-time comparison
  const a = Buffer.from(expected, 'hex');
  const b = Buffer.from(received.padEnd(expected.length, '0'), 'hex');
  return a.byteLength === b.byteLength && crypto.timingSafeEqual(a, b);
}

Reject requests where the timestamp is more than 5 minutes old. The example above includes this check.

Always verify the signature before processing the payload. Never trust unverified webhook bodies.

Retry policy

Genlytic makes one delivery attempt with a 10-second timeout. Failed deliveries (non-2xx or timeout) are not retried. Return 2xx immediately and process the event asynchronously.

Testing

Use the Send test event button in Settings → API → Webhooks to fire a test payload to your endpoint. Test events are signed identically to live events.

Next steps

← Previous
Rate Limits
Next →
Endpoints Overview
Was this page helpful?Report an issue →