Cookbook
Integrate with an existing pipeline
Wire Bedrock into your back-office without rewriting it. The pattern is the same regardless of what your existing system looks like.
You already have a system that produces advice — Intelliflo, Iress XPLAN, an in-house Salesforce build, a Python notebook, whatever. The job is to add Bedrock without ripping that system out. There are three integration points.
1. The submission hook
Wherever your existing system finalises a piece of advice, add a single call to Bedrock to submit it for review. This can be:
- A workflow step in your back-office BPM
- A database trigger watching the “advice ready” state
- A message-queue consumer picking up “advice.finalised” events
- A scheduled job submitting whatever was completed in the last hour
async function onAdviceFinalised(advice: Advice) {
// 1. Upload the finalised PDF directly to S3 via a presigned URL.
const documentKey = await uploadDocument(advice.documentBytes, `${advice.id}.pdf`);
// 2. Submit the uploaded document for review.
const { id: bedrockJobId } = await bedrock.submitAdvice({
documentKey,
documentType: 'SUITABILITY_REPORT',
clientReference: advice.id,
documentReference: advice.documentReference,
factFindSummary: {
riskProfile: advice.riskProfile,
investmentObjective: advice.investmentObjective,
investmentHorizon: advice.investmentHorizon,
capacityForLoss: advice.capacityForLoss,
existingHoldings: advice.existingHoldings,
vulnerabilityFlags: advice.vulnerabilityFlags ?? [],
annualIncome: advice.annualIncome,
netWorth: advice.netWorth,
},
// Only include aiContext when model identity is available
...(advice.modelProvider && advice.modelVersion
? {
aiContext: {
model: { provider: advice.modelProvider, version: advice.modelVersion },
inputs: advice.modelInputs,
outputs: advice.modelOutputs,
},
}
: {}),
});
await db.advice.update({ where: { id: advice.id }, data: { bedrockJobId } });
}See Submit an advice record for the full uploadDocument / submitAdvice implementation.
2. The webhook receiver
See Handle a webhook. This is the path that updates your customer record with the certificate URL.
3. The reconciliation job
Even with webhooks, you should reconcile periodically — say nightly — to catch any events you missed during downtime. List the firm's ledger records from your last successful run and apply anything you haven't already seen. The ledger list endpoint accepts startDate/endDate filters and standard pagination.
async function reconcile() {
const lastSeenAt = (await db.config.get('bedrock.lastSeenAt')) ?? '1970-01-01T00:00:00.000Z';
let page = 1;
let highWatermark = lastSeenAt;
for (;;) {
const url = new URL('https://api.bedrockcompliance.co.uk/v1/ledger/records');
url.searchParams.set('startDate', lastSeenAt);
url.searchParams.set('page', String(page));
url.searchParams.set('pageSize', '100');
const res = await fetch(url, {
headers: { 'X-Bedrock-Key': process.env.BEDROCK_API_KEY! },
});
const { data: records, pagination } = (await res.json()) as {
data: Array<{ id: string; sequenceNumber: number; eventType: string; timestamp: string }>;
pagination: { hasNext: boolean };
};
for (const record of records) {
// Idempotent — dedupe on sequenceNumber inside applyEntry.
await applyEntry(record);
if (record.timestamp > highWatermark) highWatermark = record.timestamp;
}
if (!pagination.hasNext) break;
page += 1;
}
await db.config.set('bedrock.lastSeenAt', highWatermark);
}What not to do
- Don't make Bedrock submission synchronous with adviser submission. Queue it; treat the submission as fire-and-forget with a retry on failure.
- Don't rely solely on webhooks. They're at-least-once, but networks fail; reconcile.
- Don't skip reconciliation. The whole point of Bedrock is that the records exist independently of your system; if the two diverge, the ledger is the truth.