Skip to content
ForceTricks
Back to blog

Salesforce Integration: Monitoring and What I Learned Breaking Things

5 min read
SeriesSalesforce Integration: From Basics to AdvancedPart 3 of 3
  1. 1Salesforce Integration: Patterns That Actually Work
  2. 2Salesforce Integration: Implementing Without Creating Technical Debt
  3. 3Salesforce Integration: Monitoring and What I Learned Breaking Things

Every integration fails. The question is whether you find out from monitoring or from the customer.

This is the final post in the series. In the previous two I covered patterns and implementation. Here I close with what I learned about observability — after having discovered failures both ways.

The Minimum Viable Monitoring

Before any sophisticated tooling, you need three things:

1. A structured log object

// Recommended fields on the Custom Object: IntegrationLog__c
IntegrationLog__c log = new IntegrationLog__c(
    System__c          = 'InventoryAPI',          // Text(100)
    Operation__c       = 'GET /products/{id}',    // Text(255)
    Status__c          = 'Failure',               // Picklist: Success | Failure | Timeout
    HttpCode__c        = 500,                     // Number
    ResponseTimeMs__c  = 1200,                    // Number
    Payload__c         = res.getBody(),           // Long Text Area — failure only
    ErrorMessage__c    = e.getMessage(),          // Text Area(255)
    Timestamp__c       = Datetime.now()           // DateTime
);
insert log;

Don't store the payload in production for high-volume calls — you'll fill the org's storage in weeks. Store it only on failure, and even then consider truncating after X days.

Alternative: Nebula Logger

Before building out IntegrationLog__c from scratch, it's worth knowing this problem has already been solved. Nebula Logger is an actively maintained open-source logging framework for Salesforce, widely adopted across the community. It provides everything you'd build into a custom object — structured fields, log levels (DEBUG / INFO / WARN / ERROR), Platform Events integration, and a ready-made dashboard — without reinventing the wheel.

The API is straightforward:

// Replaces the custom IntegrationLogger
Logger.info('Successful call to InventoryAPI')
    .setHttpRequestDetails(req)
    .setHttpResponseDetails(res);
Logger.saveLog();

// On failure:
Logger.error('Failed call to InventoryAPI')
    .setHttpRequestDetails(req)
    .setExceptionDetails(e);
Logger.saveLog();

When it makes sense to use it: on most greenfield projects, especially when the org already allows managed package installs and you don't want to maintain your own log infrastructure long-term.

When to prefer the custom object: orgs with strict third-party package restrictions, or when your fields and retention rules need to meet very specific compliance requirements. In those cases, full ownership of the object still justifies the maintenance cost.

2. Alerts on consecutive failures

A single error can be noise. Five consecutive errors for the same external system is a real problem. Set up a Flow or Scheduled Apex that queries the logs and fires an email or Slack notification when the threshold is hit.

3. A simple dashboard

A report showing success rate per system over the last 7 days. It doesn't need to be pretty. It needs to be easy to open when someone reports an issue.

The Silent Errors That Cost Me the Most Time

Timeout without a log

If an Apex callout throws System.CalloutException: Read timed out but it isn't caught and logged correctly, you have no traceability. The record ends up in an inconsistent state and nobody knows why.

try {
    InventoryClient.get(externalId);
} catch (System.CalloutException e) {
    // "Read timed out" lands here — logging is mandatory
    IntegrationLogger.record('InventoryAPI', 'timeout', e.getMessage());
    throw e; // re-throw so the error isn't silently swallowed
}

HTTP 200 with an error in the body

Some APIs return HTTP 200 with {"status": "error", "message": "..."} in the body. If you only check the status code, you'll treat this as a success — and the failure surfaces much later, when someone notices the data was never processed.

InventoryResponse resp = (InventoryResponse) JSON.deserialize(res.getBody(), InventoryResponse.class);
if (resp.success == null || !resp.success) {
    throw new InventoryException(resp.message);
}

Silent contract change

The external API changed a field from Integer to String. The JSON.deserialize passes without error but the value comes back null. You discover this three weeks later when someone notices inventory is always zero.

The simplest protection: validate critical response fields before persisting.

What I Would Do Differently Today

Looking back, the three decisions that most impacted the quality of the integrations I've maintained:

Centralize HTTP in a single client per external system. When every developer creates their own HttpRequest, you end up with five slightly different implementations of the same endpoint, each with its own error behavior.

Log before persisting, not after. If the Salesforce record insert fails after you've already called the external API, you have an inconsistency with no evidence. Logging the callout before committing the data gives you traceability even in the worst case.

Don't rely on Platform Events' automatic retry for business failures. The retry is for infrastructure failures. If the event has invalid data, retrying it for 72 hours won't fix anything — it'll just generate log noise.

Closing the Series

Integration is one of the oldest parts of Salesforce and still where projects break. The patterns haven't changed much — what changes is scale and the expectation of resilience.

What I've learned can be summed up as: pick the right pattern before writing code, structure the code for testability, and build observability from day one — not after the customer calls.


Missed the earlier posts? See Part 1 (patterns) and Part 2 (implementation).

Questions or different experiences? Let me know on LinkedIn.

Gabriel Cruz Ferreira

Gabriel Cruz Ferreira

Salesforce Architect · 15x Certified · Road to CTA

Was this post helpful?