Skip to main content

Webhooks

Limelight can push events to your server as they happen. Each event is sent as an HTTP POST with an HMAC-signed JSON body so you can verify that the payload came from Limelight and wasn't modified in transit.

This page covers the concepts that apply to every webhook (envelope, signing, retries). For the events you can subscribe to, see the per-product pages:

  • Lead events — lead2.created, activity.detected
  • Signals — coming soon
  • Inbox — coming soon

Setting up a webhook​

  1. Go to Settings > API > Lead webhook.
  2. Click Add webhook.
  3. Enter an https:// URL and pick which events you want to subscribe to.
  4. Copy the signing secret that appears after creation — it is only shown once. Store it wherever you store other secrets (env variable, secret manager).

Envelope format​

All payloads share a versioned envelope:

{
"id": "wh_...",
"event": "lead2.created",
"version": "1",
"createdAt": "2026-04-16T10:22:00.000Z",
"brandId": "brand_abc",
"data": { ... }
}

The shape of data depends on the event — see each event's page for examples.

Headers​

Every delivery includes these headers:

HeaderValue
X-Limelight-EventEvent type (lead2.created, activity.detected, …)
X-Limelight-Delivery-IdUnique ID for this delivery (wh_...). Use it as an idempotency key on your side.
X-Limelight-TimestampUnix seconds at which the delivery was signed.
X-Limelight-Signaturesha256=<hex> HMAC of <timestamp>.<raw body> using your signing secret.
User-AgentLimelight-Webhook/1.0
Content-Typeapplication/json

Verifying the signature​

The signed content is timestamp + "." + raw_body. You must verify against the exact raw body — do not re-serialize.

Node (TypeScript)​

import crypto from 'node:crypto';

function verifyWebhook(
rawBody: string,
headers: Record<string, string | undefined>,
secret: string,
): boolean {
const timestamp = headers['x-limelight-timestamp'];
const signature = headers['x-limelight-signature'];
if (!timestamp || !signature) return false;

const expected =
'sha256=' +
crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');

if (expected.length !== signature.length) return false;
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature),
);
}

Python​

import hmac
import hashlib

def verify_webhook(raw_body: bytes, headers: dict, secret: str) -> bool:
timestamp = headers.get('X-Limelight-Timestamp')
signature = headers.get('X-Limelight-Signature')
if not timestamp or not signature:
return False

signed = f"{timestamp}.".encode() + raw_body
expected = 'sha256=' + hmac.new(
secret.encode(), signed, hashlib.sha256
).hexdigest()

return hmac.compare_digest(expected, signature)

Retries​

We retry failed deliveries using Google Cloud Tasks with exponential backoff.

  • Timeout: 10 seconds per attempt.
  • Retry: any non-2xx response or network error triggers a retry.
  • Stop condition: Cloud Tasks enforces a maximum of 5 attempts over roughly 1 hour.
  • Idempotency: Cloud Tasks may retry after your server returned 2xx. Use X-Limelight-Delivery-Id to dedupe on your side.

Auto-pause​

If a webhook has 20 consecutive failed deliveries with no successes in between, we automatically flip it to paused. You'll see an "Auto-paused" badge in Settings → API. Fix your endpoint, resume the webhook, and the counter resets.

Signing secret rotation​

Click Rotate secret in the webhook's ⋯ menu. The old secret stops working immediately, so update your receiver first (or accept both old and new during a short window).

Manual resend​

From the relevant feed, open any item's ⋯ menu and choose Send to webhook to re-push the event to a webhook of your choice. The delivery is marked trigger: manual in the Deliveries drawer.

  • You can only resend events that still exist in the event store. Events are retained for 30 days.
  • Manual resends are rate-limited to 10 per user per minute.
  • Manual failures do not affect the auto-pause counter.

Security notes​

  • Your webhook URL must use https and cannot resolve to a private IP range (RFC-1918, loopback, link-local). We do not follow HTTP redirects on delivery.
  • Treat your signing secret the same way you treat API keys: never commit it, never log it, rotate it if exposed.
  • Raw scraped data is never sent in webhook payloads. We extract only a small, stable subset for each event.