Reference
Webhook payloads
The shape of every webhook event Bedrock can send, with examples and signature verification details.
Request format
Every webhook is a JSON POST. Bedrock sends two headers and the payload:
X-Bedrock-Event— the webhook event name (e.g.document.approved,sla.breached,incident.logged). See the ledger events table for the full mapping.X-Bedrock-Signature—sha256=<hex>, an HMAC-SHA256 of the raw request body using your firm webhook secret- The body is the same envelope for every event type — a small projection of the underlying ledger record. Fetch the full record (and any related job, certificate, or incident) by id when you need more.
Signature
The signature header has the form sha256=<hex>. Compute HMAC-SHA256 over the raw request bytes using your firm's webhook secret and compare the hex digest in constant time.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody: Buffer, header: string, secret: string) {
const [scheme, provided] = header.split('=');
if (scheme !== 'sha256' || !provided) return false;
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(provided, 'hex');
const b = Buffer.from(expected, 'hex');
return a.length === b.length && timingSafeEqual(a, b);
}Payload shape
Every delivery — regardless of event type — carries the same flat envelope, a projection of the underlying ledger record. The full event-specific snapshot (review actions, incident metadata, impact-assessment template, etc.) lives on the ledger record itself; call GET /v1/ledger/records/{id} with the id from the payload to fetch it.
{
"id": "01HW2...",
"firmId": "01HW1...",
"sequenceNumber": 4271,
"eventType": "DOCUMENT_APPROVED",
"timestamp": "2026-04-07T12:35:02.000Z",
"documentHash": "9f86d081884c7d65...",
"chainHash": "b7e23ec29af22b0b..."
}id— ledger record id. Use it to fetch the full record (and any related review job, certificate, or incident) on demand.firmId— your firm id. Useful when one webhook receiver fronts multiple firm subscriptions.sequenceNumber— strictly-monotonic position of this record in your firm's chain. The right field to dedupe on if you receive the same delivery twice.eventType— theLedgerEventTypeenum value (e.g.DOCUMENT_APPROVED). TheX-Bedrock-Eventheader carries the dot-format webhook name for the same event.timestamp— server-assigned ISO 8601 timestamp at which the record was written.documentHash— sha256 of the underlying document, where the event is document-related. Empty string for non-document events.chainHash— sha256 binding this record to the previous one in the chain. Verifies that the record is part of your firm's untampered history.
Resolving the full record
For an outcome event (DOCUMENT_APPROVED / DOCUMENT_MODIFIED / DOCUMENT_REJECTED), the certificate becomes available shortly after delivery. Look it up by the record id from the payload:
curl https://api.bedrockcompliance.co.uk/v1/ledger/records/$RECORD_ID/certificate \
-H "X-Bedrock-Key: bk_live_..."For an SLA_BREACHED, INCIDENT_LOGGED, INCIDENT_RESOLVED or IMPACT_ASSESSMENT_APPROVED event, fetch the underlying record to inspect the snapshot it carries:
curl https://api.bedrockcompliance.co.uk/v1/ledger/records/$RECORD_ID \
-H "X-Bedrock-Key: bk_live_..."Delivery semantics
- At-least-once delivery — be ready to handle the same payload more than once.
- Deliveries are queued through SQS and the worker retries on every non-2xx response until SQS gives up; design your handler to be idempotent.
- Dedupe on
sequenceNumber— it is unique per firm and strictly increasing. - Subscriptions are per-event-type. Register only the events your handler cares about; unsubscribed events are not delivered at all.