Stream the audit log to an HTTPS webhook
Administrator·3 minutes to read
What this does for you
Every new entry in your organisation's audit log is posted, within a minute of being written, to an https endpoint you run — your own collector, or any SIEM's generic HTTP input. Each request is signed, so your endpoint can prove it came from Botseon and refuse anything else. Only an owner or an administrator can see or change audit streams.
Before you start
- Your team is on Enterprise or its two-week Enterprise trial. The audit log and its streams are
part of the Enterprise plan: on a Team plan, Organisation → Audit shows
The audit log, its exports and the SIEM stream are part of the Enterprise plan.with a link toSee Enterprise. - An https address on the public internet that answers 2xx once it has stored the entries. It must not redirect (redirects are not followed) and must not resolve to a private or local network.
- Somewhere to keep the signing secret, which is shown once.
Steps
- Open Organisation → Audit and find Audit streams.
- Under Destination, choose HTTPS webhook, give it a Name and enter the Address.
- Press Add stream, and copy the signing secret it shows. It is not shown again.
- On your endpoint, for every request:
- read the raw body before parsing it;
- compute the hex HMAC-SHA256 of
v1:+ thex-botseon-timestampheader +:+ the body, keyed with the signing secret exactly as shown; - compare it with the
x-botseon-signatureheader in constant time, and answer 401 if it differs or the timestamp is more than five minutes old; - store the rows, then answer 2xx.
Each request has the header x-botseon-event: audit.rows and a JSON body
{"event":"audit.rows","organisation":"<id>","rows":[…]} with up to 200 entries.
x-botseon-delivery is the same on a retry of the same entries, so use it to drop duplicates.
A worked example
In Node.js:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verified(secret, headers, rawBody) {
const ts = headers['x-botseon-timestamp'];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const want = createHmac('sha256', secret).update(`v1:${ts}:${rawBody}`).digest('hex');
const got = String(headers['x-botseon-signature'] ?? '');
return got.length === want.length && timingSafeEqual(Buffer.from(got), Buffer.from(want));
}
What can go wrong
The stream's row on Organisation → Audit shows the last delivery and, when the newest attempt failed, why and when it is tried again. A failed batch is never skipped: the same entries are sent again after 15 seconds, then 30 seconds, 1 minute, 5 minutes, 15 minutes, and then every hour until the destination takes them.
| What the row says | What to do |
|---|---|
| the destination refused the request (HTTP 401) | your endpoint's secret is not the one shown when the stream was added: remove the stream, add it again, and store the new secret |
| the address is refused | the address resolves to a private or local network |
| the destination could not be reached / did not answer in time | your endpoint is down, slower than ten seconds, or answers with a redirect (enter the final address) |
See also
- Organisation policies — the audit log the stream reads from
- Stream to Splunk, Sumo Logic or Amazon S3
- To revoke, remove the stream: its signing secret is deleted, and your endpoint should stop accepting it.
Last verified against build 9d01262.