Appearance
Errors & authentication
This page covers structured API errors, correlation ids, and how customers authenticate to Exhale. Webhook ingest uses per-source verification — HMAC for PagerDuty and manual webhooks; URL/header tokens for most other sources. See the Sources index and guides linked below.
A public route catalog and language SDKs ship after launch. Tenant API keys can be issued in the app under Settings → Security & data → API keys.
Structured errors
Non-success responses use a consistent JSON shape:
json
{
"error": {
"code": "NOT_FOUND",
"message": "Pulse … not found",
"correlation_id": "550e8400-e29b-41d4-a716-446655440000"
}
}| Field | Description |
|---|---|
code | Machine-readable error code (e.g. NOT_FOUND, VALIDATION_ERROR, UNAUTHORIZED) |
message | Human-readable detail |
correlation_id | Request id for support and log lookup |
Every response includes an X-Correlation-Id header. Clients may send their own value; otherwise the API generates a UUID. Match this id in support requests.
| HTTP | Typical code | When |
|---|---|---|
| 400 | BAD_REQUEST | Missing required webhook fields, invalid Content-Length |
| 401 | UNAUTHORIZED | Bad or missing signature / session. Login and related auth flows may set a more specific error.code: invalid_credentials (unknown email or wrong password — same response either way), invalid_mfa_code, mfa_challenge_expired, invalid_password, invalid_reset_token |
| 403 | FORBIDDEN | Viewer role, disabled tenant, expired trial. Product flows may set a more specific error.code: trial_expired, subscription_canceled, subscription_past_due, entitlement_limit / entitlement_* |
| 404 | NOT_FOUND | Unknown pulse, unmapped paging account, or unknown webhook token |
| 409 | CONFLICT | Conflicting state (e.g. retry-triage race, billing conflict) |
| 413 | PAYLOAD_TOO_LARGE | Body over size limit (default 1 MiB) |
| 422 | VALIDATION_ERROR | Schema validation failure |
| 429 | RATE_LIMITED | Per-IP webhook or API rate limit (Retry-After). Per-email sign-in lockout uses auth_locked |
| 503 | (varies) | Redis fail-closed, maintenance, ambiguous account mapping |
| 500 | INTERNAL_ERROR | Unexpected server error |
Customer authentication
Session cookie (primary)
Customer UI and /auth routes use the exhale_session HTTP-only cookie after login or signup.
Mutating /auth routes require:
- Valid session
X-CSRF-Tokenheader matching the CSRF cookie (double-submit pattern)OriginorRefererfrom an allowed browser UI origin
MFA (TOTP): Paid tenant owners and admins may be required to enroll in TOTP before full session access when admin MFA is required. Trial signups and optional member/viewer MFA policies are configured under Settings → Account → Sign-in & MFA. Owners set the tenant MFA policy (PATCH /auth/settings key security).
Social login (identity OAuth): When Google and/or GitHub sign-in is enabled, Sign in and Sign up show Continue with Google / Continue with GitHub. Those flows authenticate the person only — they do not connect repositories or APM accounts. Repo and observability context still use Connections → Context. OAuth errors return as ?auth_error= on login/signup. If the IdP email matches an existing Exhale account that does not already have that identity, the callback redirects to
/link-oauth so the owner can confirm with password, an emailed link, or an existing session. MFA after social login uses the same TOTP enrollment/verify path as password login.
Source and notification setup in the app uses the signed-in session and CSRF.
Tenant API keys (customer)
Tenant owners issue keys under Settings → Security & data → API keys. The secret is shown once (exh_… prefix); only a hash is stored. Keys do not authenticate inbound webhooks. A public route catalog for those keys ships with language SDKs after launch.
Webhook authentication
Webhooks do not use session cookies. Each source has its own verification:
| Auth style | Sources | How |
|---|---|---|
| HMAC signature | PagerDuty, Manual webhook | HMAC-SHA256 of the raw body as v1=<hex> (X-PagerDuty-Signature or X-Exhale-Signature). Manual also requires X-Exhale-Webhook-Token (header route) or a path token. |
| URL / header token | JSM Operations (Opsgenie), Splunk On-Call, incident.io, Grafana, Datadog, Alertmanager, New Relic, Sentry, Honeycomb, Elastic, Dynatrace, AWS CloudWatch | Opaque tenant token via header X-Exhale-Webhook-Token (preferred) or a token in the webhook path. No vendor HMAC. |
Health
On the hosted app, the only public health probe is:
GET https://exhaleoncall.com/health/liveIt checks process liveness and does not require authentication. It does not report database, Redis, or queue depth. GET /health on that host returns 404.