Getting Started
Your first integration
A full walkthrough wiring Bedrock into a real advice pipeline — from suitability report PDF to verifiable certificate.
This guide is the longer cousin of the Quickstart. It assumes you've already submitted a record by hand and now want to wire Bedrock into a real production pipeline. We'll build a minimal Node service that:
- Receives a finished suitability report from your back-office system.
- Submits it to Bedrock for review.
- Listens for the webhook that fires when the review completes.
- Stores the certificate URL on the customer record.
1. Project setup
mkdir bedrock-integration && cd bedrock-integration
npm init -y
npm install fastify undiciCreate a .env file with the two secrets we'll need:
BEDROCK_API_KEY=bk_live_...
BEDROCK_WEBHOOK_SECRET=whsec_...2. Submit a record
The submit endpoint takes a document type, a URL Bedrock can fetch the source document from, and any metadata you want to round-trip back through the certificate. The URL must be reachable from Bedrock's ingress — a presigned S3 URL is the usual pattern.
import { request } from 'undici';
export async function submitForReview(input: {
documentType: 'SUITABILITY_REPORT';
documentUrl: string;
clientReference: string;
documentReference: string;
factFindSummary: Record<string, unknown>;
}) {
const res = await request('https://api.bedrockcompliance.co.uk/v1/principal/jobs', {
method: 'POST',
headers: {
'X-Bedrock-Key': process.env.BEDROCK_API_KEY!,
'Content-Type': 'application/json',
},
body: JSON.stringify(input),
});
if (res.statusCode !== 201) {
const body = await res.body.text();
throw new Error(`Bedrock submit failed: ${res.statusCode} ${body}`);
}
return (await res.body.json()) as { id: string; status: string };
}3. Receive the webhook
Bedrock signs every webhook payload with HMAC-SHA256 using your firm's webhook secret. Verify the signature on every request — never trust the payload alone.
import Fastify from 'fastify';
import { createHmac, timingSafeEqual } from 'node:crypto';
const app = Fastify();
// Capture the raw request body — the signature is computed over the exact bytes
// Bedrock sent. Re-serialising via JSON.stringify(req.body) will not match.
app.addContentTypeParser(
'application/json',
{ parseAs: 'buffer' },
(_req, body, done) => done(null, body),
);
app.post('/webhooks/bedrock', async (req, reply) => {
const header = req.headers['x-bedrock-signature'] as string | undefined;
const eventName = req.headers['x-bedrock-event'] as string | undefined;
if (!header || !eventName) return reply.code(400).send('missing signature');
// Header format is "sha256=<hex>"
const [scheme, provided] = header.split('=');
if (scheme !== 'sha256' || !provided) return reply.code(400).send('bad signature format');
const rawBody = req.body as Buffer;
const expected = createHmac('sha256', process.env.BEDROCK_WEBHOOK_SECRET!)
.update(rawBody)
.digest('hex');
const a = Buffer.from(provided, 'hex');
const b = Buffer.from(expected, 'hex');
if (a.length !== b.length || !timingSafeEqual(a, b)) {
return reply.code(401).send('bad signature');
}
// The body is the raw payload — there is no envelope. The event name comes
// from the X-Bedrock-Event header.
const payload = JSON.parse(rawBody.toString('utf8'));
if (eventName === 'review.completed') {
await persistCertificate(payload.reviewJobId, payload.certificateId);
}
return reply.code(204).send();
});
app.listen({ port: 8080 });4. Persist the certificate
The webhook gives you the certificate ID. Store it on your customer record alongside the canonical URL — clients can use that URL to verify the document themselves at any point in the future, without your involvement.
async function persistCertificate(jobId: string, certificateId: string) {
const url = `https://verify.bedrockcompliance.co.uk/c/${certificateId}`;
await db.advice.update({
where: { externalRef: jobId },
data: {
bedrockCertificateId: certificateId,
bedrockCertificateUrl: url,
bedrockReviewedAt: new Date(),
},
});
}5. Test end-to-end
Run your service and submit a record:
node server.js &
curl -X POST http://localhost:8080/internal/submit-testWithin seconds you should see the job land in the Principal app, get reviewed (use a test reviewer in staging), and trigger your webhook. The certificate URL appears on the customer record.
What to build next
- Replay logic for missed webhooks (see Handle a webhook).
- A scheduled job to verify the chain integrity of last week's records.
- An incident response flow that creates a Principal escalation when your back-office system flags a complaint.