Pingtower Docs
Docs The service Integrations

Integrations

Tell something other than your phone. Every integration keeps a per-event delivery log, so when Slack goes quiet you can see whether it was you or them.

KindNeedsNotes
webhookurl. Optional secret; one is minted and shown once if you leave it out.Signed. Payload below.
slackwebhook_url from an incoming-webhook app.
telegramtoken for a bot and a chat_id.
apnsNothing. Rejected if you send credentials.Push to the iOS app. Recipients are the tenant’s members; the row exists so you can filter its events and read its delivery log.

Credentials are sealed at rest and never returned by a read. Set -secret-key on a self-hosted daemon before creating any.

Create one

POST /v1/projects/{id}/integrations

{
  "name": "ops-slack",
  "type": "slack",
  "config": { "webhook_url": "https://hooks.slack.com/services/…" },
  "events": ["alert.opened", "alert.escalated", "alert.resolved"]
}

events narrows which alert events it receives: alert.opened, alert.bumped, alert.escalated, alert.acked, alert.resolved. Leave it out to receive all five. In the app: Integrations → New.

POST /v1/projects/{id}/integrations/{iid}/test sends a synthetic alert.test event synchronously and returns what the far end said, so you can see a 403 from Slack before you save. A disabled integration answers 409.

Webhook payload

{
  "event": "alert.opened",
  "alert_id": "…",
  "project": "checkout",
  "rule": "gateway-timeouts",
  "template_sha256": "…",
  "state": "firing",
  "level": 400,
  "count": 7,
  "escalation_level": 0,
  "message": "payment gateway timeout after 30012ms for order o_48113",
  "at": 1756696364,
  "identity": "gateway-timeouts",
  "identifiers": { "gateway": "stripe" },
  "page_accounts": ["…"]
}

identifiers and page_accounts are present only when the rule set them.

Verifying the signature

Two headers are sent: X-Pingtower-Timestamp, a unix timestamp, and X-Pingtower-Signature, the hex HMAC-SHA256 of "<timestamp>.<raw body>" under the endpoint’s secret. Recompute it over the exact bytes you received and compare in constant time. Reject timestamps older than a few minutes to close replays.

import hmac, hashlib

def verify(secret: bytes, timestamp: str, body: bytes, signature: str) -> bool:
    mac = hmac.new(secret, timestamp.encode() + b"." + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(mac, signature)

Outbound URLs are checked against private and loopback ranges before delivery, so a webhook cannot be pointed at something inside the daemon’s own network.

Delivery log

GET /v1/projects/{id}/integrations/{iid}/deliveries?limit= lists attempts, newest first: event, alert_id, status, attempts, last_error, next_attempt_at. The app shows the same list under the integration.

Delivery is at-least-once through a durable outbox. A failed attempt retries with backoff starting at 30 seconds and doubling up to an hour. After ten consecutive failures the integration is disabled and stops trying; re-enable it with POST /v1/projects/{id}/integrations/{iid}/enable once the far end is fixed, which also clears the failure streak.

Last updated 1 Sep 2026 Report a problem with this page