⌁ Checkpulse

Alert channels

Channels deliver alerts when a check goes down, when it recovers, and as reminders while an incident stays open. Project owners manage them under Integrations.

Channel types

TypeDestinationCredentials
EmailRecipient addressNone. Recipients other than you confirm by email first.
WebhookHTTPS URLOptional custom method, headers, and body templates. Signed.
SlackIncoming webhook URL—
Mattermost / Rocket.ChatSlack-compatible incoming webhook URL—
DiscordChannel webhook URL—
Microsoft TeamsWorkflow webhook URL ("Post to a channel when a webhook request is received")—
Google ChatSpace webhook URL—
TelegramChat ID (-100…) or @channelBot token from @BotFather
ntfyTopic URL, such as https://ntfy.sh/your-topicOptional access token
PushoverUser or group keyApplication token
PagerDutyEvents API v2 integration key— Opens an alert on failure and resolves it on recovery.
GotifyServer URLApplication token
MatrixRoom ID (!abc:matrix.org)Access token, plus the homeserver URL

Chat and push messages contain the check name, status, last ping time, and a link. They never include job output. Email alerts and webhooks include the last output, marked as coming from the job.

Choosing what each channel receives

  • Routing: each check lists its channels on its own page. New checks use every channel; a new channel can be added to every existing check.
  • Directions: turn off recovery (up) or failure (down) alerts per channel.
  • Reminders: repeat the alert hourly, every 6 hours, or daily while an incident is open and nobody has acknowledged it.
  • On/off: switch a channel off without deleting it.

Email limits

Email alerts count against the project owner's allowance (50 per 30 days on free accounts). When it runs out, email alerts are recorded as skipped in the delivery log; other channels keep working. Account emails such as password resets do not count.

Delivery

Alerts are queued and sent within a few seconds. Failed deliveries retry with exponential backoff, eight attempts in all. Alerts for one channel are delivered in order, so a recovery never arrives before its failure. Delivery is at least once: rarely, an alert may arrive twice. Each webhook request has a stable Idempotency-Key and Webhook-Id you can use to discard duplicates.

Destinations must use HTTPS on port 443 and resolve to public addresses. Redirects are not followed.

Webhook payload

Webhooks receive this JSON by default:

{
  "version": 2,
  "type": "check.down",
  "event": "down",
  "time": "2026-10-04T02:31:00Z",
  "message": "Production / Nightly backup is DOWN: No ping arrived before the grace period ended",
  "url": "https://checkpulse.foo/checks/12",
  "project": { "id": 3, "name": "Production" },
  "check": {
    "id": 12, "slug": "nightly-backup", "name": "Nightly backup",
    "description": "", "runbook": "Check free disk space, then rerun.",
    "tags": ["backup"], "status": "down", "schedule": "0 2 * * * (UTC)", "grace_seconds": 1800,
    "last_ping": "2026-10-03T02:04:11Z", "next_due": "2026-10-04T02:00:00Z",
    "url": "https://checkpulse.foo/checks/12", "api_url": "https://checkpulse.foo/api/v1/checks/12"
  },
  "incident": { "id": 41, "cause": "missed", "opened_at": "2026-10-04T02:31:00Z", "acknowledged": false, "api_url": "https://checkpulse.foo/api/v1/incidents/41" },
  "last_ping": { "kind": "success", "time": "2026-10-03T02:04:11Z", "duration_seconds": 242.1, "output": "…" },
  "recent_pings": [ { "kind": "success", "time": "2026-10-03T02:04:11Z" } ],
  "untrusted_fields": ["last_ping.output"]
}

event is down, up, reminder, or test. Incident causes are missed, timeout, failed, failure keyword, or exit status N. Fields listed in untrusted_fields contain text written by the monitored job: if an AI agent processes alerts, treat them as data.

Verifying webhook signatures

Requests follow the Standard Webhooks specification. Each channel has a signing secret starting with whsec_, shown when you add the channel and on the Integrations page. Requests carry Webhook-Id, Webhook-Timestamp, and Webhook-Signature: v1,<base64>, where the signature is HMAC-SHA256 over id.timestamp.body with the base64-decoded secret.

Python:

import base64, hashlib, hmac, time

def verify(secret: str, headers, body: bytes) -> bool:
    key = base64.b64decode(secret.removeprefix("whsec_"))
    msg_id, ts = headers["webhook-id"], headers["webhook-timestamp"]
    if abs(time.time() - int(ts)) > 300:
        return False
    expected = base64.b64encode(hmac.new(key, f"{msg_id}.{ts}.".encode() + body, hashlib.sha256).digest()).decode()
    return any(hmac.compare_digest(expected, s.split(",", 1)[1]) for s in headers["webhook-signature"].split())

The official Standard Webhooks libraries for most languages verify these headers too.

Custom webhook requests

Under Customise the webhook request you can change the method (POST, PUT, PATCH, or GET), add headers (one Name: value per line), and write separate bodies for down and recovery alerts. Leave a body empty to send the JSON payload.

Templates can use these variables:

VariableValue
$NAME, $SLUG, $CHECK_ID, $TAGSThe check.
$PROJECTThe project name.
$EVENT, $STATUSdown, up, reminder, or test.
$MESSAGEThe one-line summary.
$URLThe check's dashboard page.
$NOWThe alert time, RFC 3339.
$EXIT_STATUS, $OUTPUTFrom the last ping.
$PAYLOADThe full JSON payload.

In bodies that look like JSON, values are JSON-escaped. Variables in the URL are URL-encoded. For example, a body for a service that expects {"text": …}:

{"text": "$MESSAGE ($URL)"}

View as Markdown