Skip to main content

Webhooks

Workshop can POST events to an HTTPS endpoint of your choosing as they happen, letting you forward Workshop activity into your own systems — SIEMs, ticketing, chat, or custom automation. Each delivery is signed following the Standard Webhooks specification so your receiver can verify it genuinely came from Workshop.

Event sources

There are three independent webhook sources. Each has its own destination URL, signing secret, and delivery filters, so you can send different event types to different endpoints (or the same one).

SourceFires whenFilter
Audit eventsAny change is made to Workshop (rules, settings, tags, etc.)Event type
Signal reportsA detection signal report is first received, and again on each triage state changeReport state
Software approvalsA piece of software is approved through an approval workflow for the first timeNone

Audit events

Fires once for every audit event — the same record of every change made to Workshop that appears in the audit log, whether the change was made through the UI or the API. See the Audit documentation for the full list of audit event types.

By default every audit event is delivered. You can narrow delivery to specific event types; leave the filter empty to deliver all of them.

Signal reports

Fires when a detection signal report is first received from a host (state NEW) and again each time a report's triage state changes (for example when it moves to ACKNOWLEDGED or REMEDIATED).

The state filter applies to both cases — receipt counts as the NEW state. Leave it empty to deliver for every state. The available states are NEW, ACKNOWLEDGED, INVESTIGATING, REMEDIATED, and DISMISSED.

Software approvals

Fires once, the first time a given piece of software (a binary or a bundle) is approved through any approval workflow — self-service, designated approver, or social voting. Subsequent approvals of the same software do not fire. See the Approval Workflows documentation for how approvals work.

This source has no filter. The payload's requesting_user field identifies who requested the software for self-service and designated-approver workflows; it is empty for social voting, which has no single requester.

Configuring webhooks

Navigate to Settings → Webhooks. Each source is configured in its own section with the following fields:

  1. Enable toggle — turns delivery on or off. Disabling a source stops delivery but keeps its URL and secret so you can re-enable it later without re-entering them.
  2. Destination URL — the HTTPS endpoint that receives deliveries.
  3. Signing secret — the key used to sign every delivery (see Verifying signatures). It must be at least 24 bytes.
  4. Filter — event types (audit events) or states (signal reports), where applicable.
  5. Custom headers (audit events) — additional HTTP headers sent with every delivery, e.g. an Authorization header your receiver expects.

Click Save Changes to apply. Saving requires the write:settings permission.

note

The signing secret is write-only. Workshop never displays or returns it again after you save it. When editing an existing configuration, leave the secret field blank to keep the current secret — only enter a value when you want to replace it. A source cannot be enabled without a secret, since deliveries can't be signed without one.

Delivery format

Each delivery is an HTTP POST with a Content-Type of application/json. The body is a JSON object with exactly one field set, identifying the source:

FieldSource
audit_eventAudit events
signal_reportSignal reports
software_approvalSoftware approvals

Payloads are serialized from Workshop's protobuf definitions, so:

  • Field names are snake_case and enum values are their string names (not numbers).
  • Every field is emitted even when empty — an unset string is "", a number is 0, a boolean is false, an unset nested object is null, and an empty list is []. Don't assume a missing field; assume an empty one.

The examples below are trimmed to the fields worth highlighting; a real delivery includes the remaining fields at their empty values as described above.

{
"audit_event": {
"id": "0f9c2e6a-1d3b-4a7e-9c2f-8b1a5e6d7c40",
"transaction_id": "3a1b8f22-6c4d-4e19-8f2a-1b7c9d0e5a63",
"timestamp": "2026-07-20T15:04:05Z",
"actor": "user:rah@northpole.security",
"event": "AUDIT_EVENT_RULE_UPSERT",
"resource": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
"outcome": "OUTCOME_SUCCESS",
"details": "{\"policy\":\"ALLOWLIST\",\"rule_type\":\"BINARY\"}",
"previous_value": "",
"ai_chat_conversation_id": "",
"via_mcp": false
}
}

Request headers

Every delivery includes the Standard Webhooks headers plus a Workshop User-Agent:

HeaderDescription
webhook-idUnique identifier for the delivery (see below). Use it to deduplicate.
webhook-timestampDelivery time as a Unix timestamp in seconds.
webhook-signatureThe signature over the payload (see Verifying signatures).
Content-TypeAlways application/json.
User-AgentWorkshop/<version> (+https://northpole.security)

Any custom headers you configure are added on top of these.

The webhook-id is namespaced by source so you can tell deliveries apart:

Sourcewebhook-id format
Audit eventsaudit:<event-id>
Signal reportssignal:<uuid>
Software approvalssoftware-approval:<uuid>

The same webhook-id is reused across retries of a delivery, so it's safe to deduplicate on it.

Verifying signatures

Workshop signs every delivery using the Standard Webhooks scheme. The signature is an HMAC-SHA256 over the string {webhook-id}.{webhook-timestamp}.{body}, base64-encoded, and sent in the webhook-signature header as v1,<signature>.

The simplest way to verify is with one of the Standard Webhooks libraries, which handle the signature construction and comparison for you — construct a verifier with your configured secret and pass it the raw request body and headers. Note that the standard verification also enforces a 5-minute timestamp tolerance to guard against replay, so your receiver's clock should be reasonably in sync.

note

The signing secret is the HMAC key. Workshop follows the Standard Webhooks convention: if you enter a base64-encoded value, its decoded bytes are used as the key; if you enter a plain string, the string's raw bytes are used. Standard Webhooks verification libraries expect a base64-encoded secret, so the simplest setup is to generate a random secret, base64-encode it, and use that same string both in Workshop and in your receiver.

Delivery behavior

Deliveries happen asynchronously, off the request path — a webhook failure never blocks or fails the underlying action (for example, an audited change still succeeds even if its webhook can't be delivered). Delivery is best-effort: failures are logged server-side, but there is no delivery dashboard or manual re-drive.

  • Retries — after the initial attempt, a failed delivery is retried up to 5 more times (six attempts in total) with exponential backoff (starting at 100ms, capped at 30s). Retries happen on connection errors, HTTP 429, and 5xx responses.
  • Success — any 2xx response is treated as success. Redirects are not followed.
  • Timeouts — each attempt has its own 30s timeout, and the whole delivery — loading settings, building the client, and all attempts with their backoffs — is bounded to 60s.
  • Payload size — deliveries are capped at 1MB. A payload larger than that is dropped rather than sent.

Your endpoint should acknowledge quickly with a 2xx and do any heavy processing asynchronously.

Security

  • Destination URLs must use HTTPS (plain HTTP is only permitted for localhost, for local testing).
  • URLs may not contain embedded credentials (user:pass@host).
  • URLs that resolve to private, internal, or link-local addresses are rejected to guard against SSRF. Redirects are never followed.
  • The signing secret is write-only: it is never returned by the API or shown in the UI, and it is stripped from audit log entries.

Configuring via the API

Webhook settings can also be managed through the API:

  • GetWebhookSettings retrieves the current configuration for every source. Requires the read:settings permission. The write-only signing secret is never included in the response.
  • UpdateWebhookSettings replaces the configuration for every source. Requires the write:settings permission.

Because UpdateWebhookSettings replaces the entire configuration, include every source you want to keep in each call — omitting a source clears it. Sending an empty secret for a source keeps its existing secret rather than clearing it.