Skip to content
Back to skills

Betterstack

ASecurity

Better Stack (formerly Better Uptime + Logtail) is an observability platform that combines uptime monitoring, log management, incident response with on-call escalation, and status pages. Use when a user asks to set up uptime monitors or heartbeat monitoring for cron jobs, create escalation policies, send logs with @logtail/node or the Pino transport, manage Better Stack through its API or Terraform provider, or publish status page updates.

  • 142 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added May 27, 2026
devopstypescriptrustgoshellbashsqlnodeterraformapidatabase

Works with

  • terminal
  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 2 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add TerminalSkills/skills --skill betterstack --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Betterstack?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Betterstack
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/terminalskills-betterstack/badge)](https://www.skillsdirectory.com/skills/terminalskills-betterstack)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: betterstack
description: Better Stack (formerly Better Uptime + Logtail) is an observability platform that combines uptime monitoring, log management, incident response with on-call escalation, and status pages. Use when a user asks to set up uptime monitors or heartbeat monitoring for cron jobs, create escalation policies, send logs with @logtail/node or the Pino transport, manage Better Stack through its API or Terraform provider, or publish status page updates.
license: Apache-2.0
compatibility: Better Stack account with an API token; Node.js for the @logtail packages; Terraform 0.14+ for the provider
metadata:
  author: terminal-skills
  version: 1.1.0
  category: devops
  tags:
  - uptime
  - logging
  - incident-management
  - status-page
  - on-call
---

# Better Stack — Uptime, Logs, and Incident Management

## Overview

Better Stack (formerly Better Uptime + Logtail) is the observability platform combining uptime monitoring, log management, incident response, and status pages. It helps developers set up comprehensive monitoring with alerting, on-call schedules, and public status pages.

Monitors, heartbeats, incidents, escalation policies and status pages are also managed through a JSON:API-style REST API — the reference documents it at `https://incidents.betterstack.com`, while older examples and the Terraform provider use the earlier host `uptime.betterstack.com` — and through the `BetterStackHQ/better-uptime` Terraform provider. Requests use `Authorization: Bearer $TOKEN`. Tokens come from **Better Stack → API tokens**: a team-scoped Uptime API token, or a global token (then pass `team_name` when creating resources). Logs use a separate per-source token and ingesting host.

## Instructions

### Uptime Monitoring

```hcl
# main.tf — the provider reads the token from BETTERUPTIME_API_TOKEN
provider "betteruptime" {}

resource "betteruptime_monitor" "api" {
  url                   = "https://api.northwind-shop.com/health"
  pronounceable_name    = "Northwind API"
  monitor_type          = "expected_status_code"
  expected_status_codes = [200]
  check_frequency       = 30                  # seconds
  confirmation_period   = 60                  # wait 60 s of failures before opening an incident
  regions               = ["us", "eu", "as"]  # allowed: us, eu, as, au
  ssl_expiration        = 30                  # alert 30 days before the certificate expires
  policy_id             = betteruptime_policy.default.id

  request_headers = [{
    name  = "Authorization"
    value = "Bearer ${var.health_check_token}"   # declare as a sensitive variable
  }]
}

resource "betteruptime_monitor" "database" {
  url             = "db.northwind-shop.com"
  monitor_type    = "tcp"
  port            = "5432"
  check_frequency = 60
  policy_id       = betteruptime_policy.default.id
}
```

Monitor types: `status` (any 2XX), `expected_status_code`, `keyword`, `keyword_absence`, `ping`, `tcp`, `udp`, `smtp`, `pop`, `imap`, `dns`, `playwright`. There is no separate SSL monitor type: `ssl_expiration` and `domain_expiration` are attributes of an HTTP monitor (valid values 1, 2, 3, 7, 14, 30, 60 days). Maintenance windows are set per monitor with `maintenance_days`, `maintenance_from`, `maintenance_to`, `maintenance_timezone`.

The same monitor through the API is `POST https://incidents.betterstack.com/api/v2/monitors` with these attributes as a JSON body (see Example 1).

### Log Management (Logtail)

Create a source under **Sources** in the Better Stack dashboard; every source has its own source token and ingesting host. Both are required — the client must be pointed at the source's own host.

```typescript
// Send structured logs to Better Stack
import { Logtail } from "@logtail/node";

const logtail = new Logtail(process.env.BETTERSTACK_SOURCE_TOKEN!, {
  endpoint: `https://${process.env.BETTERSTACK_INGESTING_HOST}`,
});

logtail.info("Order processed", {
  orderId: "ord_8f3k2",
  userId: "usr_456",
  amount: 99.99,
  duration_ms: 245,
});

logtail.error("Payment failed", {
  orderId: "ord_9d1m7",
  stripe_error_code: "card_declined",
  retryable: true,
});

await logtail.flush();   // logs are batched; flush before the process exits

// Pino integration (Pino 7+)
import pino from "pino";

const logger = pino(
  pino.transport({
    target: "@logtail/pino",
    options: {
      sourceToken: process.env.BETTERSTACK_SOURCE_TOKEN,
      options: { endpoint: `https://${process.env.BETTERSTACK_INGESTING_HOST}` },
    },
  })
);

logger.info({ orderId: "ord_8f3k2", userId: "usr_456" }, "Order created");
```

Any language can post JSON to the ingesting host directly:

```bash
curl -X POST "https://$BETTERSTACK_INGESTING_HOST" \
  -H "Authorization: Bearer $BETTERSTACK_SOURCE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{"message":"deploy finished","service":"checkout","dt":"2026-10-01 07:03:30+00:00"}]'
```

A `202` means accepted; `402` quota or spending limit reached, `403` invalid source token, `406` unparsable body, `413` body over 10 MiB. `dt` overrides the event time; without it the time of receipt is used.

### On-Call and Escalation

An escalation policy is a list of steps; each escalation step notifies its members after `wait_before` seconds unless the incident was acknowledged.

```hcl
data "betteruptime_severity" "high" {
  name = "High Severity"    # default severity present in every account
}

resource "betteruptime_policy" "default" {
  name         = "Production escalation"
  repeat_count = 3          # repeat the whole policy if nobody acknowledges
  repeat_delay = 1800

  steps {                   # Step 1: whoever is on call, immediately
    type        = "escalation"
    wait_before = 0
    urgency_id  = data.betteruptime_severity.high.id
    step_members { type = "current_on_call" }
  }
  steps {                   # Step 2: after 10 minutes unacknowledged, the entire team
    type        = "escalation"
    wait_before = 600
    urgency_id  = data.betteruptime_severity.high.id
    step_members { type = "entire_team" }
  }
}
```

Other member types include `user` (with `email`), `all_slack_integrations` and `policy` (chain to another policy). The API equivalent is `POST /api/v3/policies`. On-call calendars are managed in the dashboard or under `/api/v2/on-calls`. Incidents can be acknowledged and resolved from scripts: `POST /api/v3/incidents/{incident_id}/acknowledge` and `/resolve`.

### Status Pages

Hosted status pages support a custom domain, sections and resources linked to monitors, subscriber notifications and maintenance announcements. Publish an update with a status report (not with the incidents endpoint):

```typescript
const statusPageId = process.env.BETTERSTACK_STATUS_PAGE_ID;
const response = await fetch(
  `https://incidents.betterstack.com/api/v2/status-pages/${statusPageId}/status-reports`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.BETTERSTACK_API_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title: "Elevated API latency",
      message: "API responses are slower than usual while a database migration runs. No data loss expected.",
      report_type: "manual",            // or "maintenance": ends_at required, every status "maintenance"
      notify_subscribers: true,         // default false
      affected_resources: [
        { status_page_resource_id: "8742113", status: "degraded" },  // resolved | degraded | downtime
      ],
    }),
  }
);
```

To page people instead, create an incident: `POST /api/v3/incidents` with `summary` and `requester_email` (required), optional `name`, `description`, `policy_id`, `call`, `sms`, `email`, `push`.

### Heartbeat Monitoring

A heartbeat expects a request every `period` seconds (plus `grace`) and opens an incident when it stops arriving. It stays "Pending" until the first ping.

```typescript
// The heartbeat's `url` attribute: https://incidents.betterstack.com/api/v1/heartbeat/ followed by its token
const heartbeatUrl = process.env.BETTERSTACK_HEARTBEAT_URL!;

async function dailyReport() {
  try {
    await generateReport();
    await sendReportEmail();
    await fetch(heartbeatUrl);                 // success ping
  } catch (error) {
    await fetch(`${heartbeatUrl}/fail`);       // report the failure right away
    throw error;
  }
}
```

In shell jobs, send the output and append the exit code instead: `output=$(/opt/northwind/backup.sh 2>&1); curl -d "$output" "$BETTERSTACK_HEARTBEAT_URL/$?"`.

## Installation

```bash
# Node.js logging
npm install @logtail/node
npm install @logtail/pino pino     # Pino transport
```

```hcl
terraform {
  required_providers {
    betteruptime = {
      source  = "BetterStackHQ/better-uptime"
      version = ">= 0.22.0"
    }
  }
}
```

## Examples

### Example 1: Monitor an API and a nightly cron job

**User request:**

```
We run a Node.js API at api.northwind-shop.com and a nightly Postgres backup. Monitor both with Better Stack.
```

```bash
API=https://incidents.betterstack.com/api/v2
curl -s -X POST "$API/monitors" \
  -H "Authorization: Bearer $BETTERSTACK_API_TOKEN" -H 'Content-Type: application/json' \
  -d '{"monitor_type":"expected_status_code","url":"https://api.northwind-shop.com/health",
       "pronounceable_name":"Northwind API","expected_status_codes":[200],
       "check_frequency":30,"confirmation_period":60,"regions":["us","eu","as"],"email":true,"push":true}'

curl -s -X POST "$API/heartbeats" \
  -H "Authorization: Bearer $BETTERSTACK_API_TOKEN" -H 'Content-Type: application/json' \
  -d '{"name":"Nightly database backup","period":86400,"grace":3600}'
```

Each call answers `201` with the new resource; the heartbeat response carries the URL to ping:

```json
{"data":{"id":"512044","type":"heartbeat","attributes":{
  "url":"https://incidents.betterstack.com/api/v1/heartbeat/abcd1234abcd1234abcd1234",
  "name":"Nightly database backup","period":86400,"grace":3600}}}
```

```bash
# crontab (define BETTERSTACK_HEARTBEAT_URL in the crontab too — cron does not read the shell profile): ping only on success
0 2 * * * /opt/northwind/backup.sh && curl -fsS "$BETTERSTACK_HEARTBEAT_URL" > /dev/null
```

A validation error returns `422` with an `errors` object, for example `{"errors":{"base":["URL is invalid."]}}`.

### Example 2: Logs from a Node.js service never arrive

**User request:**

```
We added @logtail/node to the checkout service but nothing shows up in Better Stack Live tail.
```

First test the source itself, outside the application:

```bash
curl -s -o /dev/null -w '%{http_code}\n' -X POST "https://$BETTERSTACK_INGESTING_HOST" \
  -H "Authorization: Bearer $BETTERSTACK_SOURCE_TOKEN" -H "Content-Type: application/json" \
  -d '{"message":"connectivity test from checkout"}'
```

`202` means the token and host are right and the problem is in the code; `403` means the source token is invalid for that host. The two usual causes in code are a client created without the source's `endpoint`, and a short-lived process (script, serverless function) that exits before the batch is sent:

```typescript
const logtail = new Logtail(process.env.BETTERSTACK_SOURCE_TOKEN!, {
  endpoint: `https://${process.env.BETTERSTACK_INGESTING_HOST}`,
});
logtail.info("Checkout started", { cartId: "cart_7h2p", items: 3 });
await logtail.flush();
```

After the fix the event appears in Live tail with `cartId` and `items` as queryable fields.

## Guidelines

1. **Monitor from multiple regions** — Check from several of US, EU, Asia and Australia (`us`, `eu`, `as`, `au`); a single region can have false positives from network issues
2. **Heartbeats for cron jobs** — Use heartbeat monitors for every scheduled task; silent failures are the worst kind. Set `grace` to roughly 20% of `period`
3. **Escalation policies** — Always have an escalation chain; a single point of failure in alerting defeats the purpose
4. **Status page for trust** — Public status pages build customer trust; link monitors as status page resources so their state is shown automatically
5. **Structured logs** — Send JSON with context (userId, orderId, etc.); Better Stack's SQL queries work best with structured data
6. **Alert fatigue prevention** — Don't alert on single failures; use `confirmation_period` so an incident starts only after the failure persists, and `recovery_period` before auto-resolving
7. **Maintenance windows** — Schedule maintenance windows to suppress alerts during planned work
8. **Keep tokens apart and secret** — The API token manages monitors and incidents; a source token can only write logs to one source. Read both from environment variables, mark Terraform header values as sensitive, and treat heartbeat URLs as secrets too — anyone who has one can silence the alert
9. **Check frequency vs timeout** — `check_frequency` must be at least the request timeout; for ping/TCP monitors `request_timeout` is in milliseconds, for HTTP monitors in seconds
10. **Limits** — This is a paid service: log ingestion answers `402` once the free quota or the spending limit is reached, and a single log request is limited to 10 MiB.

Files in this skill

  • SKILL.md7.1 KB
  • _scores.json1.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…