---
title: "Audit Export"
description: "Stream audit events to your own systems. HMAC-signed webhooks for any endpoint, plus native formats for Splunk HEC, Datadog, and Elastic."
canonical: "https://pgbeam.com/docs/audit-export"
last-updated: "2026-10-29T19:01:20.000Z"
---

# Audit Export

> Stream audit events to your own systems. HMAC-signed webhooks for any endpoint, plus native formats for Splunk HEC, Datadog, and Elastic.

URL: https://pgbeam.com/docs/audit-export

Audit export pushes PgBeam events to your systems as they happen. Point it at a webhook endpoint and PgBeam delivers a signed JSON payload for each event. Point it at a SIEM and PgBeam formats the events the way that SIEM expects. The [audit log](/docs/audit-log) keeps the full history for querying; export is for getting events out in real time.

## Event types

| Event                | Fires when                                                                                |
| -------------------- | ----------------------------------------------------------------------------------------- |
| `query_blocked`      | A statement is rejected by policy (allowlist, read-only, etc.).                           |
| `budget_exhausted`   | A credential hits its query or row budget.                                                |
| `kill_switch`        | A credential or project kill-switch is tripped.                                           |
| `masked`             | A result is returned with one or more masked columns.                                     |
| `migration_flagged`  | A DDL statement is flagged by the [safe-migration](/docs/safe-migrations) linter.         |
| `approval_requested` | A write or DDL is held for [approval](/docs/approvals).                                   |
| `canary_tripped`     | A statement references a [honeytoken](/docs/honeytokens) decoy relation.                  |
| `content_flagged`    | The result-content scan finds instruction-shaped text in the values a statement returned. |
| `anomaly_alert`      | [Anomaly detection](/docs/anomaly-detection) raises an alert.                             |
| `audit_checkpoint`   | A signed checkpoint is issued over the project's audit chain.                             |

For each event's full payload shape, see [webhook events](/docs/webhook-events).

## Create a webhook endpoint

**CLI**

```bash
pgbeam webhooks create https://hooks.example.com/pgbeam \
  --event query_blocked,kill_switch,anomaly_alert
```

**Dashboard**

Open **Webhooks**, add an endpoint URL, pick the events to send, and copy the
signing secret. Use **Send test event** to verify your receiver before you
rely on it.

**API**

```json title="Create endpoint"
{
  "url": "https://hooks.example.com/pgbeam",
  "event_types": ["query_blocked", "kill_switch", "anomaly_alert"],
  "format": "json"
}
```

You set the signing secret when you create the endpoint. It is write-only: PgBeam stores it to sign deliveries and never returns it, so keep your own copy. The same secret verifies every delivery.

## Webhook payload

Each delivery is a JSON body with the event, the project, and the event-specific detail under `data`. The [webhook events](/docs/webhook-events) page documents the `data` fields for every event type.

```json title="query_blocked delivery"
{
  "id": "whd_2a9f1c",
  "type": "query_blocked",
  "project_id": "prj_abc",
  "occurred_at": "2026-06-13T09:24:11.512Z",
  "data": {
    "audit_id": "aud_9f2c",
    "credential_id": "agent_4f2c",
    "region": "us-east-1",
    "event": "blocked",
    "sql": "DELETE FROM users",
    "reason": "policy is read-only: DELETE is not allowed"
  }
}
```

PgBeam signs every native (`json`) and `elastic` delivery. Two signature headers are sent, both as `sha256=<hex>` keyed with your signing secret:

* `X-PgBeam-Signature` (v1) is the HMAC-SHA-256 of the raw request body only.
* `X-PgBeam-Signature-V2` (v2) is the HMAC-SHA-256 of the exact byte string `timestamp + "." + body`, where `timestamp` is the same value sent in the `X-PgBeam-Timestamp` header (unix seconds, as a decimal string) and `.` is a single literal period. Because the timestamp is part of the signed bytes, a captured delivery cannot be replayed with a rewritten timestamp: rewriting it invalidates the signature. This is the same construction Stripe uses.

Each delivery also carries `X-PgBeam-Event`, `X-PgBeam-Event-Id`, `X-PgBeam-Timestamp` (unix seconds), and `X-PgBeam-Delivery`. `X-PgBeam-Event-Id` matches the event's `id` and is the same on every endpoint that receives the event. `X-PgBeam-Delivery` identifies one event sent to one endpoint: it stays the same on every retry of that delivery and differs between endpoints.

We recommend verifying `X-PgBeam-Signature-V2` and rejecting stale timestamps. v1 stays in place unchanged for existing receivers, so you can migrate at your own pace. The SIEM/token destinations (Splunk HEC, Datadog) carry neither signature; they authenticate with the destination's own token.

```ts title="Verify a delivery with the v2 signature (Node.js)"
import { createHmac, timingSafeEqual } from "node:crypto";

// Signed bytes are exactly: timestamp + "." + rawBody
export function verifyV2(
  rawBody: string,
  timestamp: string, // the X-PgBeam-Timestamp header, verbatim
  signatureHeader: string, // the X-PgBeam-Signature-V2 header
  secret: string,
  toleranceSeconds = 300,
): boolean {
  // Reject stale timestamps first to enforce a replay window.
  const ts = Number(timestamp);
  if (!Number.isFinite(ts)) return false;
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - ts) > toleranceSeconds) return false;

  const expected =
    "sha256=" +
    createHmac("sha256", secret)
      .update(timestamp + "." + rawBody)
      .digest("hex");

  const a = Buffer.from(signatureHeader);
  const b = Buffer.from(expected);
  return a.length === b.length && timingSafeEqual(a, b);
}
```

Verify against the raw request body, before any JSON parsing reserializes it, and use the `X-PgBeam-Timestamp` value verbatim so your recomputed signature matches the bytes we signed. Compare with a constant-time function. Rejecting deliveries whose `X-PgBeam-Timestamp` is far from the current time (5 minutes is a reasonable window) is what closes the replay gap, so enforce it when you verify v2.

The v1 header is still valid if you have not migrated. It signs the body only, so it cannot bind the timestamp:

```ts title="Verify the legacy v1 signature (Node.js)"
import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(
  rawBody: string,
  signatureHeader: string,
  secret: string,
): boolean {
  const expected =
    "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");

  const a = Buffer.from(signatureHeader);
  const b = Buffer.from(expected);
  return a.length === b.length && timingSafeEqual(a, b);
}
```

> **SIEM authentication**
>
> Splunk HEC and Datadog deliveries are not HMAC-signed. They authenticate with
> the destination's own token instead: Splunk uses an `Authorization: Splunk   <token>` header and Datadog uses `DD-API-KEY`. Set that token as the endpoint's
> secret.

> **Delivery and retries**
>
> PgBeam expects a `2xx` response. A request that gets no response within 10
> seconds counts as a timeout. A non-`2xx` response or a timeout is retried.
> Redirects are not followed: a `3xx` counts as a failed attempt, so register
> the final URL. Each delivery gets up to 8 attempts, with waits of 30 seconds,
> 2 minutes, 10 minutes, 1 hour, 6 hours, 12 hours, and 24 hours between them.
> If the eighth attempt fails, about 43 hours after the first, the delivery is
> given up. While an endpoint is disabled its deliveries are held without using
> attempts, for up to 7 days. Make your receiver idempotent by keying on the
> event `id` or `X-PgBeam-Delivery`, both of which are stable across retries.

## SIEM formats

For a SIEM, set the endpoint's `format` and PgBeam shapes each event the way that product ingests it. Retry behavior is the same. Signing differs for `splunk_hec` and `datadog`, as noted below the table.

| Format       | Sends                                                                         |
| ------------ | ----------------------------------------------------------------------------- |
| `splunk_hec` | Splunk HTTP Event Collector (HEC) envelope. Set your HEC token as the secret. |
| `datadog`    | Datadog Logs intake payload with `ddsource: pgbeam` and event tags.           |
| `elastic`    | Elastic / OpenSearch JSON documents with an ECS-style shape.                  |

For `splunk_hec` and `datadog`, the endpoint secret is the destination's own token (Splunk sends it as `Authorization: Splunk <token>`, Datadog as `DD-API-KEY`) rather than an HMAC signature.

```bash title="Export to Splunk HEC"
pgbeam webhooks create https://splunk.example.com:8088/services/collector \
  --format splunk_hec \
  --secret "$SPLUNK_HEC_TOKEN" \
  --event query_blocked,budget_exhausted,kill_switch,anomaly_alert
```

## Related

* [Webhook events](/docs/webhook-events): every event type and its payload shape.
* [Audit log](/docs/audit-log): the queryable history these events come from.
* [Anomaly detection](/docs/anomaly-detection): source of `anomaly_alert` events.
* [Approvals](/docs/approvals): source of `approval_requested` events.
* [Safe migrations](/docs/safe-migrations): source of `migration_flagged` events.