AIXBT Docs

Webhooks

Receive signed alert deliveries on your own HTTPS endpoint

A webhook destination receives alerts and report notifications as alert.v3 JSON:

  • intelligence.material when material intel matches an alert's filters.
  • rank.entered when a project or topic enters your selected ranking.
  • report.published when a subscribed new topic report is published. Its compact event.report object contains the report id and the account-authorized REST API URL; fetch that URL with your API key for the full report.

Set up

  1. Open Integrations and add a webhook with a public HTTPS endpoint.
  2. Registration sends a destination.test request. Return the challenge value from its JSON body as the plain-text response body. To try the flow before your receiver exists, register https://docs.aixbt.tech/api/webhook-echo first.
  3. Store the signing secret when it appears. It is shown once.
  4. Select the webhook on the alerts or reports you want delivered.

Verify every request

Read the AIXBT-Webhook-Timestamp and AIXBT-Webhook-Signature headers and check them against the exact raw request body with your stored secret. Do not parse and re-serialize the body before verifying:

import { createHmac, timingSafeEqual } from 'node:crypto'

export function verifyAixbtWebhook(
  rawBody: Buffer,
  timestamp: string, // AIXBT-Webhook-Timestamp header
  signature: string, // AIXBT-Webhook-Signature header
  secret: string, // shown once in Integrations
): boolean {
  const sentAt = Number(timestamp)
  if (!Number.isSafeInteger(sentAt) || sentAt < 1) return false
  if (Math.abs(Date.now() / 1000 - sentAt) > 300) return false

  const expected = createHmac('sha256', secret)
    .update(`${timestamp}.`)
    .update(rawBody)
    .digest('hex')
  return signature.split(',').some(part => {
    const digest = part.trim().split('=')[1] ?? ''
    return (
      /^[a-f0-9]{64}$/.test(digest) &&
      timingSafeEqual(Buffer.from(digest, 'hex'), Buffer.from(expected, 'hex'))
    )
  })
}

The signature header carries one entry, or two separated by a comma for 24 hours after you rotate the secret, so your stored secret keeps verifying straight through a rotation. The AIXBT-Webhook-Event, AIXBT-Webhook-Event-ID, and AIXBT-Webhook-Delivery-ID headers tell you what arrived without parsing the body.

Respond

Return 2xx after accepting a delivery. 408, 429, and 5xx responses are retried, honoring a bounded Retry-After; other 4xx responses are not. A 410 disables the destination, and repeated 401 or 404 responses pause it until a test succeeds.

Process deliveries

Delivery is at-least-once and can arrive late or out of order. Deduplicate by the delivery.id body field; an intentional replay repeats it with a higher delivery.replayCount. Acknowledge an unknown event type with a 2xx so adding support later never causes a retry loop.

On this page