Skip to content

the Integration Log

Module 2 of 9 in the chapter Reliability, Recovery, and Observability, chapter 14 of the Integration Architect course. Each module stands on its own. Previous: Every Arrow, Out and In · Next: The Log Is Data: Retention and Masking

The question this module answers: Where is every integration call recorded, and how do you design that record so it’s there when you need it?

On one project, the middleware team didn’t call Salesforce’s standard APIs. They called custom APIs that we built for them. And because every call came through our own door, I was able to timestamp every incoming call. I knew when it happened. I knew how long it took. I even recorded the payload of what they passed in.

Every call went to one custom object, the integration log. We wrote to it asynchronously, so it didn’t slow down the transaction, and it let us track everything and replay it. We even passed a unique identifier on every request, so during load testing I could match our log, line by line, against the middleware team’s performance logs. The Data Architect course tells this story too, from the data side (Proof of Delivery).

This module is the build: what goes in an entry, how one ID ties the layers together, how to keep a log that doesn’t vanish with the failure it’s recording, and what Salesforce itself recommends once the numbers get big.

Notice where that story started: custom APIs. That’s not a detail.

When another system calls Salesforce’s standard REST API, it writes straight into your objects. There’s no code of yours in the middle, so there’s nowhere for you to write a log entry. Salesforce records the API calls in its own event logs (module 7), but your own integration log only sees what passes through something you built: an Apex REST service, a flow, a trigger, a platform event subscriber.

So it’s a trade-off, and you decide it in the first week or two of a project, in design. With the standard API, the other team needs to know your objects and fields well, and the integration layer has to keep the log. With your own door, you get the log and a contract that hides your inner workings, and you need good Apex developers to build it (chapter 8 module 5).

Either way, someone keeps the log. The question is who, and where.

Whatever writes it, a log entry needs enough to answer the questions from module 1 later, when someone is looking at a failure at two in the morning.

  • Which integration, by a name everyone uses: Redrock manifests in, not Integration 3.
  • Which direction: in or out.
  • When it started and how long it took.
  • The ID that travels with it, the same one every layer records (next section).
  • The business key: the Work Order, the partner’s reference, whatever a person would search for.
  • How it ended: the status code, the error, and whether it will be retried.
  • The payload, or as much of it as you’re allowed to keep. That last part matters, and it’s the whole of module 3: a log that holds personal data is personal data.

On that project I used a long text field for the payload, because the payloads weren’t that large. Check yours. A log is no help if half the payload was cut off.

Here’s the thing I learned from matching those load-test logs. Every layer keeps its own log. Salesforce has one, the integration layer has one, the other system has one. On a bad day, you need to put them side by side. Without a shared ID, that’s guesswork by timestamp. With one, it’s a search.

So decide, for every arrow, which ID travels the whole way, and make every layer write it down.

On Steve Trucking’s status arrow, there’s a natural one. When a delivery’s status changes, Salesforce publishes an event, and every event message carries a unique ID, its EventUuid (chapter 11 module 4). The integration layer records that ID when it receives the event, and passes it on when it calls Redrock’s webhook, in the webhook-id header that the Standard Webhooks shape gives every message (chapter 8 module 6). Three logs, one ID.

One ID, three logs One ID, three logs. Salesforce publishes a status event, and its ledger records the event with its EventUuid. The integration layer receives the event and calls Redrock's webhook; its log records the call, the answer and the same ID. Redrock receives the call with the ID in the webhook-id header and records it too. Underneath: every layer keeps its own log; one ID lets you lay them side by side. {"author": "Steve Simpson", "blueprint": "1.0.0", "builder": "standard 1.58.0", "built": "2026-11-04", "id": "DGM-INT-FM-04", "rights": "\u00a9 2026 Twinshark LLC. All rights reserved.", "template": "cells 0.5.0", "theme": "stevetecharc 1.4.0"} SALESFORCE INTEGRATION LAYER REDROCK A status event Work Order: Delivered starts it Receives it, calls the webhook the status, and the ID Redrock's webhook receives the status Salesforce's ledger the event, with its EventUuid The integration log every attempt, the answer, the same ID Redrock's log the same ID, from the webhook-id header EventUuid webhook-id Every layer keeps its own log. One ID lets you lay them side by side. HOW TO READ IT each layer's own log, all with the same ID © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

One ID, three logs.

That’s what “top to bottom” means for logging. Not one log to rule them all, but one ID that lets you read them together.

Now a trap, and it catches good developers.

Say your Apex code processes an incoming manifest. Something fails, an exception is thrown, and in the catch you write an error record to your integration log. Then the exception goes on up, and the transaction fails. The Apex guide is plain about what happens next: “If the request doesn’t complete successfully, all database changes are rolled back.” All of them. Including your error record. The failure happened, and the only evidence of it was undone with it.

There are two ways out, and Salesforce has given you both.

Publish the log entry as a platform event, with Publish Immediately. Normally I’d steer you to Publish After Commit, so nobody hears about a save that didn’t happen (chapter 11 module 3). A log entry is the exception. Salesforce’s guide says to choose Publish Immediately “if the event publishing isn’t tied to transaction data”, and that it “publishes the event message when the publish call executes, regardless of whether the transaction succeeds.” That’s exactly what you want from a log. A subscriber then writes the event to the log object, in its own transaction. And it’s asynchronous, which is how we kept the log from slowing down the transaction in the first place. Remember the events count toward your event allocations (chapter 11 module 5).

Write the payload to a big object with Database.insertImmediate(). Big objects work differently. Salesforce’s own playbook puts it this way: “Because Database.insertImmediate() commits the big object record immediately — outside the normal Apex transaction, so it can’t be rolled back — the payload survives even if processing throws later.”

Outbound calls have their own rule, from chapter 9: call, then write. Salesforce won’t let you make a callout after uncommitted work in the same transaction, so the log entry for a callout is written after the answer comes back (chapter 9 module 5).

And in a flow, the fault path is where the log is written. Salesforce’s patterns guide says a flow’s default failure email “should be enhanced to put in custom behavior such as creating a case or writing errors to a custom object that can be monitored and tracked.” Many teams publish an error platform event from the fault path, and let one flow pick them all up and send the alerts. It’s the same idea: get the record out of the transaction that’s failing.

A log has to outlive the failure it records The log that rolls back: two panels. Logged inside the transaction: the manifest fails; the catch writes an error record; the transaction fails, and everything rolls back, the error record with it; no trace. Logged outside it: the catch publishes a log event with Publish Immediately, or writes the payload to a big object with insertImmediate; the transaction fails and rolls back; the log entry survives, written on its own. Underneath: a log has to outlive the failure it records. {"author": "Steve Simpson", "blueprint": "1.0.0", "builder": "standard 1.58.0", "built": "2026-11-04", "id": "DGM-INT-FM-05", "rights": "\u00a9 2026 Twinshark LLC. All rights reserved.", "template": "comparison/side-by-side 0.5.0", "theme": "stevetecharc 1.4.0"} Logged inside the transaction the error record rolls back too The manifest fails, and an exception is thrown. The catch writes an error record to the log. The transaction fails: every change rolls back. The error record is gone. No trace. Logged outside it the log entry survives The catch publishes a log event, Publish Immediately. Or writes the payload to a big object, insertImmediate. The transaction fails and rolls back. The log entry is written on its own. A log has to outlive the failure it records. © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

A log has to outlive the failure it records.

A custom object log is the right start, and for many integrations it’s all you’ll need. But do the Number of Zeros before you commit to it.

Steve Trucking’s deliveries change status about 26,400 times a day. If every status event that Salesforce publishes is recorded, that’s about 9.6 million entries a year, every year. Then add the manifests, and every other arrow.

In 2026, Salesforce published a pattern for exactly this, and it splits the log in two (The Big Objects Playbook: Payload Capture and Replay (opens in a new tab)).

The ledger is a big object. A copy of every event is written to it before any business logic runs. Big objects are built for hundreds of millions of records, and in the playbook’s words they “are not transactional database tables, instead they are a durable, queryable ledger.” Every payload goes in, success or failure.

The to-do list is a custom object. Only the failures go here, small records with the error, the status, a retry count, and a pointer back to the payload in the ledger. This is the object your support team works from, with list views, reports and flows.

The two are linked by the event’s ID. When a failure needs to be replayed, the retry reads the to-do list, fetches the payload from the ledger by that ID, and runs it again (module 6). The playbook names them well: the ledger is “the filing cabinet that permanently stores everything”, and the custom object is “the daily to-do list for your support team”.

The ledger and the to-do list The ledger and the to-do list: two panels. The ledger, a big object: every payload, success or failure; millions to billions of records; written before any business logic, with insertImmediate; read only by its index, from code; no triggers, flows, list views or standard reports. The to-do list, a custom object: only the failures, with the error, a status and a retry count; hundreds to thousands of open records; list views, reports and flows; a pointer back to the ledger by the event's ID. Underneath: the ledger holds everything; the to-do list holds what a person has to act on. Both carry the same ID. {"author": "Steve Simpson", "blueprint": "1.0.0", "builder": "standard 1.58.0", "built": "2026-11-04", "id": "DGM-INT-FM-06", "rights": "\u00a9 2026 Twinshark LLC. All rights reserved.", "template": "comparison/side-by-side 0.5.0", "theme": "stevetecharc 1.4.0"} The ledger a big object Every payload, success or failure. Millions to billions of records. Written before any business logic, with insertImmediate. Read only by its index, from code. No triggers, flows, list views or standard reports. The to-do list a custom object Only the failures: the error, a status, a retry count. Hundreds to thousands of open records. List views, reports and flows. A pointer back to the ledger, by the event's ID. The ledger holds everything. The to-do list holds what a person has to act on. Both carry the same ID. © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

The filing cabinet and the to-do list.

Before you choose a big object, know what you’re giving up, because the platform is honest about it:

  • The index is forever. Every query has to filter on the big object’s index, in order. So design the index around the question you’ll ask most often, usually “give me the payload for this event.” And get it right the first time: “After you’ve deployed a big object, you can’t edit or delete the index. To change the index, start over with a new big object.”
  • No platform automation, no standard screens. No triggers, no flows, no list views, no standard reports. People see the ledger through a Lightning web component you build.
  • No transactions, and writes can partly fail. The guide says so: “Big objects don’t support transactions”, and the best practice when writing is “to have a retry mechanism in place.”
  • No encryption. Encrypted data copied into a big object is “stored as clear text on the big object.” For a log full of payloads, that’s a design question, and module 3 answers it.
  • The allocation. Salesforce Help says orgs are “allocated storage for 1 million big object records per org.” At 26,400 a day, Steve Trucking’s ledger passes that in about 38 days. So a ledger that size means more big object capacity, or a shorter retention, or keeping the full ledger in the integration layer and only the failures in Salesforce. That’s an architect’s call, made with the numbers, not discovered later.

Test knowledge. Know how to log an integration so the log survives a failure: a platform event with Publish Immediately is published “regardless of whether the transaction succeeds”, and Database.insertImmediate() writes to a big object outside the transaction. Know big objects for logs at volume: insert-only, queried by their index (which can’t be changed after deployment), no triggers or flows, no standard UI, 1 million records allocated per org.

Back to the failure plan from module 1, and its first column: where is each arrow logged?

ArrowSalesforceThe integration layerShared ID
Manifests in from RedrockNothing of its own: standard API upserts, seen in Salesforce’s event logsRedrock’s call, and its own upsert into SalesforceRedrock’s reference
Status events outThe ledger: every event it publishesEach event receivedEventUuid
Status out to RedrockNothing: Salesforce doesn’t make this callEvery attempt and its answerEventUuid, sent as webhook-id
Failures, any arrowThe to-do list, a custom object, for anything Salesforce must act onIts own error queuethe same ID

Two things to notice. First, a lot of the logging lives in the integration layer, because that’s where most of the calls are made. Salesforce can only log what passes through its own code. Second, the shared-ID column is what makes the table work. Without it, these are four separate logs. With it, they’re one story.

Beyond the exam: what AI changes. Point an AI assistant at three logs with a shared ID, and it can find the one status that never arrived, among millions of lines, in a moment. Without the shared ID, it guesses by timestamp, just as you would. The design decision that makes AI useful here is the same one that makes your own search work.

  • Who keeps the log for each arrow, decided in design: your own door logs in Salesforce; the standard API means the integration layer logs.
  • What every entry holds, and the one ID every layer writes down.
  • How a log survives the failure it records: a Publish Immediately event, insertImmediate() to a big object, the fault path in a flow, and “call, then write” for callouts.
  • Where the payloads live at volume: a custom object, a big object ledger beside a to-do list, or the integration layer, sized with the Number of Zeros and the allocation.
  • The big object’s index, designed around the question you’ll ask most, before it’s deployed.