How make callout from Trigger?

Why you can’t make a synchronous HTTP callout directly from a Trigger

Salesforce triggers execute as part of the database transaction. Making a synchronous HTTP callout inside that same transaction is not allowed because external latency could make the transaction unreliable and increase the chance of rollbacks. Instead you must perform the callout asynchronously.

Approved approaches (recommended)

1) @future(callout=true)

Use a static Apex method annotated with @future(callout=true). From a trigger, call that future method and perform the HTTP request there. Good for simple fire-and-forget integrations. Limitations: future methods are queued and limited in number per transaction.

public class CalloutService {
@future(callout=true)
public static void doCallout(String jsonPayload) {
Http http = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.example.com/endpoint');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(jsonPayload);
HttpResponse res = http.send(req);
// handle response or log
}
}

// Trigger snippet
trigger AccountTrigger on Account (after insert) {
List payloads = new List();
for (Account a : Trigger.new) {
// build payload per record or combine to single payload
payloads.add(JSON.serialize(a));
}
// Example: call once per transaction with combined JSON
CalloutService.doCallout(JSON.serialize(payloads));
}

2) Queueable Apex (recommended for more control)

Queueable provides more control, chaining and supports complex types. Use implements Queueable, Database.AllowsCallouts.

public class QueueableCallout implements Queueable, Database.AllowsCallouts {
private List payloads;
public QueueableCallout(List payloads){
this.payloads = payloads;
}
public void execute(QueueableContext ctx){
Http http = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_Named_Credential/endpoint');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(payloads));
HttpResponse res = http.send(req);
// process response
}
}

// Trigger snippet
trigger OpportunityTrigger on Opportunity (after insert) {
List payloads = new List();
for (Opportunity o : Trigger.new) payloads.add(JSON.serialize(o));
System.enqueueJob(new QueueableCallout(payloads));
}

3) Platform Events / Change Data Capture

Publish a Platform Event from the trigger and have a subscriber (Apex trigger on the event or external streaming client) process and perform the callout. This decouples processing and is scalable.

Important considerations and best practices

  • Remote Site / Named Credentials: Use Named Credentials when possible for secure auth and simplified endpoint management. Otherwise add the endpoint to Remote Site Settings.
  • Bulkification: Don’t perform a callout per record. Aggregate payloads and make a single call for the batch of records in the trigger context.
  • Limits: You can enqueue a limited number of async jobs per transaction (e.g., future methods, queueable limits). Design accordingly.
  • Testing: Use HttpCalloutMock in tests to mock responses — callouts are not allowed in synchronous tests unless mocked.
  • Error handling & retry: Log failures to a custom object or use Platform Events for retry logic. Consider using a durable queuing mechanism if the external system is critical.

Example: Test with HttpCalloutMock

@IsTest
global class MockHttpResponseGenerator implements HttpCalloutMock {
global HTTPResponse respond(HTTPRequest req) {
HttpResponse res = new HttpResponse();
res.setHeader('Content-Type','application/json');
res.setBody('{"status":"ok"}');
res.setStatusCode(200);
return res;
}
}

@IsTest
private class CalloutServiceTest {
@IsTest static void testFutureCallout() {
Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator());
Test.startTest();
CalloutService.doCallout('{"test":"value"}');
Test.stopTest();
// assertions
}
}

Summary

You cannot perform synchronous HTTP callouts inside a trigger transaction. Use asynchronous patterns like @future(callout=true), Queueable with Database.AllowsCallouts, or Platform Events to perform callouts triggered by triggers. Bulkify requests, use Named Credentials, and ensure proper mocking and error handling in tests.