Integrations

Configure where ExitProbe sends notifications — webhook, Slack, Discord, Telegram, or email — and which events each one should fire on.

Types

webhook — POSTs a signed JSON payload to any URL you control (see Webhooks & Events for the exact payload shape and HMAC signature verification). slack and discord post to an incoming webhook URL from those apps. telegram sends via a bot token + chat id. email sends to one address.

Sensitive config values (URLs, tokens, secrets) are always masked in API responses after creation — you’ll see something like https://example.com/…/1a2b, never the full value again. Only Rotate Secret returns a plaintext secret, and only once.
After 10 consecutive failed deliveries, an integration is automatically disabled (is_active: false) and the account owner is emailed once about it — check last_error and failure_count on List Integrations, or the Delivery History endpoint below, to diagnose why.
GET/integrations/meta
Integration Types & Events

The canonical list of integration types (with their config fields) and events you can subscribe to — build your integration-creation UI from this rather than hardcoding it.

Example Request
curl -X GET "https://api.exitprobe.com/api/v1/integrations/meta" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
Example Response
{
  "success": true,
  "data": {
    "types": [
      { "type": "webhook", "label": "Webhook", "fields": [
        { "key": "url", "label": "URL", "type": "url", "required": true },
        { "key": "secret", "label": "Signing Secret", "type": "password", "required": false },
        { "key": "headers", "label": "Extra Headers (JSON)", "type": "textarea", "required": false }
      ] },
      { "type": "email", "label": "Email", "fields": [{ "key": "email", "label": "Email Address", "type": "email", "required": true }] },
      { "type": "telegram", "label": "Telegram", "fields": [
        { "key": "bot_token", "label": "Bot Token", "type": "password", "required": true },
        { "key": "chat_id", "label": "Chat ID", "type": "text", "required": true }
      ] },
      { "type": "slack", "label": "Slack", "fields": [{ "key": "webhook_url", "label": "Webhook URL", "type": "password", "required": true }] },
      { "type": "discord", "label": "Discord", "fields": [{ "key": "webhook_url", "label": "Webhook URL", "type": "password", "required": true }] }
    ],
    "events": [
      { "event": "monitor.down", "label": "Monitor Down" },
      { "event": "monitor.up", "label": "Monitor Up" },
      { "event": "monitor.degraded", "label": "Monitor Degraded" },
      { "event": "monitor.ip_mismatch", "label": "Exit IP Mismatch" },
      { "event": "incident.created", "label": "Incident Created" },
      { "event": "incident.resolved", "label": "Incident Resolved" },
      { "event": "incident.correlated", "label": "Incidents Correlated" }
    ]
  }
}
GET/integrations
List Integrations

Notes
  • •Sensitive config fields (secrets, tokens, webhook URLs) are always masked in this response — the plaintext only ever appears once, from Rotate Secret.
Example Request
curl -X GET "https://api.exitprobe.com/api/v1/integrations" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
Example Response
{
  "success": true,
  "data": {
    "integrations": [
      {
        "id": 1, "project_id": null, "type": "webhook", "name": "My Webhook", "is_active": true,
        "events": ["monitor.down", "monitor.up"],
        "config": { "url": "https://example.com/…/1a2b", "secret": "whsec_…d4f9" },
        "last_triggered_at": "2026-09-26T00:00:00Z", "last_status": "success", "last_error": null,
        "failure_count": 0, "deliveries_count": 12
      }
    ],
    "usage": { "used": 1, "limit": 3, "remaining": 2, "percentage": 33, "is_unlimited": false, "is_exhausted": false }
  }
}
POST/integrations
Create Integration

project_id null makes this an account-wide integration (also required for VPS events, since a VPS has no project).

Request Body
type*stringwebhook, email, telegram, slack, or discord.
name*stringMax 255 chars.
project_idintegerOmit for account-wide.
is_activebooleanDefault true.
events*array<string>At least 1 — see Webhooks & Events for the full catalogue.
config*objectFields depend on type — see Integration Types & Events above.
Notes
  • •422 if your plan doesn't include integrations, or you're at your plan's integration limit.
  • •A `url`/`webhook_url` config value must be a public address — internal/loopback/link-local URLs are rejected (SSRF guard).
Example Request
curl -X POST /api/v1/integrations \
  -H "Authorization: Bearer 1|K7f9XyZaQ2mN8pL4vR6tW1sJ0hD3cB5gA7eF9uI2oY4" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "webhook",
    "name": "My Webhook",
    "events": ["monitor.down", "monitor.up", "incident.created"],
    "config": {"url": "https://example.com/hooks/exitprobe"}
  }'
Example Response
{ "success": true, "message": "Integration created successfully.", "data": { "integration": { "id": 1, "type": "webhook", "name": "My Webhook", "events": ["monitor.down", "monitor.up", "incident.created"] } } }
GET/integrations/{id}
Get Integration

Example Request
curl -X GET "https://api.exitprobe.com/api/v1/integrations/{id}" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
PUT/integrations/{id}
Update Integration

All fields optional (partial update). Echoing back a masked secret unchanged (e.g. resubmitting the exact masked value shown by List) keeps the real stored value rather than overwriting it with the mask string.

Example Request
curl -X PUT "https://api.exitprobe.com/api/v1/integrations/{id}" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
DELETE/integrations/{id}
Delete Integration

Example Request
curl -X DELETE "https://api.exitprobe.com/api/v1/integrations/{id}" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
POST/integrations/{id}/test
Send Test Notification

Delivers a synthetic sample event (a fake "Sample Server (test)" monitor going down) through this integration, so you can confirm it's wired up correctly.

Example Request
curl -X POST "https://api.exitprobe.com/api/v1/integrations/{id}/test" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
Example Response
{ "success": true, "message": "Test notification delivered successfully.", "data": { "delivery": { "id": 999, "status": "success", "response_code": 200, "duration_ms": 184 } } }
POST/integrations/{id}/rotate-secret
Rotate Signing Secret

Webhook integrations only. Generates a new HMAC signing secret. The previous secret keeps working for 24 hours (both are used to sign deliveries during that window) so you have time to update your endpoint.

Notes
  • •422 "Secret rotation only applies to webhook integrations." for any other type.
  • •The plaintext secret is returned exactly once, here.
Example Request
curl -X POST "https://api.exitprobe.com/api/v1/integrations/{id}/rotate-secret" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
Example Response
{
  "success": true,
  "message": "Secret rotated. The previous secret stays valid for 24 hours so you can update your endpoint.",
  "data": { "secret": "whsec_9f8e7d6c5b4a3210...", "secondary_secret_expires_at": "2026-09-27T00:00:00Z" }
}
GET/integrations/deliveries
Delivery History

Every attempted notification across all your integrations (or filtered to one).

Query Parameters
per_pageintegerDefault 50, max 200.
integration_idintegerFilter to one integration.
Example Request
curl -X GET "https://api.exitprobe.com/api/v1/integrations/deliveries" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
Example Response
{
  "success": true,
  "data": {
    "deliveries": [
      { "id": 999, "integration_id": 1, "integration_name": "My Webhook", "integration_type": "webhook", "event": "monitor.down", "monitor_id": 42, "monitor_name": "US-East WG1", "status": "success", "response_code": 200, "error": null, "duration_ms": 184, "created_at": "2026-09-26T00:00:00Z" }
    ]
  }
}