Skip to content
Connect an agent
Hosted diagrams and plans

Send audit events and drift to your tools

On Enterprise, an organisation can send every event of its audit log, and every drift run its CI reports, to the tools it already runs: Datadog, Dynatrace, Splunk, Microsoft Sentinel, or an HTTPS webhook of its own. Plotwork sends each one as it happens, in batches, and tries again for a day when a tool is down.

TL;DR

QuestionAnswer
Which plan?Enterprise. On another plan the Integrations tab says so, nothing is sent, and a destination saved earlier can still be removed.
Who sets it up?The owner and the admins, signed in, on the organisation's Integrations tab in the portal.
Where can it go?Datadog (logs, and drift as metrics), Dynatrace (logs, and drift as metrics), Splunk (HTTP Event Collector), Microsoft Sentinel (the Azure Monitor Logs Ingestion API), or a webhook signed with HMAC-SHA256. Up to five destinations.
What is sent?Metadata only: the same fields as the audit log's export and the drift dashboard. Never a diagram's source, a key's value or a token.
How fast?About five seconds after an event, in batches of up to 100.
What if my tool is down?Plotwork tries again after 30 seconds, then waits twice as long each time, up to an hour, for a day. After a day the events are dropped and counted.
How are secrets kept?Sealed with AES-256-GCM as soon as they are saved, and never shown again, not even to the owner. Leave the field empty when you edit to keep the saved one.
Can Plotwork reach my private network?No. Plotwork sends only to HTTPS addresses on public names.

1. Add a destination

  1. Open the organisation in the portal and choose the Integrations tab.
  2. Choose the tool, give the destination a name, and choose what it receives: the audit log, the drift results, or both.
  3. Fill in the tool's fields from the section below, then save.
  4. Press Send test event. Plotwork sends one event of type test at once and says whether the tool took it, or why not, in one sentence such as "Datadog answered HTTP 403."

Each destination then shows, in plain sentences, when it last took a batch, its last error, how many events wait to be sent, how many were given up on, and when it tries next.

2. Datadog

Audit events and drift runs go to the Logs intake, https://http-intake.logs.<site>/api/v2/logs. Drift runs also go to the metrics API, https://api.<site>/api/v2/series.

FieldWhat to enter
SiteThe site your organisation signs in to, from the table below
API keyAn API key from Organization Settings, API Keys. Not an application key. It is sent as the DD-API-KEY header.
SiteDomain
US1datadoghq.com
US3us3.datadoghq.com
US5us5.datadoghq.com
EU1datadoghq.eu
AP1ap1.datadoghq.com
US1-FED (gov)ddog-gov.com

Each log has ddsource and service set to plotwork, a plotwork_type tag (audit_event, drift_run or test), a one-line summary as its message, and the whole event under the plotwork attribute.

For each diagram a drift run checked, Plotwork sends two gauges tagged repo:<repository> and source:<name>:

MetricValue
plotwork.drift.stale1 when the diagram drifted from its files or could not be made, 0 when it is in step or was written
plotwork.drift.checked1

A point carries the time it is sent, because Datadog refuses points more than an hour old and a retried batch can be older.

3. Dynatrace

Audit events and drift runs go to Log Monitoring API v2, https://<environment id>.live.dynatrace.com/api/v2/logs/ingest. Drift runs also go to Metrics API v2, /api/v2/metrics/ingest, as line protocol.

FieldWhat to enter
Environment idThe first part of your environment's address, such as abc12345 in abc12345.live.dynatrace.com
Access tokenA token with the Ingest logs (logs.ingest) and Ingest metrics (metrics.ingest) scopes. It is sent as Authorization: Api-Token <token>.

Each log record has the event as JSON in content, its time in timestamp, log.source set to plotwork, and the attributes plotwork.type, plotwork.org_id, and plotwork.action or plotwork.repository. The drift metrics are plotwork.drift.stale and plotwork.drift.checked, gauges with the dimensions repo and source, as for Datadog, stamped by Dynatrace when they arrive.

4. Splunk

Events go to the HTTP Event Collector.

FieldWhat to enter
AddressThe collector's event endpoint, such as https://http-inputs-<stack>.splunkcloud.com/services/collector/event on Splunk Cloud, or https://splunk.example.com:8088/services/collector/event on a server you run. HTTPS on port 443 or 8088.
IndexThe index to write to, or empty for the token's default
TokenAn HTTP Event Collector token. It is sent as Authorization: Splunk <token>. Turn indexer acknowledgement off for this token.

Each event has host set to plotwork.dev, source set to plotwork, a sourcetype of plotwork:audit_event, plotwork:drift_run or plotwork:test, its time in seconds, and the whole event as event.

5. Microsoft Sentinel

Events go to a Log Analytics workspace through the Azure Monitor Logs Ingestion API, with a token Plotwork asks Microsoft Entra ID for with the client credentials you give. Plotwork keeps the token in memory until five minutes before it expires, and never stores it.

Set it up in Azure once:

  1. In Microsoft Entra ID, register an application and add a client secret. Note the tenant id, the application (client) id and the secret's value.
  2. In the workspace that Sentinel uses, create a custom table from a data collection rule (Tables, Create, New custom log (DCR-based)), named for example PlotworkAudit_CL, with the columns below. Azure creates the rule and its stream, Custom-PlotworkAudit_CL.
  3. Give the application the Monitoring Metrics Publisher role on the data collection rule.
  4. From the rule's JSON view, copy its immutableId. From its data collection endpoint (or the rule's own logs ingestion endpoint), copy the logs ingestion address.
ColumnTypeHolds
TimeGenerateddatetimeWhen the event happened
RecordTypestringaudit_event, drift_run or test
OrgIdstringThe organisation's id
SummarystringA one-line summary, such as orgs.member.role by ada@acme.com on dev@acme.com
DatadynamicThe whole event
FieldWhat to enter
TenantThe tenant id or its primary domain
Client idThe application (client) id
Client secretThe secret's value
EndpointThe logs ingestion address, on *.ingest.monitor.azure.com
Rule idThe rule's immutable id, dcr- and 32 hexadecimal characters
StreamThe stream, such as Custom-PlotworkAudit_CL

6. A webhook

Plotwork posts each batch to your HTTPS address as a JSON array of events.

HeaderValue
Content-Typeapplication/json
Plotwork-Signaturet=<unix seconds>,v1=<hex>: HMAC-SHA256 under your signing secret of the timestamp, a full stop, and the raw body
Plotwork-DeliveryThe id of the batch's first event, so a retry of the same batch carries the same value
User-AgentPlotwork-Delivery/1 and a link to this page
FieldWhat to enter
AddressYour endpoint. HTTPS on port 443, on a public name.
Signing secretAt least 32 characters. Generate in the portal fills in a random one; copy it to your receiver before you save, because it is not shown again.

Answer with a 2xx status within 10 seconds. Any other status, a redirect, or no answer is a failure, and the batch is sent again later. Check the signature over the body exactly as it arrived, before parsing it, and refuse a timestamp more than five minutes from your clock, so a captured request cannot be replayed. An event can arrive twice, so use its id to skip one you have seen.

In Node:

js
import { createHmac, timingSafeEqual } from 'node:crypto'
export function verifyPlotworkSignature(secret, header, rawBody, toleranceSeconds = 300) {  const parts = Object.fromEntries(header.split(',').map((part) => part.trim().split('=', 2)))  const timestamp = Number(parts.t)  if (!Number.isInteger(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false  const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')  const given = String(parts.v1 ?? '')  return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected))}

In Python:

python
import hashlibimport hmacimport time

def verify_plotwork_signature(secret: str, header: str, raw_body: bytes, tolerance_seconds: int = 300) -> bool:    parts = dict(part.strip().split("=", 1) for part in header.split(",") if "=" in part)    try:        timestamp = int(parts.get("t", ""))    except ValueError:        return False    if abs(time.time() - timestamp) > tolerance_seconds:        return False    expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest()    return hmac.compare_digest(expected, parts.get("v1", ""))

7. What is sent

Every event has a type, a version (1), an id, the organisation's orgId, and at, an ISO 8601 time in UTC.

TypeFields
audit_eventThe fields of plotwork.audit-event.json (opens in a new tab), the shape the audit log's export uses: action, actor (kind, id, name), target (type, id, name), metadata, ip (the network only), userAgent and requestId. The audit log guide lists every action.
drift_runWhat the GitHub Action reported: repository, ref, sha, event, runUrl, pullRequestUrl; counts with sources, inStep, drifted, written, failed and errors; and sources, each with name, out, status, added, removed, changed, errors and owners. The drift dashboard guide explains each.
testmessage, from Send test event

Nothing else is sent: never a diagram's source, a file's content, the names of what changed, a key's value or a token.

8. How delivery works

QuestionAnswer
When is an event sent?About five seconds after the first event of a burst, so a burst goes as one batch.
How big is a batch?Up to 100 events and 512 KB. Up to 10 batches go out at a time, then the rest a second later.
In what order?Oldest first, for each destination.
What counts as a failure?Any answer that is not 2xx, a redirect, no answer within 10 seconds, or an address that is not public HTTPS. The events stay in the queue.
How often is a failure tried again?After 30 seconds, then 1, 2, 4 and 8 minutes and so on, up to once an hour, each a fifth earlier or later at random.
When does Plotwork give up?An event still unsent a day after it happened is dropped and counted as given up on. So is the oldest event when 10,000 wait for one destination.
Can an event arrive twice?Yes. When a tool takes a batch but its answer is lost, the batch is sent again. Use each event's id.
What does the status say when something fails?One sentence of Plotwork's own, such as "Splunk answered HTTP 401." or "The webhook did not answer within 10 seconds." Never what your tool answered.
What happens when I remove a destination?Its sealed secret is deleted with it, and so is everything waiting for it.
What does "Paused by Plotwork support" mean?Plotwork support stopped sending to the destination, usually because it kept failing. Events keep waiting for it and are given up on after a day, as when it fails. status.pausedAt says when. Ask support why, or to resume it; nothing you set up is changed.
What can Plotwork support see?Each destination's kind, name, host and delivery status, to answer a question about it. Never its secret, its full address or the events.

9. The API

The Integrations tab uses these routes. They take the portal's session, the owner's or an admin's, and are not in the OpenAPI reference.

RouteDoes
GET /orgs/{org}/integrationsLists the destinations: the fields that are not secret, secretSet in place of the secret, and each one's status, with available saying whether the plan sends
POST /orgs/{org}/integrationsAdds one. The body has type, name, streams (audit, drift, or both), the type's fields and its secret field (apiKey, token, clientSecret or signingSecret).
PUT /orgs/{org}/integrations/{id}Changes one. The type cannot change; leave the secret out to keep the saved one.
DELETE /orgs/{org}/integrations/{id}Removes one and what waits for it
POST /orgs/{org}/integrations/{id}/testSends a test event now and answers { "ok": true } or { "ok": false, "error": "<one sentence>" }
Error codeStatusMeans
integrations.invalid400A field is wrong. details.reason is address (not a public HTTPS address on an allowed port), secret_required, field or type_change, and details.field names the field.
plan.siem_delivery_not_included403The organisation's plan does not send to its tools
orgs.forbidden403Only the owner and the admins can set it up
integrations.limit409The organisation has five destinations already
integrations.unavailable503Delivery is not set up on this server

Adding, changing and removing a destination is recorded in the audit log as orgs.integration.create, orgs.integration.update and orgs.integration.delete, without the secret.

See also

PageWhy
Audit logEvery action, the export, and polling it with a key
The drift dashboardWhat a drift run reports
Single sign-onThe sign-in events the log records
Plans and billingWhat Enterprise includes
Privacy policyWhat is stored to send, and for how long