Webhooks

Paqet delivers monitoring events to an HTTPS endpoint you control, signed so you can verify they came from us.

Webhooks are an Enterprise feature. Create and manage them in Console → Webhooks — each webhook has a URL, a destination type (Generic, Slack, or Discord), and a set of event types it subscribes to. A generated signing secret is shown once at creation.

How delivery works

  • When a subscribed event fires, Paqet sends an HTTP POST to your URL with a JSON body.
  • Any 2xx response counts as delivered. Anything else — or a timeout — is a failure and is retried.
  • Delivery is per-webhook: if three of your webhooks subscribe to the same event, each gets its own POST and its own retry schedule.
  • Every attempt (request, response, status, duration) is recorded under Console → Webhooks → Deliveries.

Payload envelope

Generic webhooks receive a signed envelope. The event-specific data is nested under Data; the envelope keys are PascalCase.

{
  "Id": "5b6e1e3a-9c7d-4f2a-8b1e-2c3d4e5f6a7b",
  "Type": "incident.created",
  "CreatedAt": "2026-09-01T12:00:00+00:00",
  "Data": {
    "DomainId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "Host": "example.com",
    "CheckType": "HTTP",
    "Status": "Error",
    "Title": "HTTP Error: 503 Service Unavailable",
    "Summary": "The endpoint returned 503 for 3 consecutive checks."
  }
}
Field Type Description
Id string (uuid) Unique delivery ID. Matches the X-Paqet-Delivery header. Use it to de-duplicate retries.
Type string The event's wire name (see Events).
CreatedAt string (ISO 8601) When the event was raised, UTC.
Data object Event-specific payload — see Event payloads.

Headers

Header Description
X-Paqet-Event The event's wire name, e.g. incident.created.
X-Paqet-Delivery The delivery ID (same value as Id in the body). Stable across retries of the same event.
X-Paqet-Signature t=<unix seconds>,v1=<hex> — the signed timestamp and HMAC. See Verifying signatures.
Authorization Present only if you set an auth token on the webhook: Bearer <your token>.

Verifying signatures

Recompute the signature yourself and compare it to v1 — never trust the header without checking it.

X-Paqet-Signature: t=1756728000,v1=a29680b4c1f2...

Formula

v1 = HMAC_SHA256(secret, t + "." + rawRequestBody)   // lowercase hex

Node.js

const crypto = require("crypto");

function verify(secret, rawBody, signatureHeader) {
    const parts = Object.fromEntries(signatureHeader.split(",").map(p => p.split("=")));
    const expected = crypto
        .createHmac("sha256", secret)
        .update(`${parts.t}.${rawBody}`)
        .digest("hex");

    // Reject old timestamps to blunt replay attacks (Paqet uses a 5-minute tolerance).
    if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;

    return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
  • Hash the raw request body, byte-for-byte — not a re-serialized copy.
  • Use a constant-time comparison.
  • Reject deliveries whose t is more than 5 minutes from your clock.

Retries

A failed delivery is retried up to 5 times with a fixed backoff:

attempt 1  →  immediately
attempt 2  →  +30 seconds
attempt 3  →  +2 minutes
attempt 4  →  +10 minutes
attempt 5  →  +30 minutes
attempt 6  →  +2 hours

After that the delivery is abandoned. Make your handler idempotent on X-Paqet-Delivery — a retry can arrive after your endpoint has, in fact, already processed the first attempt. If your Enterprise plan lapses, pending retries stop and the webhook is disabled.

Events

Subscribe to events per-webhook in Console → Webhooks.

Event Fires when Payload
incident.created A monitoring check opens a new incident (DNS failure, certificate-transparency finding, and other non-HTTP check types). Incident
incident.resolved An open incident is cleared — either the check goes healthy again, or someone resolves it in the console. HTTP incidents use monitor.up instead. Incident
monitor.down An HTTP check starts failing for a domain. Incident
monitor.up A failing HTTP check recovers. Incident
ssl.expiring A domain's certificate enters its expiry-warning window (days remaining still > 0). Incident
ssl.expired A domain's certificate has expired. Incident
domain.expiring A domain's registration enters its expiry-warning window, or has lapsed. Incident
maintenance.started A scheduled maintenance window begins for a domain. Maintenance
maintenance.finished A maintenance window ends (or is cancelled). Maintenance
assistant.analysis.completed The Paqet Assistant finishes an analysis with a NeedsAttention or Uncertain verdict. Benign analyses do not fire this event. Assistant
incident.updated Reserved — selectable but not currently delivered. —
account.limit_reached Reserved — selectable but not currently delivered. —

Event payloads

Incident

Sent for incident.created, incident.resolved, monitor.down, monitor.up, ssl.expiring, ssl.expired, and domain.expiring.

{
  "DomainId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "Host": "example.com",
  "CheckType": "HTTP",
  "Status": "Error",
  "Title": "HTTP Error: 503 Service Unavailable",
  "Summary": "The endpoint returned 503 for 3 consecutive checks."
}
Field Type Description
DomainIdstring (uuid)The affected domain. Look it up via GET /v1/domains/{id}.
HoststringThe domain's hostname.
CheckTypestringDNS, SSL, HTTP, CertTransparency, or DomainExpiry.
StatusstringThe check status that triggered the event — Error, Warning, or Healthy (on resolve).
TitlestringHuman-readable incident summary.
Summarystring | nullExtra detail where available.

Maintenance

Sent for maintenance.started and maintenance.finished.

{
  "DomainId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "Host": "example.com",
  "StartsAt": "2026-09-02T01:00:00+00:00",
  "EndsAt": "2026-09-02T03:00:00+00:00",
  "Reason": "Database migration"
}
Field Type Description
DomainIdstring (uuid)The domain the window applies to.
HoststringThe domain's hostname.
StartsAtstring (ISO 8601)Window start, UTC.
EndsAtstring (ISO 8601)Window end, UTC.
Reasonstring | nullOptional operator note.

Assistant analysis

Sent for assistant.analysis.completed.

{
  "AnalysisId": "d4a1c9f0-6b2e-4a1d-9c3f-1e2d3c4b5a60",
  "OrganizationId": "7b2c1a90-3f4e-4d5c-8a1b-2c3d4e5f6a7b",
  "CycleDate": "2026-09-01",
  "TriggerType": "Scheduled",
  "Verdict": "NeedsAttention",
  "Confidence": "High",
  "Severity": "Medium",
  "Headline": "Unexpected certificate issuance for api.example.com",
  "Reasoning": "A certificate for api.example.com was issued by a CA this organization has not used before...",
  "SignalCount": 3,
  "ConsoleUrl": "https://app.paqet.io/console/assistant?open=d4a1c9f0-6b2e-4a1d-9c3f-1e2d3c4b5a60"
}
Field Type Description
AnalysisIdstring (uuid)Fetch the full analysis via GET /v1/assistant/analyses/{id}.
OrganizationIdstring (uuid)The organization the analysis belongs to.
CycleDatestring (date)The daily cycle the analysis covers (YYYY-MM-DD).
TriggerTypestringScheduled (the daily pass) or OnDemand (a re-run).
VerdictstringNeedsAttention or Uncertain — see below.
Confidencestring | nullLow, Medium, or High.
Severitystring | nullInfo, Low, Medium, High, or Critical.
Headlinestring | nullOne-line summary of the finding.
Reasoningstring | nullThe Assistant's full explanation (up to ~4,000 characters).
SignalCountnumberHow many signals the analysis reviewed.
ConsoleUrlstringDeep link that opens this analysis in the console.

Assistant verdicts, confidence & severity

The Assistant reviews monitoring signals and produces three graded fields, used on both the webhook payload and the Assistant API.

  • Verdict — Benign (matches this organization's established patterns; no webhook is sent), NeedsAttention (review this), Uncertain (the Assistant could not confidently rule it benign — treat it as "look, but don't panic", not as a confirmed problem).
  • Confidence — Low / Medium / High: how sure the Assistant is of that verdict.
  • Severity — Info / Low / Medium / High / Critical: how urgent the finding is if it is real.

Slack & Discord

Webhooks with a Slack or Discord destination receive that platform's native message format (Block Kit / embeds) instead of the signed envelope, so they render as a readable message in-channel. The same X-Paqet-* headers are still sent and still signed. If you want the raw JSON envelope, use a Generic destination.

Set up a webhook

Create one, pick its events, and send a test delivery from the console.

Open Webhooks