---
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)