SfSkills Domains Docs GitHub

Home / apex / callouts-and-http-integrations

callouts-and-http-integrations

Use when building, reviewing, or debugging outbound Apex HTTP callouts, Named Credentials, request/response handling, timeout behavior, or mock-based tests.

Open the skill folder on GitHub


Use this skill when an Apex integration needs to leave Salesforce safely. The aim is to use Named Credentials correctly, keep authentication and endpoint management out of code, separate callouts from unsafe transaction contexts, and make failure modes visible and testable.

Before Starting

Questions to Ask Before Configuring

Ask these before writing the first HttpRequest. Each one maps to a gotcha in references/gotchas.md; an agent that skips them produces a callout that works in a scratch org and double-posts in production.

AskWhy it mattersWhat a good answer adds
"What is already pending in the transaction that reaches this code — DML, an enqueued job, a batch, a @future, an email?"All five block a later callout in the same transaction, not just DML (Gotcha 1)The async boundary: callout-then-DML inline, or hand it to a Queueable
"Is this POST/PUT safe to send twice, and does the remote system accept an idempotency key?"A 5xx or a transport failure means unknown, not failed; retrying without a key is how you double-bill (Gotcha 11, anti-pattern 7)Either a key header and a retry policy, or a documented no-retry decision
"How many records can arrive in one invocation, and how slow is the remote system per call?"100 callouts and 120 s cumulative are per transaction, so n × timeout ≤ 120,000 decides the chunk size (Gotcha 3)The per-job record budget and the setTimeout value that fits it
"What does each status code mean for this integration — 401, 409, 422, 429, 503?"Retryable vs non-retryable cannot be inferred from the number alone; a 401 retried is a lockout (Gotcha 10)The classification table in references/examples.md §3, which the tests then mirror
"Org-wide credential or per-user, and who grants principal access in each org?"NamedPrincipal vs PerUserPrincipal changes who the integration works for, and the principal field was removed in API 58.0 (Gotcha 8)The External Credential shape plus the permission set that grants it
"How large is the biggest request and response, and is this path sync or async?"6 MB sync / 12 MB async caps the payload and counts against heap (Gotcha 5)A paging decision, or a decision to run async for the doubled ceiling
"Where does a permanent failure land, and who looks at it?"A swallowed CalloutException is invisible; AsyncApexJob alone does not say which record failedThe failure field or log object, and the query operations will run

What a proper callout design adds over just calling Http.send(): the endpoint and secret live in a Named Credential that can be repointed per org without a code deploy, every status code has a declared retry decision that a test proves, retries carry an idempotency key so a lost reply cannot double-write, and a permanent failure leaves a queryable row instead of a debug-log line.


Core Concepts

Named Credentials Are The Default Endpoint Boundary

Salesforce recommends Named Credentials for outbound authentication and endpoint management. Modern setups also use External Credentials and User External Credentials for principal mapping and secret handling. In practice, this means Apex should usually call callout:My_Named_Credential/... instead of embedding URLs, tokens, or headers directly in code.

Transaction Context Determines Whether The Callout Is Safe

Callouts from the wrong place cause production pain. A trigger or synchronous transaction that already performed DML can hit "uncommitted work pending" or create user-facing latency. The right fix is often to persist the business data first, then hand off outbound work to Queueable Apex with Database.AllowsCallouts.

HTTP Work Needs Explicit Failure Handling

A successful callout is not just "no exception thrown." You must check status codes, timeouts, payload parsing, and idempotency assumptions. Set an explicit timeout, classify retryable versus non-retryable errors, and log or surface failures with enough context to support operations.

Tests Must Mock The Remote System

Real HTTP traffic is not allowed in Apex tests. Test.setMock(HttpCalloutMock.class, mock) should cover both success and failure responses so integration logic can be exercised deterministically.

Common Patterns

Named Credential Service Wrapper

When to use: A service class owns outbound communication to one remote system.

How it works: Keep endpoint paths relative to a Named Credential, centralize request construction, set timeouts explicitly, and throw a domain-specific exception when the response is unusable.

Why not the alternative: Hardcoded endpoints and inline auth headers create release drift and security risk across environments.

Queueable After Commit For Outbound Sync

When to use: A trigger or controller must perform a callout after saving Salesforce data.

How it works: Enqueue one Queueable with IDs, re-query inside the job, make the callout there, and update integration status in a controlled way.

Mock-Driven Test Matrix

When to use: The callout code must prove success, non-200 responses, and timeout-like failures.

How it works: Build focused HttpCalloutMock classes and assert on side effects, not just on response parsing.

Decision Guidance

SituationRecommended ApproachReason
Outbound HTTP integration with managed credentialsNamed Credential + External CredentialKeeps secrets and endpoint config out of Apex
Trigger or save transaction needs to notify an external APIQueueable + Database.AllowsCalloutsSafe post-commit boundary for callouts
Small synchronous lookup that must block the userDirect callout with timeout and safe error messagingSometimes acceptable when latency is part of the workflow
Tests need to validate HTTP behaviorHttpCalloutMock with explicit success and failure casesDeterministic and CI-safe
Remote call is slow and must return to a user-facing pageContinuationOnly mechanism that suspends the request; costs its own limit set — 3 parallel / 3 chained callouts, 1 MB response, setTimeout ignored (apexdev L36301–36315). See apex/continuation-callouts
More records than the transaction's callout budgetChunk in the Queueable, carry the remainder into the single allowed re-enqueue100 callouts per transaction (apexdev L35844); async context allows one System.enqueueJob
The "we never found out" case must leave a rowSystem.Finalizer on the QueueableThe parent's own DML is rolled back when execute() dies. See apex/apex-transaction-finalizers
Remote system requires mutual TLSNamed Credential ClientCertificate parameter, or setClientCertificateNameKeeps the private key in Certificate and Key Management, never in Apex
  1. Answer the questions above and fill the worksheet. templates/callouts-and-http-integrations-template.md captures the identity model, the header contract, and the retryable status list — the three things reviewers ask for and generated code never has.
  2. Fix the transaction boundary before writing any HTTP code. Decide inline callout-then-DML, Queueable, @Future(callout=true), or Continuation. If a trigger is anywhere on the path, the answer is async (references/gotchas.md Gotcha 1 and 7).
  3. Write the credential metadata first. Take the NamedCredential + ExternalCredential XML from references/code-examples.md §4; the Apex must never contain a literal https:// endpoint or an Authorization header it built itself.
  4. Write the service against templates/apex/HttpClient.cls. Copy BillingApiService.cls from references/code-examples.md §1: explicit setTimeout, idempotency-key header, typed response DTO, and a classify() method whose branches match the table in references/examples.md §3. Leave retryOnTransient(false) — backoff belongs on the job boundary, not in a busy-wait.
  5. Write the async wrapper and the test class together. BillingSyncQueueable (implements Queueable, Database.AllowsCallouts, callouts before DML, bounded re-enqueue) and BillingApiServiceTest, which must cover 200, 5xx, 401, a malformed 2xx and a transport failure. Test.startTest() goes before Test.setMock(...), with the data setup outside the block.
  6. Run the checker. python3 skills/apex/callouts-and-http-integrations/scripts/check_callouts_and_http_integrations.py --manifest-dir force-app — it flags literal endpoints, callouts in loops, DML before a callout, synchronous callouts on a trigger path, a missing setTimeout, a body on a GET, tests without Test.setMock, and swallowed CalloutExceptions.
  7. Deploy and verify. sf project deploy start -x manifest/package.xml, then sf apex run test -n BillingApiServiceTest -r human -w 10 -c, then the NamedCredential and AsyncApexJob checks in references/code-examples.md §9 — a Named Credential that resolves to the wrong endpoint is the failure that reaches production silently.
  8. Walk the Review Checklist below before handing the change to a reviewer.

Review Checklist

Salesforce-Specific Gotchas

The full list with What happens / When it occurs / How to avoid and guide line numbers is in references/gotchas.md (12 entries). The ones that most often survive review:

  1. The blocker is "pending work", not "DML". Enqueuing a job, running Database.executeBatch, calling a @future method or sending email all block a later callout the same way an insert does (apexdev L35379–35380).
  2. setTimeout is a connect timeout. It bounds establishing the connection, not the transfer (apexrefguide L216720–216723). The real ceiling is 120 s cumulative across the transaction (apexdev L35856–35857).
  3. A body on a GET silently becomes a POST (apexdev L35377–35378) — no exception, no warning.
  4. The 6 MB / 12 MB payload cap is also a heap cap (cheat sheet L389–391, L415), so a large response leaves almost nothing for parsing it.
  5. Queueables that make callouts need Database.AllowsCallouts (apexdev L16164–16166); future methods need @Future(callout=true), whose default is false (apexdev L5134–5135).
  6. An unreleased savepoint blocks the callout even after a rollback — a different CalloutException with a different message (apexdev L8747–8748).
  7. A 200-series response is not the whole contract — a 200 carrying an HTML maintenance page is the case that reaches production.

Output Artifacts

ArtifactDescription
Callout reviewFindings on endpoint security, transaction safety, timeout handling, and tests
Callout scaffoldNamed Credential-based service wrapper with response classification and mock hooks
Retry/failure notesGuidance on which failures should be retried, surfaced to users, or logged for operations

Reference Files

FileRead it when
references/code-examples.mdYou are writing the code — service, Queueable, test class, NamedCredential / ExternalCredential / RemoteSiteSetting XML, -meta.xml, package.xml, deploy and verification commands
references/gotchas.mdA callout fails in a way the code does not explain — 12 grounded platform behaviours with guide line numbers
references/examples.mdYou want the narrative walk-through, the status-code classification table, or the mutual-TLS variant
references/llm-anti-patterns.mdYou are reviewing AI-generated callout code, or self-checking your own output (8 anti-patterns with detection hints)
references/well-architected.mdYou are justifying the async boundary, the identity model, or Continuation vs Queueable — and need the source list
templates/callouts-and-http-integrations-template.mdYou are capturing the endpoint, identity, header and retry decisions before writing code

Package files