--- title: "Webhook Payload Reference — Uptrack" description: "Exact JSON payloads Uptrack sends your webhook when a monitor goes down or recovers, signed with HMAC-SHA256. Verify and parse them in Node, Python or Elixir." url: https://uptrack.app/guides/webhook-payloads ---

# 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: | Event | When it fires | | ------------ | ---------------------------------------------------- | | monitor.down | A monitor fails its check and an incident is created | | monitor.up | A 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 | Field | Type | Description | | ----------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | event | string | Always "monitor.down" for this event type | | timestamp | string | ISO 8601 timestamp of when the event was emitted | | monitor.id | string | Unique monitor identifier | | monitor.name | string | Human-readable monitor name | | monitor.url | string | The URL being monitored | | monitor.check\_interval | integer | Check frequency in seconds (e.g. 30) | | monitor.protocol | string \| null | "https", "http" or "tcp"; null for other monitor types | | incident.id | string | Unique incident identifier | | incident.started\_at | string | ISO 8601 timestamp of when the incident started | | incident.cause | string | Human-readable reason for the failure | | check | object \| null | Details 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 | Field | Type | Description | | -------------------------- | ------- | ---------------------------------------------------- | | incident.resolved\_at | string | ISO 8601 timestamp of when the incident was resolved | | incident.duration\_seconds | integer | Total 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 ``` | Field | Type | Description | | ------------------- | ------ | ---------------------------------------------------------------------------------- | | Content-Type | string | Always "application/json" | | User-Agent | string | Always "Uptrack-Webhook/1.0" | | X-Uptrack-Event | string | The event type ("monitor.down" or "monitor.up") | | X-Uptrack-Signature | string | t=<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 carries`X-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](https://uptrack.app/signup) ## Related reading - [Connect Uptrack to Zapier](https://uptrack.app/guides/zapier) - [Slack slash commands](https://uptrack.app/guides/slack-commands) - [n8n node](https://uptrack.app/integrations/n8n) - [Uptime monitoring](https://uptrack.app/features/uptime-monitoring)