Webhooks

TraceIQ Webhooks forward events in real time to external HTTP endpoints as JSON payloads. Manage endpoints, monitor deliveries, inspect failure logs, and test endpoints from the Webhooks page.

Request Headers

Every webhook POST request includes standard metadata headers and an HMAC-SHA256 signature:

  • X-TraceIQ-Event — The name of the event being delivered (e.g. order_completed, $identify).
  • X-TraceIQ-Delivery-ID — Unique UUID for this specific delivery attempt.
  • X-TraceIQ-Timestamp — Unix timestamp in seconds when the webhook was signed.
  • X-TraceIQ-Signature — HMAC-SHA256 signature in format "sha256=<hex_digest>" computed over `${timestamp}.${rawBody}`.
  • User-Agent — TraceIQ-Webhooks/2.0

Verifying Signatures

To prevent replay attacks and verify authenticity, always verify the signature against the raw request body and timestamp before processing.

Node.js (Express / Fastify):

ts
import crypto from 'crypto';

export function verifyWebhook(rawBody: string, signatureHeader: string, timestampHeader: string, secret: string) {
  const [scheme, signature] = signatureHeader.split('=');
  if (scheme !== 'sha256' || !signature) return false;

  // Prevent replay attacks (tolerance: 5 minutes)
  const timestamp = parseInt(timestampHeader, 10);
  if (isNaN(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > 300) {
    return false;
  }

  const payloadToSign = `${timestampHeader}.${rawBody}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payloadToSign)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expectedSignature, 'hex')
  );
}

Python (FastAPI / Flask):

python
import hmac
import hashlib
import time

def verify_webhook(raw_body: bytes, signature_header: str, timestamp_header: str, secret: str) -> bool:
    try:
        scheme, signature = signature_header.split('=', 1)
        if scheme != 'sha256':
            return False
        
        # Check timestamp freshness (5 minutes tolerance)
        ts = int(timestamp_header)
        if abs(time.time() - ts) > 300:
            return False
            
        payload = f"{timestamp_header}.".encode('utf-8') + raw_body
        expected = hmac.new(secret.encode('utf-8'), payload, hashlib.sha256).hexdigest()
        return hmac.compare_digest(signature, expected)
    except Exception:
        return False

Retry Policy

Deliveries timeout after 10 seconds. On transient failures (HTTP 408, 425, 429, 500, 502, 503, 504, or connection timeouts), TraceIQ automatically retries up to 5 times with exponential backoff:

  • Attempt 1 → 2 — ~60 seconds
  • Attempt 2 → 3 — ~5 minutes
  • Attempt 3 → 4 — ~30 minutes
  • Attempt 4 → 5 — ~2 hours

Client errors (400, 401, 403, 404, 422) are treated as permanent failures and will not be retried automatically. You can always manually retry any delivery from the webhook detail drawer.