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.
{
"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.
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 eventThe 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.