Skip to content

Event subscriptions

Event subscriptions (REST hooks) send check and incident events to an HTTPS URL you register through the API. They power the Zapier integration. They are separate from webhook notification channels, which are configured in notification groups.

All subscription operations require the webhooks:manage scope. A subscription belongs to the API key or OAuth connection that created it.

POST /api/v1/hooks

Field Required Meaning
target_url Yes Public HTTPS URL, up to 2,000 characters, without credentials or a fragment
event Yes One of the events below
check_id No Only deliver events for this check
{
"target_url": "https://hooks.example.com/pingstack",
"event": "incident.opened",
"check_id": "check_EXAMPLE"
}

The 201 response includes the subscription and its signing secret. The secret is returned only here; store it privately.

{
"id": "hook_EXAMPLE",
"event": "incident.opened",
"target_url": "https://hooks.example.com/pingstack",
"check_id": "check_EXAMPLE",
"created_at": "2026-01-01T12:00:00Z",
"secret": "YOUR_SIGNING_SECRET"
}

Plain HTTP, private or internal addresses and checks from another account are rejected. Each API key or connection can hold up to 100 subscriptions.

DELETE /api/v1/hooks/:id deletes one of this key’s or connection’s subscriptions and returns it without the secret. Another key’s subscription returns 404.

Subscriptions are also deleted immediately when their API key is revoked or their OAuth connection is disconnected, within a day of the key or connection expiring, and when the receiver answers 410 Gone.

Event Sent when
check.down A check goes down
check.degraded A check becomes degraded
check.recovered A down or degraded check is back up
incident.opened An incident opens
incident.updated An open incident changes severity, or a status update is posted to it
incident.resolved An incident resolves

check.late is accepted as a subscription event but is not sent at present.

Each delivery is a POST with a JSON body:

{
"id": "4f1c2a6e-0000-4000-8000-000000000000",
"event": "incident.updated",
"occurred_at": "2026-01-01T12:05:00Z",
"alert": "Public website posted an incident update.",
"check": {
"id": "check_EXAMPLE",
"name": "Public website",
"kind": "website",
"target": "https://example.com/health",
"status": "down",
"previous_status": "down",
"url": "https://app.pingstack.io/checks/check_EXAMPLE"
},
"incident": {
"id": "123",
"cause": "Human-readable cause",
"started_at": "2026-01-01T12:00:00Z",
"resolved_at": null
},
"update": {
"id": "iupd_EXAMPLE",
"body": "We are rolling back the deploy.",
"author_name": "Example User",
"created_at": "2026-01-01T12:05:00Z"
}
}
Field Meaning
id Unique delivery event ID; also sent as X-Pingstack-Delivery
alert Display text; don’t parse it
check.target Hostname, host:port, or website URL without credentials, query or fragment; null for heartbeat checks
check.status, check.previous_status Status after and before the change
incident Null when the event has no incident
update Present only for incident.updated caused by a posted status update

Unlike incident reads, payloads include the incident cause and status update text.

Each delivery has these headers:

Header Value
X-Pingstack-Event The event name
X-Pingstack-Delivery The payload id
X-Pingstack-Timestamp Unix time in seconds when the delivery was sent
X-Pingstack-Signature sha256= and the hex HMAC-SHA256 of TIMESTAMP.BODY with the subscription secret

Compute the HMAC over the timestamp, a period and the exact raw request body, compare it in constant time, and reject old timestamps to limit replays:

expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", ENV.fetch("PINGSTACK_HOOK_SECRET"), "#{timestamp}.#{raw_body}")
valid = Rack::Utils.secure_compare(expected, signature_header) &&
(Time.now.to_i - timestamp.to_i).abs < 300

Return any 2xx response within 5 seconds. Redirects are not followed and count as failures. Failed deliveries are retried up to eight attempts with increasing backoff, then dropped. Return 410 Gone to delete the subscription.

Deliveries can repeat and arrive out of order; use id to deduplicate and occurred_at to order. Events stop while the key or connection has expired or its approving user lacks access to the account.

GET /api/v1/hooks/samples?event=EVENT returns recent real incidents from the account shaped exactly like live deliveries, for testing receivers and building field mappings.

Query Meaning
event Required event name
check_id Optional check filter
limit One to 10; default 3

The response is {"items": [...]}, where each item is a payload as above with an id beginning sample_. Sample statuses are the ones the event implies, so they can differ from the check’s current status. An account without matching incidents returns an empty list.