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:

  1. Receives a finished suitability report from your back-office system.
  2. Submits it to Bedrock for review.
  3. Listens for the webhook that fires when the review completes.
  4. Stores the certificate URL on the customer record.

1. Project setup

bash
mkdir bedrock-integration && cd bedrock-integration
npm init -y
npm install fastify undici

Create a .env file with the two secrets we'll need:

bash
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.

ts
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.

ts
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.

ts
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:

bash
node server.js &
curl -X POST http://localhost:8080/internal/submit-test

Within 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.