Skip to the content
Documentation
Help Start
Reading in:
  • German: 9,075 of 9,075 pages
About the help in other languages
All help pages

Outgoing webhooks from the audit trail

Using the audit trail's signed webhook sink as an outgoing event feed - what it sends, how to route content events such as content.object.publish to it, the HMAC signature and how a receiver checks it, delivery with…

Download as PDF

Address: https://doc.exponential.earth/exphelp/viewer/help/integrators/outgoing-webhooks-from-the-audit-trail.md

On this page

Outgoing webhooks from the audit trail

This page is for integrators who want Exponential to push events to another system over HTTPS without writing code: a security information and event management system (SIEM), a log platform, or a service that must learn about publications and removals. The audit trail records what happens in the installation and can deliver its records to sinks besides its own files: syslog, e-mail and a signed webhook (kernel/classes/audit/sinks/expauditwebhooksink.php). The audit itself is described in Audit.

Contents

  1. What the webhook sends
  2. Sending security events to a SIEM
  3. Sending content events to another system
  4. Checking the signature on the receiving side
  5. Delivery, retries and testing
  6. Limits

What the webhook sends

Batches of audit records as JSON, by HTTP POST:

{"v":1,"installation":"<installation id>","site":"<SiteName>","batch":"b-...","events":[<record>, ...]}
HeaderValue
X-Exponential-TimestampEpoch seconds when the batch was signed
X-Exponential-BatchThe batch id, for de-duplication
X-Exponential-Signaturesha256=<hex HMAC-SHA-256 of timestamp + "." + body, keyed with SigningSecret>

A record names the event (content.object.publish, access.session.login.failed ...), the actor, the object and target by type, id and identifying fields, the result and a request id. Records never contain content attribute values; the receiver reads the content it needs through the REST API or expservices.

Sending security events to a SIEM

The access and system channels carry sign-ins, refusals, role changes and settings changes. In settings/override/audit.ini.append.php:

[AuditSink_webhook]
URL=https://siem.example.com/ingest/exponential
SigningSecret=<a long random value>
MinSeverity=notice

[AuditChannel_access]
Sinks[]
Sinks[]=syslog
Sinks[]=webhook

[AuditChannel_system]
Sinks[]
Sinks[]=syslog
Sinks[]=webhook

The empty Sinks[] line clears the shipped list before the new one; keep syslog if you want it too. The secret is masked in the administration; keep it in settings/override, never in Git.

Sending content events to another system

Content events are recorded per event name. content.object.publish and content.object.create are off by default; removals, moves, hides, state and section changes are on. To feed publications and removals to a webhook:

[AuditEventSettings]
Enabled[]=content.object.create
Enabled[]=content.object.publish

[AuditChannel_content]
Sinks[]
Sinks[]=webhook

[AuditSink_webhook]
URL=https://hub.example.com/hooks/exponential
SigningSecret=<a long random value>
Events[]
Events[]=content.object.publish
Events[]=content.node.remove.*
Events[]=content.object.remove
Events[]=content.node.move
# only the listed events: a record outside Events[] is still taken when it reaches MinSeverity
MinSeverity=emergency

A sink takes a record that matches Events[] whatever its severity, and any other record routed to it when it reaches MinSeverity; emergency therefore keeps the sink to the listed events in practice.

Note

The audit is designed as a record of what happened, not as a message bus: records always go through the spool (a request never waits for the network) and the audit cronjob part delivers them, so delivery follows the cron schedule, typically every few minutes. The shape of a record follows the audit's event model. If the receiving system needs its own payload, a guaranteed order per object, or sub-second delivery, use a workflow event with an outbox (Workflows and triggers).

Checking the signature on the receiving side

Reject a request when the signature does not match or the timestamp is more than 300 seconds away; de-duplicate by the batch id and by each record's event id. In Python:

import hashlib, hmac, time

def verify(secret: bytes, timestamp: str, body: bytes, signature: str, tolerance=300) -> bool:
    if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > tolerance:
        return False
    expected = 'sha256=' + hmac.new(secret, timestamp.encode() + b'.' + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

A receiver written in PHP with the kernel loaded can call expAuditWebhookSink::verify( $secret, $timestamp, $body, $signature ), which answers '' when valid, else bad_signature or stale_timestamp.

Delivery, retries and testing

Setting ([AuditSink_webhook])DefaultMeaning
BatchSize100Records per request
BatchSeconds10Seconds a batch may wait
Timeout5Seconds per request
Retries5Attempts before a batch stays in the spool
RetryBackoff30Seconds before the first retry; doubled each time

Every record goes to the spool first ([AuditSinkSettings] SpoolDir, log/audit/spool); the audit cronjob part (in frequent, and as the part audit) delivers it and retries what failed. A 2xx answer delivers a batch. A retried batch keeps its batch id. Delivery is at least once: the receiver must de-duplicate by event id.

php bin/php/console exp:audit sinks list --allow-root-user          # ready? spooled? channels
php bin/php/console exp:audit sinks test webhook --allow-root-user  # one test record, exit 0 when delivered
php bin/php/console exp:audit sinks flush --apply --allow-root-user         # send what is spooled now

Limits

  • Only the audit trail sends webhooks; there is no generic outgoing webhook per content class or field.
  • There is no incoming webhook endpoint; a receiver of other systems' webhooks is a module or service function of your extension.
  • An event that is not recorded (switched off in [AuditEventSettings], or below MinSeverity of the audit) is not sent.

Become an Exponential Partner

Partners share their information, provide funding, contribute back and develop the products with us, and that is how Exponential grows. A partner website and dashboard are planned for mid-2027: please become a partner today to speed up our growth rate and land new features faster.

Something wrong on this page? Report an error

Documentation license

This documentation is free documentation, licensed under the GNU Free Documentation License 1.3 or later.

Exponential Copyright © 1998-2026 Exponential Foundation and 7x