tutorial

Monitoring AWS SNS with Vigilmon: Subscriber Health Checks, Delivery Failure Alerts & Topic Heartbeats

Practical guide to uptime monitoring for AWS SNS — HTTP health endpoints for subscriber services, heartbeat patterns for topic processors, delivery failure probes, and external alerting with Vigilmon.

AWS SNS fans messages out to dozens of subscribers in milliseconds — but when a subscriber is unhealthy, SNS keeps retrying silently until it gives up and drops the message. You won't see a red dashboard; you'll see a missing order confirmation email six hours after the Lambda started returning 500s. CloudWatch can track delivery failures, but it's inside your AWS account and can be disrupted by the same incident you're trying to detect. Vigilmon gives you an external, independent view: HTTP monitors on your subscriber services, heartbeat monitors for topic workers, and delivery failure probes that alert before messages are lost.

This tutorial walks you through monitoring SNS-based architectures with Vigilmon.

What You'll Build

  • A health endpoint on your SNS subscriber service that probes real topic access
  • A Vigilmon HTTP monitor with appropriate settings for push subscribers
  • A heartbeat pattern for Lambda and SQS subscribers
  • A delivery failure probe that withholds a heartbeat when failures accumulate
  • An alerting strategy for subscriber outages and topic delivery problems

Prerequisites

  • AWS account with at least one SNS topic and a subscriber service (HTTP endpoint, Lambda, or SQS)
  • AWS CLI or CDK/SAM configured locally
  • A free account at vigilmon.online

Step 1: Add a Health Endpoint to Your Subscriber Service

HTTP/HTTPS subscribers are the easiest to monitor: SNS pushes directly to your endpoint. Add a /health route that checks real SNS connectivity.

Node.js (Express)

// routes/health.js
import { SNSClient, GetTopicAttributesCommand } from "@aws-sdk/client-sns";

const sns = new SNSClient({});

export async function healthHandler(req, res) {
  const checks = {};
  let ok = true;

  // Verify we can read the topic we're subscribed to
  try {
    const result = await sns.send(
      new GetTopicAttributesCommand({
        TopicArn: process.env.SNS_TOPIC_ARN,
      })
    );
    const subsCount = result.Attributes?.SubscriptionsConfirmed ?? "unknown";
    checks.sns = "ok";
    checks.confirmedSubscriptions = subsCount;
  } catch (err) {
    checks.sns = `error: ${err.message}`;
    ok = false;
  }

  // Check our own subscription is still confirmed
  try {
    const { SNSClient: SNS, ListSubscriptionsByTopicCommand } = await import("@aws-sdk/client-sns");
    const result = await sns.send(
      new ListSubscriptionsByTopicCommand({ TopicArn: process.env.SNS_TOPIC_ARN })
    );
    const sub = result.Subscriptions?.find(
      (s) => s.Endpoint === process.env.SERVICE_URL + "/sns"
    );
    checks.subscription = sub?.SubscriptionArn?.startsWith("arn:") ? "confirmed" : "pending_or_missing";
    if (checks.subscription !== "confirmed") ok = false;
  } catch (err) {
    checks.subscription = `error: ${err.message}`;
    ok = false;
  }

  res.status(ok ? 200 : 503).json({
    status: ok ? "ok" : "degraded",
    service: "sns-subscriber",
    checks,
  });
}

Python (FastAPI)

# routers/health.py
import os
import boto3
from fastapi import APIRouter
from botocore.exceptions import ClientError

router = APIRouter()
sns = boto3.client("sns")

@router.get("/health")
async def health():
    checks = {}
    ok = True

    try:
        resp = sns.get_topic_attributes(TopicArn=os.environ["SNS_TOPIC_ARN"])
        checks["sns"] = "ok"
        checks["confirmedSubscriptions"] = resp["Attributes"].get("SubscriptionsConfirmed", "unknown")
    except ClientError as e:
        checks["sns"] = f"error: {e.response['Error']['Code']}"
        ok = False

    return {
        "status": "ok" if ok else "degraded",
        "service": "sns-subscriber",
        "checks": checks,
    }

Go (net/http)

// internal/health/handler.go
package health

import (
    "context"
    "encoding/json"
    "net/http"
    "os"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/service/sns"
)

func Handler(client *sns.Client) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        checks := map[string]any{}
        ok := true

        _, err := client.GetTopicAttributes(context.Background(), &sns.GetTopicAttributesInput{
            TopicArn: aws.String(os.Getenv("SNS_TOPIC_ARN")),
        })
        if err != nil {
            checks["sns"] = "error: " + err.Error()
            ok = false
        } else {
            checks["sns"] = "ok"
        }

        w.Header().Set("Content-Type", "application/json")
        if !ok {
            w.WriteHeader(http.StatusServiceUnavailable)
        }
        json.NewEncoder(w).Encode(map[string]any{
            "status":  map[bool]string{true: "ok", false: "degraded"}[ok],
            "service": "sns-subscriber",
            "checks":  checks,
        })
    }
}

Step 2: Configure the Vigilmon HTTP Monitor

  1. Log in to VigilmonAdd Monitor → HTTP.
  2. URL: your subscriber service health endpoint (e.g. https://notifications.example.com/health).
  3. Check interval: 60 seconds.
  4. Response timeout: 10 seconds.
  5. Expected status: 200.
  6. JSON assertion: path status, expected value ok.

Tip: SNS HTTP subscribers must respond to the subscription confirmation request before they receive messages. If your health endpoint returns 200 but your /sns handler is broken, messages are silently dropped. Include a subscription: confirmed check in the health response as shown above.


Step 3: Heartbeat Monitoring for Lambda Subscribers

Lambda subscribers run on-demand — there is no persistent process to probe. Use Vigilmon's Heartbeat monitor and ping it after successful message processing.

// handlers/notifications.mjs
export async function handler(event) {
  for (const record of event.Records) {
    const message = JSON.parse(record.Sns.Message);
    await processNotification(message);
  }

  // Ping Vigilmon heartbeat after successful batch
  await fetch(process.env.VIGILMON_HEARTBEAT_URL, { method: "POST" });
  console.log(`Processed ${event.Records.length} SNS records — heartbeat sent`);
}

Set the grace period to 2–3× the expected maximum gap between topic messages. For critical notification pipelines (email sends, payment events), set it as low as 5 minutes.

Store the heartbeat URL in SSM Parameter Store:

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

Reference it in your SAM/CDK configuration as an environment variable resolved from SSM.


Step 4: Delivery Failure Probe with CloudWatch

SNS records delivery failures in CloudWatch. A lightweight Lambda scheduled every 5 minutes can read that metric and withhold a Vigilmon heartbeat when failures accumulate.

# dlf-probe/handler.py
import os
import boto3
from datetime import datetime, timedelta, timezone

cloudwatch = boto3.client("cloudwatch")

def handler(event, context):
    end = datetime.now(timezone.utc)
    start = end - timedelta(minutes=5)

    resp = cloudwatch.get_metric_statistics(
        Namespace="AWS/SNS",
        MetricName="NumberOfNotificationsFailed",
        Dimensions=[{"Name": "TopicName", "Value": os.environ["TOPIC_NAME"]}],
        StartTime=start,
        EndTime=end,
        Period=300,
        Statistics=["Sum"],
    )

    total_failures = sum(dp["Sum"] for dp in resp.get("Datapoints", []))

    if total_failures == 0:
        # No failures — send heartbeat
        import urllib.request
        req = urllib.request.Request(
            os.environ["VIGILMON_HEARTBEAT_URL"],
            method="POST"
        )
        urllib.request.urlopen(req)
        print("No delivery failures — heartbeat sent")
    else:
        # Withhold heartbeat — Vigilmon will alert
        print(f"SNS delivery failures: {int(total_failures)} — heartbeat withheld")

Schedule with EventBridge every 5 minutes. Set the Vigilmon heartbeat grace period to 10 minutes.


Step 5: Fan-Out Architecture Monitoring

SNS fan-out (one topic → multiple SQS queues, Lambdas, HTTP endpoints) means one topic failure can break multiple downstream services. Monitor each subscriber independently.

| Vigilmon monitor | What it watches | |---|---| | notifications-http | HTTP subscriber health | | notifications-lambda-heartbeat | Lambda subscriber heartbeat | | notifications-sqs-depth | SQS dead-letter queue probe | | delivery-failures-heartbeat | CloudWatch failure probe |

Group them under one Status Page for a unified view. Configure alerts so the whole team sees the same incident, not four separate Slack messages.


Step 6: Alerting Strategy

| Alert type | Recommended channel | |---|---| | HTTP subscriber 503 | Email + PagerDuty | | Lambda heartbeat missed | Slack + PagerDuty | | Delivery failure probe triggered | Email + Slack | | Multiple subscribers degraded | Status page + PagerDuty escalation |

Use alert escalation: notify immediately, re-notify every 5 minutes if still down. SNS delivery retries exhaust quickly (default 3 retries over ~20 seconds for HTTP), so detection needs to be fast.


What Vigilmon Catches That CloudWatch Misses

| Scenario | CloudWatch | Vigilmon | |---|---|---| | HTTP subscriber returns 503 | Needs DeliveryFailures alarm | HTTP monitor catches immediately | | Lambda subscriber stops processing | Needs custom metric | Heartbeat grace period fires alert | | Subscription accidentally unconfirmed | No built-in alert | Health endpoint confirms subscription status | | AWS account has IAM issue | Your alarms may also break | Vigilmon is external — unaffected | | Delivery failures accumulate silently | Needs metric alarm + SNS (ironic) | Failure probe + heartbeat covers this |


SNS looks reliable until a subscriber goes down and messages disappear without a trace. Delivery retries are silent, and by the time you notice, events are lost. External monitoring from Vigilmon gives you the independent signal you need — before your message pipeline runs dry.

Start monitoring your SNS subscribers 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 →