Skip to main content

Webhooks & Event Destinations

Push agent events to external systems automatically. The webhook system listens to the agent’s EventBus and forwards events to configured destinations with retry and batching.

Quick Start

import { Agent, openai, httpWebhook, slackWebhook } from "@radaros/core";

const agent = new Agent({
  name: "assistant",
  model: openai("gpt-4o"),
  webhooks: {
    destinations: [
      httpWebhook({
        url: "https://your-api.com/webhooks",
        secret: "my-hmac-secret",
      }),
      slackWebhook({
        webhookUrl: "https://hooks.slack.com/services/T.../B.../...",
      }),
    ],
    events: ["run.complete", "run.error"],
    retries: 3,
  },
});

Destinations

HTTP

httpWebhook({
  url: "https://api.example.com/events",
  headers: { "X-API-Key": "..." },
  method: "POST",        // or "PUT"
  secret: "hmac-secret", // Signs payload with HMAC-SHA256
});
The payload is JSON: { event, payload, timestamp }. With a secret, the X-Webhook-Signature header contains sha256=<hex>.

Slack

slackWebhook({
  webhookUrl: "https://hooks.slack.com/services/...",
  channel: "#alerts",
  formatMessage: (event, payload) => `Custom: ${event}`,
});
Default format:
[RadarOS] run.complete
Agent: assistant
Run: abc12345
Tokens: 450 prompt + 128 completion
Duration: 1.2s

Email (SendGrid)

emailWebhook({
  transport: "sendgrid",
  apiKey: process.env.SENDGRID_API_KEY!,
  to: ["team@example.com"],
  from: "alerts@example.com",
  subject: (event) => `[RadarOS] ${event}`,
});

Custom Destination

Build your own destination by implementing the WebhookDestination interface:
import type { WebhookDestination } from "@radaros/core";

const datadogDestination: WebhookDestination = {
  name: "datadog",
  async send(event: string, payload: Record<string, unknown>) {
    await fetch("https://http-intake.logs.datadoghq.com/api/v2/logs", {
      method: "POST",
      headers: {
        "DD-API-KEY": process.env.DD_API_KEY!,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        ddsource: "radaros",
        ddtags: `event:${event},agent:${payload.agentName}`,
        message: JSON.stringify(payload),
      }),
    });
  },
};

const agent = new Agent({
  name: "assistant",
  model: openai("gpt-4o"),
  webhooks: {
    destinations: [datadogDestination],
    events: ["run.complete", "run.error", "cost.tracked"],
  },
});

Configuration

interface WebhookConfig {
  destinations: WebhookDestination[];
  events?: string[];         // Filter events (default: all)
  batchInterval?: number;    // Batch events before sending (ms)
  retries?: number;          // Retry on failure (default: 2)
  onError?: "log" | "throw"; // Error handling (default: "log")
}

Supported Events

All events from the AgentEventMap are supported, including: run.start, run.complete, run.error, tool.call, tool.result, team.delegate, handoff.transfer, handoff.complete, cost.tracked, cache.hit, cache.miss, memory.stored, skill.loaded, and more.

Event Filtering

Only forward specific events to specific destinations:
const agent = new Agent({
  name: "assistant",
  model: openai("gpt-4o"),
  webhooks: {
    destinations: [
      httpWebhook({ url: "https://api.example.com/all-events" }),
      slackWebhook({
        webhookUrl: "https://hooks.slack.com/...",
        channel: "#alerts",
      }),
    ],
    events: [
      "run.complete",
      "run.error",
      "cost.budget.exceeded",
      "handoff.transfer",
    ],
    retries: 3,
    onError: "log",
  },
});
Only the listed events are forwarded. Omit events to forward everything.

Batching

For high-throughput agents, batch events before sending to reduce HTTP calls:
webhooks: {
  destinations: [httpWebhook({ url: "https://api.example.com/events" })],
  batchInterval: 5000, // Batch events for 5 seconds before sending
}
Batched events are sent as an array in a single HTTP request. The batch flushes either when batchInterval elapses or when 100 events accumulate (whichever comes first).

HMAC Signature Verification

When using httpWebhook with a secret, every request includes a X-Webhook-Signature header. Verify it on your server:
import crypto from "node:crypto";

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string,
): boolean {
  const expected = "sha256=" +
    crypto.createHmac("sha256", secret).update(payload).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

// In your Express handler:
app.post("/webhooks", (req, res) => {
  const sig = req.headers["x-webhook-signature"] as string;
  if (!verifyWebhookSignature(JSON.stringify(req.body), sig, "my-hmac-secret")) {
    return res.status(401).send("Invalid signature");
  }
  // Process the event
  console.log(req.body.event, req.body.payload);
  res.sendStatus(200);
});