Skip to content

Manual webhook ​

Tenant manual webhooks let customers POST arbitrary JSON when no vendor adapter exists — internal runbooks, glue scripts, or custom monitoring. Each tenant gets a unique HTTPS URL and HMAC signing secret.

See Sources for how manual webhooks fit with the other incoming webhooks. Each named endpoint occupies one primary or supporting incoming slot. Trial and Starter include 1 named endpoint; Growth allows 5; Business allows 15; Enterprise is unlimited. Each endpoint has its own URL token, signing secret, field-path map, and Pulse source role.

On the hosted app, use the URL Connections copies. It starts with https://exhaleoncall.com/webhooks/. The customer host does not publish /v1/webhooks/....


Endpoint ​

After Connect in Connections → Sources → Custom ingest → Manual webhook:

Preferred (keeps tokens out of proxy access logs):

POST https://exhaleoncall.com/webhooks/manual

Send header X-Exhale-Webhook-Token: {token}.

Legacy path form (still supported):

POST https://exhaleoncall.com/webhooks/manual/{token}
  • token — opaque URL segment (not a secret; the signing secret authenticates deliveries).
  • Response: 202 Accepted with pulse_id, status, created, and message.

Unknown tokens return 404. Disabled or expired-trial tenants return 403.


Customer setup ​

  1. Sign in as admin → Connections → Sources → Custom ingest → Manual webhook.
  2. Click Connect — the signing secret is shown once; copy it to your sender.
  3. Copy the webhook URL from the card. The card matches Edit settings: named-endpoint dropdown above a bordered block with the endpoint name heading, then Webhook URL (POST). Use View docs for the JSON envelope and curl examples (external_id required).
  4. Optional: Edit settings opens a scrollable dialog (wider than other Edit settings dialogs; Pulse source role is the first row in the well — bordered group with the label above the dropdown and role copy to its right; Disconnect is on the footer left with Close / Save & close on the right, same chrome as other Edit settings). The named-endpoint dropdown sits above a bordered block for the selected endpoint (name heading through collapsed Advanced... field-path mapping, plus {refresh} Rotate signing secret and {trash} Remove endpoint). Remove endpoint confirms in a dialog with a trash icon before the title and before the label; that button uses the same colors as Edit settings Disconnect, at the confirm-button size. A dropdown selects the URL to copy; Add another named endpoint is the last option (disabled at the plan cap). Growth or higher workspaces can add more named URLs until the cap.
  5. Send test alert creates a synthetic pulse and marks the integration configured.

Rotate signing secret replaces the previous secret right away.


Default JSON envelope ​

FieldRequiredDefault pathNotes
external_idYesexternal_idStable id for dedupe (source=other)
titleNotitleDefaults to Manual alert {external_id}
event_typeNoevent_typetriggered (default), acknowledged, or resolved
source_urlNosource_urlDeep link in the UI (https only, no user:pass@ credentials)
delivery_idNodelivery_idReplay dedupe id; may also be sent as header
severityNoseverity (or priority)Normalized onto the pulse (critical / high / medium / low / unknown)
urgencyNourgencyNormalized high / low / unknown
service / service_nameNoservice_name, else serviceDisplay name on list/detail
alert_keyNoalert_keyStored when it differs from external_id

Signed curl example ​

bash
TOKEN="your-url-token"
SECRET="your-signing-secret"
BODY='{"external_id":"MANUAL-1","title":"Disk full on db-01","event_type":"triggered"}'
SIG="v1=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')"

# Preferred — token in header
curl -i -X POST "https://exhaleoncall.com/webhooks/manual" \
  -H "Content-Type: application/json" \
  -H "X-Exhale-Webhook-Token: ${TOKEN}" \
  -H "X-Exhale-Signature: $SIG" \
  -d "$BODY"

# Legacy — token in URL path
curl -i -X POST "https://exhaleoncall.com/webhooks/manual/${TOKEN}" \
  -H "Content-Type: application/json" \
  -H "X-Exhale-Signature: $SIG" \
  -d "$BODY"

Field path mapping ​

When payloads nest fields, admins configure dot-paths in the Integrations UI or via:

  • GET /auth/sources/manual-webhook/field-paths
  • PUT /auth/sources/manual-webhook/field-paths (admin + CSRF)

Example paths:

json
{
  "external_id": "alert.id",
  "title": "alert.summary",
  "event_type": "alert.status",
  "source_url": "alert.url",
  "delivery_id": "meta.delivery_id"
}
Exhale reads each field only from its configured path. Missing required paths return 400.

Signature verification ​

Deliveries must include X-Exhale-Signature: HMAC-SHA256 of the raw body as v1=<hex>. Missing or invalid signatures receive 401.

There is no timestamp in the signature header — use delivery_id (body or X-Exhale-Delivery-Id header) for replay protection.


Dedupe and reliability ​

Same pipeline limits as PagerDuty ingest:

  • Bodies larger than the configured maximum (default 1 MiB) → 413
  • Invalid or missing Content-Length → 400
  • Per-IP rate limits → 429 with Retry-After
  • Unavailable dedupe storage can return 503

Lifecycle mapping ​

event_typePulse statusTriage
triggered (default)receivedEnqueued when async triage is enabled
acknowledgedacknowledgedNo new triage enqueue
resolvedresolvedNo new triage enqueue

Duplicate (tenant_id, source=other, external_id) updates the existing row and returns 202 with created: false.


Scrubbing and logging ​

Parsed JSON is scrubbed before persist (same pipeline as PagerDuty). Application logs record pulse_id, correlation_id, source, and external_id only — never the raw body.

Stored pulses use source=other; the UI labels them Manual webhook.


Auth API (customer) ​

Admin session and CSRF on mutating routes in the app.

RoutePurpose
POST …/connectIssue URL token + signing secret (once)
POST …/rotate-secretNew signing secret (once)
POST …/test-alertSynthetic ingest + mark configured
GET / PUT …/field-pathsRead/update dot-path mapping

GET /auth/sources includes webhook_path for connected manual webhooks; never exposes the signing secret.


Exhale by Kolstrom Systems LLC