Webhooks
Create a webhook endpoint
POST
/api/v1/webhooks
HTTPS only. Omit events for all event types and projectIds for all projects you can open. The response includes the signing secret — it is only shown here and on rotation. Up to 10 endpoints per account.
- API key access
- Read and write
- Project permission
- None — acts on your own account
- Success
- 201 — Created
Request body
JSON, with Content-Type: application/json.
| Field | Type | Notes |
|---|---|---|
url required |
string (uri) | |
description |
string | max 200 chars |
events |
array of "uptime.down" | "uptime.still_down" | "uptime.recovered" | "finding.opened" | "finding.resolved" | at least 1 item |
projectIds |
array of string | Empty = every project you can open |
enabled |
boolean |
Example request
Replace tk_live_YOUR_KEY with your API key.
curl -X POST 'https://trackerv2.marketingplanet.global/api/v1/webhooks' \
-H 'Authorization: Bearer tk_live_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://hooks.acmeretail.com/tracker",
"description": "Ops channel",
"events": [
"uptime.down",
"uptime.recovered"
],
"projectIds": [
"6650a4c2e1b3d5f7a9c1e301"
]
}'
const response = await fetch('https://trackerv2.marketingplanet.global/api/v1/webhooks', {
method: 'POST',
headers: {
Authorization: 'Bearer tk_live_YOUR_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"url": "https://hooks.acmeretail.com/tracker",
"description": "Ops channel",
"events": [
"uptime.down",
"uptime.recovered"
],
"projectIds": [
"6650a4c2e1b3d5f7a9c1e301"
]
}),
});
const result = await response.json();
if (!response.ok) throw new Error(`${result.error.code}: ${result.error.message}`);
console.log(result.data);
import requests
response = requests.post(
"https://trackerv2.marketingplanet.global/api/v1/webhooks",
headers={"Authorization": "Bearer tk_live_YOUR_KEY"},
json={
"url": "https://hooks.acmeretail.com/tracker",
"description": "Ops channel",
"events": [
"uptime.down",
"uptime.recovered",
],
"projectIds": [
"6650a4c2e1b3d5f7a9c1e301",
],
},
)
result = response.json()
if not response.ok:
raise RuntimeError(f"{result['error']['code']}: {result['error']['message']}")
print(result["data"])
Example response
{
"data": {
"id": "6655f9b7d6a8c0e2f4b6d801",
"url": "https://hooks.acmeretail.com/tracker",
"description": "Ops channel",
"events": [
"uptime.down",
"uptime.recovered"
],
"projectIds": [
"6650a4c2e1b3d5f7a9c1e301"
],
"enabled": true,
"disabledReason": null,
"consecutiveFailures": 0,
"lastDeliveryAt": null,
"lastSuccessAt": null,
"createdAt": "2026-09-12T08:00:00.000Z",
"updatedAt": "2026-10-06T09:20:44.000Z",
"secret": "whsec_Q2xhdWRlIGRvY3MgZXhhbXBsZSBzZWNyZXQ"
}
}
Response fields
| Field | Type | Notes |
|---|---|---|
data |
object | |
data.id |
string | |
data.url |
string | |
data.description |
string | |
data.events |
array of "uptime.down" | "uptime.still_down" | "uptime.recovered" | "finding.opened" | "finding.resolved" | |
data.projectIds |
array of string | |
data.enabled |
boolean | |
data.disabledReason |
string or null | |
data.consecutiveFailures |
integer | |
data.lastDeliveryAt |
string (date-time) or null | |
data.lastSuccessAt |
string (date-time) or null | |
data.createdAt |
string (date-time) | |
data.updatedAt |
string (date-time) | |
data.secret |
string |
Errors
Besides the errors any request can return (bad or missing key, plan, scope, rate limit), this endpoint can return:
| Status | error.code | Meaning |
|---|---|---|
| 400 | invalid-url | The webhook URL is not HTTPS, contains credentials, or points at a private address. |
| 400 | invalid-request | A parameter or body field is missing or invalid; error.message says which. |
| 404 | not-found | The resource does not exist or you cannot open it. Tracker does not reveal which. |
| 403 | too-many-endpoints | You already have the maximum of 10 webhook endpoints. |
Errors always look like { "error": { "code": "…", "message": "…" } }. See Errors & limits.