Webhook events and verification

Webhook events and verification

Verify signed raw request bytes, reject replay, and process retried events safely.

Webhook receivers verify Guard's signature before parsing or acting on an event. Use Organization → Integrations → Integration → Test delivery to inspect a synthetic event and its delivery result.

Envelope and delivery

Each event has a stable event_id, type, organization ID, occurrence time, and resource reference. A delivery attempt has a separate identity. Repeated attempts retain the original event ID.

JSON
{
  "event_id": "evt_001",
  "type": "detection.created",
  "organization_id": "org_acme",
  "occurred_at": "2025-11-15T10:00:00Z",
  "data": {
    "detection_id": "det_001",
    "asset_id": "ast_web01",
    "severity": "high",
    "rule_revision": 3
  }
}

Subscriptions accept detection.created, incident.updated, action.completed, finding.updated, and coverage.degraded. Resource events identify the changed resource and revision when it has one. Read the current resource through the API before a dependent change; events can arrive out of order.

Webhook requests have a 10-second response timeout. A 2xx response acknowledges delivery. Guard retries transport failures, 408, 429, and 5xx responses, with up to eight attempts within 24 hours. Other 4xx responses end that delivery as failed. Honor Retry-After for 429 within the remaining delivery window. After exhaustion, inspect Integration → Deliveries and request a reviewed replay.

A TLS syslog write confirms transport acceptance, not indexing by Splunk or Elastic. Verify receiver ingestion independently.

Verify a signature

Guard-Timestamp is an ASCII decimal Unix timestamp in seconds. Guard-Signature is a 64-character hexadecimal HMAC-SHA256 digest over the timestamp header, a period, and the exact body bytes. Guard-Event-Id must match the signed body's event_id. Do not normalize whitespace or reserialize JSON before verification.

This Python 3 function accepts the raw body and header strings. Load secret from the receiver's secret manager; it is the integration signing secret, not the Guard management API key. Inject now when testing clock boundaries.

Python
import hashlib
import hmac
import json
import re
import time


def verify_guard_event(body, timestamp, signature, event_id, secret, now=None):
    if not isinstance(body, bytes) or not isinstance(secret, bytes) or not secret:
        return None
    if not isinstance(timestamp, str) or not re.fullmatch(r"[0-9]{1,20}", timestamp):
        return None
    if not isinstance(signature, str) or not re.fullmatch(r"[0-9a-fA-F]{64}", signature):
        return None
    current_time = time.time() if now is None else now
    if abs(current_time - int(timestamp)) > 300:
        return None
    signed_bytes = timestamp.encode("ascii") + b"." + body
    expected = hmac.new(secret, signed_bytes, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, signature.lower()):
        return None
    try:
        event = json.loads(body)
    except (ValueError, UnicodeDecodeError, RecursionError):
        return None
    if not isinstance(event, dict):
        return None
    if not isinstance(event_id, str) or not event_id:
        return None
    if event.get("event_id") != event_id:
        return None
    return event

The function verifies origin and freshness; it does not deduplicate or authorize downstream work. Atomically store the verified event ID with durable queue admission before acknowledging the delivery. Retain deduplication records for at least the 24-hour delivery window and any later replay period you allow. A duplicate already admitted can receive 2xx without repeating side effects.

Use a bounded body size at the receiver and reject unknown organizations or unsubscribed event types before processing. If durable queue admission fails, return a retryable server error. Keep response execution behind the normal approval boundary.

Rotate and recover

During a signing-secret overlap, verify against the current or still-valid prior secret using constant-time comparison. Stop accepting the retired secret after the recorded overlap deadline. Test the replacement before revoking the old version at Integration → Credentials → Rotate.

Record event and delivery IDs in diagnostics, excluding secrets and sensitive payloads. A successful receiver acknowledgement does not establish that an endpoint action ran or that a PagerDuty incident was resolved.