tutorial

Monitoring AWS EventBridge with Vigilmon: Event Bus Health, Rule Execution Heartbeats & Dead-Letter Alerting

Practical guide to uptime monitoring for AWS EventBridge — health probes, rule execution heartbeats, dead-letter queue monitoring, and alert routing with Vigilmon.

AWS EventBridge is the backbone of serverless event-driven architectures — routing events from dozens of sources to dozens of targets. But when an EventBridge rule silently stops matching events, or a target Lambda starts throwing errors and events pile up in a dead-letter queue, your application degrades invisibly. CloudWatch has EventBridge metrics, but they require careful alarm configuration and live inside the same account that might be having the problem. Vigilmon gives you an external, independent signal that catches EventBridge failures from the outside.

This tutorial shows you how to build a monitoring strategy for EventBridge-based applications.

What You'll Build

  • A Lambda target function that pings Vigilmon after processing events
  • A scheduled EventBridge rule used as a heartbeat probe
  • Dead-letter queue monitoring via a Lambda poller
  • Alert channels for sustained failures

Prerequisites

  • AWS account with at least one EventBridge rule in use
  • AWS CLI or CDK configured locally
  • A free account at vigilmon.online

Step 1: Instrument Your Event Processor with Heartbeats

The most reliable way to know your EventBridge pipeline is working is to verify that events actually flow end-to-end. Add a Vigilmon heartbeat ping inside each critical event processor.

Lambda event handler with heartbeat

// handlers/order-processor.mjs
export async function handler(event) {
  const records = event.detail ? [event] : event.Records ?? [];

  for (const record of records) {
    try {
      await processOrder(record);
    } catch (err) {
      console.error("Order processing failed:", err);
      throw err; // EventBridge routes to DLQ on failure
    }
  }

  // Ping heartbeat only when all records processed successfully
  if (process.env.VIGILMON_HEARTBEAT_URL) {
    try {
      await fetch(process.env.VIGILMON_HEARTBEAT_URL, { method: "POST" });
    } catch (err) {
      // Log but don't fail — heartbeat ping failure shouldn't kill the event processor
      console.warn("Heartbeat ping failed:", err.message);
    }
  }
}

async function processOrder(event) {
  // Your business logic here
}

In Vigilmon, create a Heartbeat monitor and set the grace period to match your expected event frequency plus a buffer. For a pipeline that processes at least one event every 5 minutes, set the grace period to 8–10 minutes.

Store the heartbeat URL securely:

aws ssm put-parameter \
  --name "/myapp/prod/vigilmon-heartbeat-url" \
  --value "https://vigilmon.online/api/heartbeat/<your-id>" \
  --type SecureString

Reference it in your function configuration:

# SAM template excerpt
OrderProcessorFunction:
  Type: AWS::Serverless::Function
  Properties:
    Handler: handlers/order-processor.handler
    Runtime: nodejs22.x
    Policies:
      - SSMParameterReadPolicy:
          ParameterName: "/myapp/prod/vigilmon-heartbeat-url"
    Environment:
      Variables:
        VIGILMON_HEARTBEAT_URL: "{{resolve:ssm:/myapp/prod/vigilmon-heartbeat-url}}"

Step 2: Scheduled Health Probe via EventBridge Rule

Create a dedicated EventBridge rule on a schedule that triggers a health-check Lambda. This verifies the EventBridge scheduler itself is working and your event routing is functioning.

CDK: Health probe rule

import * as events from "aws-cdk-lib/aws-events";
import * as targets from "aws-cdk-lib/aws-events-targets";
import * as lambda from "aws-cdk-lib/aws-lambda";

// Health probe Lambda
const healthProbe = new lambda.Function(this, "EventBridgeHealthProbe", {
  runtime: lambda.Runtime.NODEJS_22_X,
  handler: "health-probe.handler",
  code: lambda.Code.fromAsset("handlers"),
  environment: {
    VIGILMON_HEARTBEAT_URL: ssm.StringParameter.valueForStringParameter(
      this,
      "/myapp/prod/vigilmon-heartbeat-url"
    ),
  },
});

// Schedule: ping every 5 minutes
new events.Rule(this, "HealthProbeSchedule", {
  schedule: events.Schedule.rate(cdk.Duration.minutes(5)),
  targets: [new targets.LambdaFunction(healthProbe)],
});

Health probe handler

// handlers/health-probe.mjs
import { EventBridgeClient, PutEventsCommand } from "@aws-sdk/client-eventbridge";

const eb = new EventBridgeClient({});

export async function handler() {
  const checks = {};
  let ok = true;

  // Verify we can put an event on the bus
  try {
    const result = await eb.send(
      new PutEventsCommand({
        Entries: [
          {
            Source: "myapp.health",
            DetailType: "HealthProbe",
            Detail: JSON.stringify({ probe: true, ts: Date.now() }),
            EventBusName: process.env.EVENT_BUS_NAME ?? "default",
          },
        ],
      })
    );
    const failed = result.FailedEntryCount ?? 0;
    checks.eventBus = failed === 0 ? "ok" : `${failed}_entries_failed`;
    if (failed > 0) ok = false;
  } catch (err) {
    checks.eventBus = `error: ${err.message}`;
    ok = false;
  }

  if (ok) {
    await fetch(process.env.VIGILMON_HEARTBEAT_URL, { method: "POST" });
    console.log("EventBridge health probe passed — heartbeat sent");
  } else {
    console.error("EventBridge health probe failed:", JSON.stringify(checks));
  }
}

In Vigilmon, set the heartbeat grace period to 8 minutes for a 5-minute schedule.


Step 3: Dead-Letter Queue Monitoring

When EventBridge can't deliver an event to a target, it routes to a dead-letter queue (DLQ). A growing DLQ means events are being lost. Monitor it with a Lambda poller that alerts via Vigilmon.

Attach a DLQ to your EventBridge rule target

import * as sqs from "aws-cdk-lib/aws-sqs";

const dlq = new sqs.Queue(this, "EventBridgeDLQ", {
  retentionPeriod: cdk.Duration.days(14),
});

new events.Rule(this, "OrderRule", {
  eventPattern: { source: ["myapp.orders"] },
  targets: [
    new targets.LambdaFunction(orderProcessor, {
      deadLetterQueue: dlq,
      retryAttempts: 2,
    }),
  ],
});

DLQ poller Lambda (triggered on schedule)

// handlers/dlq-monitor.mjs
import { SQSClient, GetQueueAttributesCommand } from "@aws-sdk/client-sqs";

const sqs = new SQSClient({});

export async function handler() {
  const attrs = await sqs.send(
    new GetQueueAttributesCommand({
      QueueUrl: process.env.DLQ_URL,
      AttributeNames: ["ApproximateNumberOfMessages"],
    })
  );

  const depth = parseInt(
    attrs.Attributes?.ApproximateNumberOfMessages ?? "0",
    10
  );

  if (depth > 0) {
    console.error(`DLQ has ${depth} unprocessed messages — alerting`);
    // Withhold the heartbeat so Vigilmon fires an alert
    return;
  }

  // DLQ is empty — all good
  await fetch(process.env.VIGILMON_HEARTBEAT_URL, { method: "POST" });
  console.log(`DLQ depth: ${depth} — heartbeat sent`);
}

Schedule this Lambda to run every 5 minutes using another EventBridge rule and create a separate Vigilmon heartbeat monitor for it.


Step 4: HTTP Health Endpoint for EventBridge API

If your application exposes an API Gateway endpoint that depends on EventBridge routing, add a health endpoint that validates the integration.

// handlers/api-health.mjs
import { EventBridgeClient, DescribeEventBusCommand } from "@aws-sdk/client-eventbridge";

const eb = new EventBridgeClient({});

export async function handler(event) {
  if (event.requestContext?.http?.method !== "GET") {
    return { statusCode: 405, body: "Method Not Allowed" };
  }

  const checks = {};
  let ok = true;

  try {
    const bus = await eb.send(
      new DescribeEventBusCommand({
        Name: process.env.EVENT_BUS_NAME ?? "default",
      })
    );
    checks.eventBus = bus.Name ? "ok" : "not_found";
    if (checks.eventBus !== "ok") ok = false;
  } catch (err) {
    checks.eventBus = `error: ${err.message}`;
    ok = false;
  }

  return {
    statusCode: ok ? 200 : 503,
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      status: ok ? "ok" : "degraded",
      region: process.env.AWS_REGION,
      checks,
    }),
  };
}

In Vigilmon, add an HTTP monitor pointing at this endpoint with a 10-second timeout and JSON assertion status = ok.


Step 5: Alerting Strategy

| Alert type | Recommended channel | |---|---| | Primary on-call | Email or PagerDuty webhook | | Team visibility | Slack webhook | | DLQ depth alert | Separate heartbeat monitor with short grace period | | Status page | Public Vigilmon status page embed |

Set alert escalation to notify immediately on first failure and re-notify every 5 minutes if still down. DLQ growth is often slow — set a tighter heartbeat grace period (6 minutes for a 5-minute poller) to catch it early.


What Vigilmon Catches That CloudWatch Misses

| Scenario | CloudWatch | Vigilmon | |---|---|---| | EventBridge rule silently stops matching | Needs InvocationCount alarm | Heartbeat not received — alert fires | | Target Lambda throws errors, events DLQ'd | Needs explicit DLQ depth alarm | DLQ monitor heartbeat withheld | | EventBus PutEvents fails due to IAM | Your alarms may also break | External probe catches it | | Scheduled rule skipped during AZ outage | Hard to distinguish from normal | Heartbeat grace period fires alert | | API Gateway + EventBridge integration broken | Needs API error rate alarm | HTTP monitor catches 5xx immediately |


EventBridge connects everything in your serverless architecture — which means a silent failure here ripples across dozens of downstream services. External monitoring from Vigilmon gives you the independent vantage point to catch these failures before your users do.

Start monitoring your EventBridge pipelines in under 5 minutes — register free at vigilmon.online.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →