> For the complete documentation index, see [llms.txt](https://triton.pharos.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://triton.pharos.ai/common-apex-usage-patters/integration-logs.md).

# Integration Logs

Capture the full request/response payload on an outbound callout so a failed integration is something you can inspect — and replay — instead of guess at.

When Salesforce talks to an external service, the happy path is boring: you send a request, you get a `200`, life goes on. The interesting case is the `500` at 2am that nobody sees until a customer complains. By then the request is long gone — unless you logged it.

An "integration log" isn't a special kind of log — it's an **ordinary Apex log with the HTTP request and response attached**. You build it exactly like every other Apex log in this section (`Triton.t`, or `Triton.makeBuilder()`, which defaults to the Apex category) and add one method — `.integrationPayload(request, response)` — to capture both sides of the exchange on the record. That's the whole trick: attaching a payload is a property of the log, not a separate category or entity.

## The payload viewer

Once a log carries an HTTP exchange, Triton renders it in a Postman-style viewer right on the log record:

<figure><img src="https://3094374025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFlq7dXDt5Z9LFxymJV0A%2Fuploads%2Fgit-blob-e7c3d1670418b06a3434fcd6a41a2268442bd4ea%2FScreenshot_2024-06-13_at_10_47_57_AM.png?alt=media" alt=""><figcaption><p>Pharos Triton Integration Log View</p></figcaption></figure>

From here you can inspect the endpoint, headers, parameters, and bodies of both the request and the response — and even **replay** the request (with optional edits) from your org, not just your browser. For troubleshooting a flaky endpoint, that's gold.

## The callout, before logging

Here's a typical outbound callout: gather some data, build a JSON body, fire it off.

{% code lineNumbers="true" %}

```apex
public static void pushLoanApplication(Id loanApplicationId) {
    Loan_Application__c la = [
        SELECT Id, Primary_Contact__r.FirstName, Primary_Contact__r.LastName,
               Primary_Contact__r.Email, Primary_Contact__r.Social__c
        FROM Loan_Application__c
        WHERE Id = :loanApplicationId
        LIMIT 1
    ];

    Map<String, String> requestMap = new Map<String, String>{
        'firstName' => la.Primary_Contact__r.FirstName,
        'lastName'  => la.Primary_Contact__r.LastName,
        'email'     => la.Primary_Contact__r.Email,
        'ssn'       => la.Primary_Contact__r.Social__c
    };

    Http http = new Http();
    HttpRequest request = new HttpRequest();
    request.setEndpoint('callout:LoanService/applications');
    request.setMethod('POST');
    request.setHeader('Content-Type', 'application/json;charset=UTF-8');
    request.setBody(JSON.serializePretty(requestMap));

    HttpResponse response = http.send(request);
}
```

{% endcode %}

The glaring problem: if the response comes back non-`200`, we never know. Let's fix that.

## Attaching the payload

Nothing about the log changes except one method call. It's still a plain Apex log — the default category — and `.integrationPayload(request, response)` serializes the full HTTP exchange onto the record so Pharos can render the payload viewer:

{% code lineNumbers="true" %}

```apex
public static void pushLoanApplication(Id loanApplicationId) {
    Loan_Application__c la = [
        SELECT Id, Primary_Contact__r.FirstName, Primary_Contact__r.LastName,
               Primary_Contact__r.Email, Primary_Contact__r.Social__c
        FROM Loan_Application__c
        WHERE Id = :loanApplicationId
        LIMIT 1
    ];

    Map<String, String> requestMap = new Map<String, String>{
        'firstName' => la.Primary_Contact__r.FirstName,
        'lastName'  => la.Primary_Contact__r.LastName,
        'email'     => la.Primary_Contact__r.Email,
        'ssn'       => la.Primary_Contact__r.Social__c
    };

    try {
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndpoint('callout:LoanService/applications');
        request.setMethod('POST');
        request.setHeader('Content-Type', 'application/json;charset=UTF-8');
        request.setBody(JSON.serializePretty(requestMap));

        HttpResponse response = http.send(request);

        if (response.getStatusCode() != 200) {
            // A plain Apex error log — with the HTTP exchange attached
            Triton.logNow(Triton.t
                .area(TritonTypes.Area.RestAPI)
                .summary('Unable to push loan application to loan service')
                .details(String.format(
                    'Received a {0} status code when pushing application {1}',
                    new String[]{ String.valueOf(response.getStatusCode()), la.Id }))
                .relatedObject(la.Id)
                .integrationPayload(request, response)
                .error());
        } else {
            // Success — a routine INFO log, payload attached for the record
            Triton.log(Triton.t
                .area(TritonTypes.Area.RestAPI)
                .summary('Loan application pushed to loan service')
                .relatedObject(la.Id)
                .integrationPayload(request, response)
                .info());
        }
    } catch (Exception e) {
        Triton.logNow(Triton.t
            .area(TritonTypes.Area.RestAPI)
            .relatedObject(la.Id)
            .exception(e));
    } finally {
        Triton.flush();
    }
}
```

{% endcode %}

A few things worth calling out:

* **`.integrationPayload(request, response)`** is the only thing that makes this an "integration" log — it captures both sides of the exchange for the viewer. Everything else is a normal Apex log. There's an overload for inbound REST too (`.integrationPayload(RestRequest, RestResponse)`); see [Apex REST Logging](/common-apex-usage-patters/apex-rest-logging.md).
* **No special category.** The log uses the default **Apex** category (`Triton.t` / `Triton.makeBuilder()`). You don't reach for a distinct "Integration" category — attaching a payload is a property of an ordinary log.
* **`.error()`** on the bad-response path sets the level to `ERROR` and flags the log for issue creation, so a bad response doesn't just get recorded — it gets someone's attention. On the happy path, the same log goes out at `.info()` with the payload still attached, so you have a record of successful exchanges too.
* **`.exception(e)`** handles the *other* failure mode — a thrown `CalloutException`, a timeout — with the same one-liner you use everywhere else.
* **`.relatedObject(la.Id)`** links the log to the loan application, so it shows up in the Related Objects view on that record.
* **`Triton.logNow(...)`** publishes the failure immediately — an integration failure is exactly the kind of thing you never want stuck in a buffer that might not flush — while the routine success buffers and goes out on the closing `Triton.flush()`.

Run this against a failing endpoint and you get a fully inspectable record:

<figure><img src="https://3094374025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFlq7dXDt5Z9LFxymJV0A%2Fuploads%2Fgit-blob-9af523798a2532c08046b73376cbb0046b3655a8%2FScreenshot%202024-06-13%20at%204.08.19%20PM.png?alt=media" alt=""><figcaption><p>Resulting Integration Log</p></figcaption></figure>

…with the loan application linked automatically:

<figure><img src="https://3094374025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFlq7dXDt5Z9LFxymJV0A%2Fuploads%2Fgit-blob-26a1f335676f758cefdf5e7a53b23f7f0faffb16%2FScreenshot%202024-06-13%20at%204.08.31%20PM.png?alt=media" alt=""><figcaption><p>Related Object Automatically Associated</p></figcaption></figure>

{% hint style="info" %}
**Type is yours to define.** The example leans on `Area`, but a log with a payload attached is a great place for a domain-specific `Type` like `LoanServiceSync`. Extend the `Type` enum for your org — see [Enums](/core-concepts/enums.md).
{% endhint %}
