Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.contraforce.com/llms.txt

Use this file to discover all available pages before exploring further.

When a Security Delivery Agent finishes investigating an incident, ContraForce can send a signed webhook to an endpoint you control. Use it to escalate true-positive incidents into your SIEM, ticketing, or on-call tooling at the moment the agent reaches a verdict.
This event is configured per classification on an Agent Configuration card, not as a broadcast subscription. It is delivered only to the webhook a classification card points to. Set it up under Configuring Security Delivery Agents by enabling Advanced mode and choosing a webhook as the custom action for a classification.

When It Fires

The event fires once per investigation, when the agent completes and reaches a classification, for any classification whose policy has a webhook custom action configured. The event type (schema) is:
agent.investigation.completed.v1

Request Headers

Every delivery includes these headers. Use them to verify authenticity before trusting the body.
HeaderValueNotes
X-CF-Schemaagent.investigation.completed.v1The event type.
X-CF-Event-IdA unique event identifier (GUID)Use it to deduplicate retries.
X-CF-TimestampISO 8601 timestampReject if it is more than 5 minutes from your clock.
X-CF-SignatureBase64-encoded HMAC-SHA256 signatureSee verification below.
X-CF-TesttruePresent only for test deliveries.
AuthorizationBearer <token> or Basic <base64>Present only if you configured authentication on the webhook.
Retries reuse the same X-CF-Event-Id. Treat delivery as at-least-once and make your handler idempotent.

Verifying the Signature

The signature covers the timestamp and the exact raw request body:
signature = Base64( HMAC_SHA256( signing_key, X-CF-Timestamp + "." + raw_body ) )
The signing_key is the secret shown once when the webhook was created, unless you supplied your own signing token override when setting bearer-token credentials, in which case it is that token.
1

Read the raw body

Compute the signature over the unparsed request body bytes, before any JSON deserialization.
2

Recompute

Concatenate the X-CF-Timestamp value, a literal ., and the raw body. HMAC-SHA256 it with your signing key and Base64-encode the result.
3

Compare in constant time

Compare your value to X-CF-Signature using a constant-time comparison. Reject on mismatch.
4

Check the timestamp

Reject the request if X-CF-Timestamp is more than 5 minutes from current time, to limit replay.
import base64, hashlib, hmac, time
from datetime import datetime, timezone

def verify(signing_key: str, headers: dict, raw_body: bytes) -> bool:
    ts = headers["X-CF-Timestamp"]
    sent = headers["X-CF-Signature"]
    signed = ts.encode() + b"." + raw_body
    expected = base64.b64encode(
        hmac.new(signing_key.encode(), signed, hashlib.sha256).digest()
    ).decode()
    if not hmac.compare_digest(expected, sent):
        return False
    age = abs(time.time() - datetime.fromisoformat(ts).replace(tzinfo=timezone.utc).timestamp())
    return age <= 300

Payload

The body is JSON with camelCase fields:
{
  "workspace": {
    "id": "00000000-0000-0000-0000-000000000000",
    "alias": "contoso",
    "name": "Contoso Production"
  },
  "agent": {
    "id": "agent-identifier",
    "name": "Tier 1 Triage Agent"
  },
  "incident": {
    "id": "source-system-incident-id",
    "number": 4242,
    "source": "Sentinel",
    "title": "Suspicious sign-in from impossible travel",
    "severity": "High",
    "status": "Active"
  },
  "verdict": {
    "classificationBucket": "TruePositive",
    "classificationReason": "MaliciousActivity",
    "classificationReasonComment": "Confirmed credential theft",
    "comment": "Sign-in originated from a known-malicious ASN minutes after a login from the user's usual location."
  },
  "gamebookRecommendation": {
    "incidentNumber": 4242,
    "incidentTitle": "Suspicious sign-in from impossible travel",
    "playbooks": [
      {
        "playbookId": "disable-user",
        "affectedEntity": "Account",
        "entityId": "jdoe@contoso.com",
        "sequence": 1
      }
    ]
  }
}

Fields

FieldTypeDescription
workspace.id / alias / namestringThe ContraForce workspace the incident belongs to.
agent.id / namestringThe Security Delivery Agent that ran the investigation.
incident.idstringThe source system’s incident identifier.
incident.numbernumberThe incident number shown in the portal.
incident.sourcestringDetection source, for example Sentinel.
incident.titlestringIncident title.
incident.severitystringIncident severity, for example High.
incident.statusstringIncident status at completion.
verdict.classificationBucketstringThe agent’s verdict. One of TruePositive, BenignPositive, FalsePositive, Undetermined.
verdict.classificationReasonstring | nullReason code for the classification.
verdict.classificationReasonCommentstring | nullFree-text reason detail.
verdict.commentstringThe agent’s investigation summary comment.
gamebookRecommendationobject | nullPresent only when the agent recommended gamebooks.
gamebookRecommendation.playbooks[]arrayRecommended gamebooks, each with playbookId, affectedEntity, entityId, and sequence.
Branch on verdict.classificationBucket. It is a stable, vendor-neutral value, so your integration does not need to handle source-specific classification strings.

Testing

Use the Send test action on the webhook in Developers to deliver a synthetic event. Test deliveries carry X-CF-Test: true and use sample data. They are signed identically to live events, so you can validate your verification code end to end.

Troubleshooting

SymptomLikely causeResolution
No events arriveThe classification card does not point to this webhook, or Advanced mode is offConfirm the webhook is set as the custom action for the classification and that Advanced mode is enabled and saved
Events stop arrivingThe webhook was deleted, paused, disabled, or unsubscribedThe classification card shows a binding warning. Fix the webhook in Developers. Skipped deliveries are recorded as Failed in the delivery log, not dropped silently
Signature check failsVerifying a parsed body instead of the raw bytes, or using the wrong keySign the raw request body, and use the signing token override if one was set
Duplicate eventsNormal retry behaviorDeduplicate on X-CF-Event-Id
Questions about the agent investigation webhook? Contact us at support@contraforce.com.