Skip to main content

Webhook payload reference

When an incident happens, Uptrack sends a POST request to your webhook URL with a JSON payload. This page documents every field you will receive for each event type.

Event types

Uptrack sends two types of webhook events:

EventWhen it fires
monitor.downA monitor fails its check and an incident is created
monitor.upA monitor recovers and the incident is resolved

monitor.down

Sent when a monitor fails its health check and a new incident is created.

POST https://your-server.com/webhooks/uptrack
{
  "event": "monitor.down",
  "timestamp": "2026-04-01T08:23:14Z",
  "monitor": {
    "id": "0192f1c4-7c8a-7000-8000-4b1d2e3f5a6c",
    "name": "Production API",
    "url": "https://api.example.com/health",
    "check_interval": 60,
    "protocol": "https"
  },
  "incident": {
    "id": "0192f1d0-3e21-7000-8000-9c8b7a6d5e4f",
    "started_at": "2026-04-01T08:23:14Z",
    "cause": "HTTP 503 Service Unavailable"
  },
  "check": null
}

Field descriptions

FieldTypeDescription
eventstringAlways "monitor.down" for this event type
timestampstringISO 8601 timestamp of when the event was emitted
monitor.idstringUnique monitor identifier
monitor.namestringHuman-readable monitor name
monitor.urlstringThe URL being monitored
monitor.check_intervalintegerCheck frequency in seconds (e.g. 30)
monitor.protocolstring | null"https", "http" or "tcp"; null for other monitor types
incident.idstringUnique incident identifier
incident.started_atstringISO 8601 timestamp of when the incident started
incident.causestringHuman-readable reason for the failure
checkobject | nullDetails of the failing check (status code, response time, location). null for now; the field is always present, so you can rely on it when it is filled in.

monitor.up

Sent when a monitor recovers and the open incident is resolved.

POST https://your-server.com/webhooks/uptrack
{
  "event": "monitor.up",
  "timestamp": "2026-04-01T08:31:47Z",
  "monitor": {
    "id": "0192f1c4-7c8a-7000-8000-4b1d2e3f5a6c",
    "name": "Production API",
    "url": "https://api.example.com/health",
    "check_interval": 60,
    "protocol": "https"
  },
  "incident": {
    "id": "0192f1d0-3e21-7000-8000-9c8b7a6d5e4f",
    "started_at": "2026-04-01T08:23:14Z",
    "resolved_at": "2026-04-01T08:31:47Z",
    "duration_seconds": 513
  },
  "check": null
}

Additional fields

FieldTypeDescription
incident.resolved_atstringISO 8601 timestamp of when the incident was resolved
incident.duration_secondsintegerTotal downtime duration in seconds

The monitor and check objects have the same shape as monitor.down. The incident.cause field is omitted on recovery events.

Request headers

Every webhook request includes these headers:

Content-Type: application/json
User-Agent: Uptrack-Webhook/1.0
X-Uptrack-Event: monitor.down
X-Uptrack-Signature: t=1712000000,v1=7ae11423d0db7008f5a3a3195bc742a615cb29ddbddd1d096e3f52056a3ab17b
FieldTypeDescription
Content-TypestringAlways "application/json"
User-AgentstringAlways "Uptrack-Webhook/1.0"
X-Uptrack-EventstringThe event type ("monitor.down" or "monitor.up")
X-Uptrack-Signaturestringt=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your signing secret>

Verifying webhook requests

Each webhook channel has its own signing secret. It starts with whsec_, and Uptrack shows it once, when you create the channel. Every request carriesX-Uptrack-Signature: t=…,v1=….v1 is the hex HMAC-SHA256 of the timestamp, a dot and the raw request body.

Compute the same value with your secret, compare in constant time, and reject requests older than 5 minutes. Use the raw body bytes, before any JSON parsing.

verify.js (Node)
const crypto = require('crypto')

function verifyUptrack(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries((header || '').split(',').map((p) => p.split('=')))
  const t = Number(parts.t)
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false
  const expected = crypto.createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest('hex')
  const a = Buffer.from(expected)
  const b = Buffer.from(parts.v1 || '')
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}
verify.py (Python)
import hashlib, hmac, time

def verify_uptrack(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.split('=', 1) for p in (header or '').split(',') if '=' in p)
    t = int(parts.get('t', 0))
    if abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get('v1', ''))

Lost the secret? Create the channel again, or rotate it with the API:PATCH /api/alert-channels/{id}with {"config": {"rotate_secret": true}}. The reply carries the new secret once, and the old one stops working at once.

Code examples

Receive and parse Uptrack webhook payloads in your language of choice.

Node.js (Express)

server.js
const express = require('express')
const app = express()

// Keep the raw body: the signature covers the exact bytes Uptrack sent.
app.post('/webhooks/uptrack', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.get('X-Uptrack-Signature')
  if (!verifyUptrack(req.body, signature, process.env.UPTRACK_WEBHOOK_SECRET)) {
    return res.status(401).send('Bad signature')
  }
  const { event, monitor, incident } = JSON.parse(req.body)

  switch (event) {
    case 'monitor.down':
      console.log(`[DOWN] ${monitor.name} — ${incident.cause}`)
      // Page your on-call team, create a ticket, etc.
      break

    case 'monitor.up':
      console.log(`[UP] ${monitor.name} — was down for ${incident.duration_seconds}s`)
      // Resolve the ticket, notify the channel
      break

  }

  res.status(200).send('OK')
})

app.listen(3000)

Python (Flask)

app.py
import json, os
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhooks/uptrack', methods=['POST'])
def uptrack_webhook():
    raw = request.get_data()
    if not verify_uptrack(raw, request.headers.get('X-Uptrack-Signature', ''), os.environ['UPTRACK_WEBHOOK_SECRET']):
        return jsonify(error='bad signature'), 401
    data = json.loads(raw)
    event = data['event']
    monitor = data['monitor']

    if event == 'monitor.down':
        incident = data['incident']
        print(f"[DOWN] {monitor['name']} — {incident['cause']}")

    elif event == 'monitor.up':
        incident = data['incident']
        print(f"[UP] {monitor['name']} — down for {incident['duration_seconds']}s")


    return jsonify(status='ok'), 200

Elixir (Phoenix)

webhook_controller.ex
defmodule MyAppWeb.WebhookController do
  use MyAppWeb, :controller

  def uptrack(conn, %{"event" => "monitor.down"} = params) do
    monitor = params["monitor"]
    incident = params["incident"]
    IO.puts("[DOWN] #{monitor["name"]} — #{incident["cause"]}")

    json(conn, %{status: "ok"})
  end

  def uptrack(conn, %{"event" => "monitor.up"} = params) do
    monitor = params["monitor"]
    incident = params["incident"]
    IO.puts("[UP] #{monitor["name"]} — down for #{incident["duration_seconds"]}s")

    json(conn, %{status: "ok"})
  end
end

Testing webhooks locally

Use one of these tools to inspect webhook payloads before writing any code:

webhook.site

Go to webhook.site to get a unique URL. Paste it as your webhook URL in Uptrack, then trigger a test alert. You will see the exact headers and payload in your browser.

ngrok

Run ngrok http 3000 to expose your local server. Use the generated URL as your webhook endpoint in Uptrack. Requests hit your local code so you can debug with breakpoints.

Integration ideas

Custom Slack formatting

Parse the webhook payload and post a rich Slack message with Block Kit. Include the monitor name, downtime duration, and a direct link to your status page.

PagerDuty trigger

Forward monitor.down events to PagerDuty's Events API v2 to page your on-call engineer. Auto-resolve when monitor.up arrives.

Zapier / Make

Use a Zapier "Catch Hook" trigger to receive Uptrack webhooks, then route them to Google Sheets, Jira, Linear, or any of 5,000+ apps without writing code.

Incident log database

Store every webhook payload in a database table. Query historical incidents, calculate MTTR, or build your own uptime reporting dashboard.

Start receiving webhook alerts

Pro includes 50 monitors at 30-second checks. Start free, or choose a plan for more.

See plans
HumanMachine