/webhooks endpoints with the webhooks:read and webhooks:manage scopes. The portal shows the same sample deliveries as this page: use the info button next to an event when creating or editing a webhook to view and copy its sample payload.
Event Types
Routing models
Subscription routing
Subscription routing
The webhook lists the event type in its
eventTypes array. Every active webhook in scope receives the event.Scope resolution covers the incident’s own account plus any managing service provider accounts. A service provider webhook with a non-empty monitoredAccountIds list only receives events from the accounts it lists; an empty list means all managed accounts.Externally configured routing
Externally configured routing
Subscribing alone does not opt a webhook in. An owning entity has to name the webhook explicitly. Today this applies only to
agent.investigation.completed.v1, where an Agent Configuration’s classification card selects the target webhook.The webhook must also keep agent.investigation.completed.v1 in its eventTypes. If it is unsubscribed while still bound to a classification card, the delivery is recorded as Failed in the delivery log rather than dropped silently.Manual routing
Manual routing
webhook.test.v1 is only produced by the Send test action against one specific webhook. It is not a subscribable event.The incident Trigger webhook action is also manual, but it reuses incident.created.v1 so that existing receivers process it without code changes. Subscription matching is bypassed for that fire, though monitoredAccountIds is still enforced.The Delivery Envelope
Every event, regardless of type, is delivered as the same outer envelope. The event-specific schema lives underdata.
All payload property names are camelCase.
Request Headers
Verifying the Signature
The signature covers the timestamp and the exact raw request body: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.
webhook.test.v1
A synthetic connectivity check, sent only by the Send test action on a single webhook. It carries X-CF-Test: true and isTest: true, and is signed exactly like a live event, so you can validate your verification code end to end before any real traffic arrives.
The payload mirrors the incident.created.v1 shape with fixed sample values. Both vendor blocks are populated in the test event so you can build against the full shape; a real incident carries whichever blocks apply to it.
Test deliveries are excluded from the auto-disable circuit breaker, so a failing test never takes a live webhook offline.
incident.created.v1
Fires when a new incident is ingested into a workspace the webhook monitors. The same event type is emitted by the incident Trigger webhook action so that receivers do not need a separate code path for a manually replayed incident.
Fields
Null fields are omitted from this payload rather than serialized as
null. Treat a missing owner or lastActivityAt as absent.Vendor identifiers
incident.created.v1 carries an vendorIdentifiers object holding the identifiers of every vendor system known to hold a record of the incident. It exists because incidentId alone is not always enough to act: addressing a Microsoft Sentinel incident over ARM needs the full resource id, and one real-world incident can exist in both Defender XDR and Sentinel under different ids.
The same object rides on incident.created.v1, incident.closed.v1, incident.status.changed.v1, incident.assignee.changed.v1 and incident.comment.added.v1, and is exposed on the incident representation in the REST API, so a receiver resolves an incident the same way whichever surface it came from.
Which blocks are present
Every block and every field inside it is conditional. Treat an absent block as “not resolvable for this incident”, not as an error.This object is additive. Existing fields, including the top-level
incidentId, are unchanged and continue to carry the identifier of the source the incident was ingested from.incident.closed.v1
Fires when an incident reaches Closed, whichever route got it there: the portal, a bulk close, the AI agent, or the public API. Closures performed directly in the vendor’s own console are also picked up by reconciliation and delivered here, distinguished by closedBy.origin.
Fields
incident.status.changed.v1
Fires on every status transition an incident goes through in ContraForce: the portal, a bulk update, the AI agent, or the public API. Closures picked up from the vendor’s own console by reconciliation are delivered too, with changedBy.origin set to upstream.
Fields
OnHold and WaitingOnCustomer are ContraForce statuses. They pause the SLA clocks and never propagate to the vendor, so a receiver mirroring status into a PSA should map them to its own waiting states rather than expect to find them in Sentinel or Defender.incident.assignee.changed.v1
Fires when an incident is assigned, reassigned or unassigned through ContraForce, whether by an analyst, a bulk update, or a Security Delivery Agent assigning itself. Ownership changes made directly in the vendor’s console are not detected.
Fields
incident.comment.added.v1
Fires when a comment is added to an incident through ContraForce by an analyst, a Security Delivery Agent, or a Gamebook. Comments written directly in the vendor’s console are not detected, and the closing comment entered on a close travels on incident.closed.v1 rather than here.
Fields
gamebook.run.v1
Fires when a Gamebook is dispatched to the executor against an incident, whether an analyst started it from the portal or public API, or the AI agent did.
Gamebooks awaiting approval do not fire this event while queued. The event is emitted when the run is actually dispatched.
Fields
agent.investigation.completed.v1
Fires once per investigation, when a Security Delivery Agent completes and reaches a classification whose card has a webhook custom action configured.
This event is configured per classification on an Agent Configuration card, not as a broadcast subscription. Set it up under Configuring Security Delivery Agents by enabling Advanced mode and choosing a webhook as the custom action for a classification. Full walkthrough and troubleshooting: Agent Investigation Completed Webhook.
Fields
Malicious score
verdict.maliciousScore reports how malicious the agent judged the incident to be, on a 0-100 scale. It lets you branch on degree rather than only on the four-value classificationBucket, for example auto-closing below a threshold while escalating above one.
The bands above match how the portal colors the score. They are guidance for reading a value, not thresholds the platform enforces.
maliciousScore is null when the investigation came through the legacy agent path, which does not collect a score. The key is always present on this payload, so check for null rather than for absence.
Delivery Rules
These apply to every event type.Retries
Delivery is at-least-once. A failed attempt is retried with a fixed backoff, up to 10 attempts within a 5 minute window from the first attempt. Whichever limit is hit first ends the retry budget and the event is dead-lettered.
Retries are triggered by HTTP
408, 429, 500, 502, 503, and 504, and by connection or timeout errors. Any other non-success status is treated as a permanent failure and is not retried.
A Retry-After header is honored when it is longer than the scheduled backoff, clamped to the 5 minute retry window.
Event IDs
incident.created.v1, incident.closed.v1, gamebook.run.v1 and incident.comment.added.v1 derive a deterministic event ID from the underlying subject (the comment, for comment events), so a republished event produces the same ID and can be deduplicated.
incident.status.changed.v1 and incident.assignee.changed.v1 get a fresh ID per transition. The same transition can happen more than once on one incident (pause, resume, pause again), and each occurrence is a distinct event that a subject-derived ID would wrongly collapse.
Test events and manually triggered incident fires get a fresh ID per fire, which is what distinguishes a manual replay from the original automatic event.
Auto-disable
If 5 or more distinct incidents fail final delivery to the same webhook within a rolling 24 hours, the webhook is automatically disabled and stops receiving events. Test deliveries never count toward this threshold. Re-enable it withPOST /webhooks/{id}/enable or from the webhook detail page in the portal, after fixing the endpoint.
Delivery logs
Every delivery attempt is recorded as a delivery-log entry on the webhook. Entries are listed under Settings → Developer Integrations in the portal and throughGET /webhooks/{id}/delivery-logs.
A delivery-log entry is delivery metadata, not a copy of the event. Incident content is processed in memory while the event is built and sent, and none of it is written to the log.
Entries are kept for a bounded period and expire automatically; they are not retained for the life of the account.
Opening an entry in the portal, or calling
GET /webhooks/{id}/delivery-logs/{logId}, shows the incident as it stands now, fetched live from the source system. It is not a snapshot of what was sent. When the incident no longer exists, the entry shows its delivery metadata and no body. See the delivery log model for the response shape.
Entries written before 2026-08-27 may still carry a stored body and response snippet until they expire or are purged.
Redelivery
AFailed entry can be sent again with POST /webhooks/{id}/delivery-logs/{logId}/redeliver?createdAt= or from the entry’s details in the portal. Because the body is not stored, redelivery rebuilds it from the source incident at the moment you redeliver.
- The envelope keeps the original
X-CF-Event-IdandoccurredAt, so deduplication treats it as the same event. datareflects the incident’s current state. Status, owner, severity, and alerts can differ from what the original attempt carried.- Only
incident.created.v1can be rebuilt.incident.closed.v1,gamebook.run.v1, andagent.investigation.completed.v1cannot be redelivered.
422 with code VALIDATION_ERROR, or a 409 with code CONFLICT. The detail field says why:
Requirements for your endpoint
- Must be reachable over HTTPS. Plain HTTP, localhost, and private network addresses are rejected at configuration time.
- Must respond within 30 seconds. Acknowledge with a 2xx immediately and do the real work asynchronously.
- Response bodies are read up to 1 MB and then discarded. The delivery log records the HTTP status code and latency, never the body.
- Should default-ignore unknown values of
X-CF-Schemaso new event types do not break your handler.
Subscribing
Set the event types when you create or update a webhook. At least one is required.webhook.test.v1 is not subscribable. agent.investigation.completed.v1 must be listed here and selected on an Agent Configuration classification card before it delivers anything.
Delivery outcomes for every event are visible per webhook under Settings → Developer Integrations, and through GET /webhooks/{id}/delivery-logs.
Questions about webhook events? Contact us at support@contraforce.com.