function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Amir 3Amir 3 

Apex trigger callout firing twice

Hello all,
We are encoutring a wierd behavior in Salesforce trigger. The trigger seems to run once but one out of 2 callouts seems to be sent twice without any infomation in the debug logs.

The idea:
When a certain object’s attribute changes -send an HTTP request to two endpoints using Apex trigger.
The Apex code will first send a GET request to some endpoint.
Then, it will send a POST request to some different endpoint.

Implementation:
We added a trigger on object A - on update (when status changes to complete).
We added an Apex class that has a future method (decorated with @future(callout=true)) that will call two endpoints.
Inside the trigger a condition is checked and a callout is sent if the condition is passed.
The endpoints were added to the remote sites of the environment.

Problem description:
The receiving side reports two requests were received at the second endpoint (both requests received are identical) while we only see evidence of one http send in the debug logs.

Details:
The behaviour occurs even when triggered from the layout (status change to complete).
The time between the calls varies from 4 to 10 seconds.
When we look at the logs we only see one request sent, always.
When we send the request to that endpoint not from salesforce only one request is sent (tested using the same endpoints).
When we changed the endpoint to a different one we only see one request (when endpoint is hosted on our side, without any logic).
The code is extremely simple and does not contain any complicated logic.
We couldn’t find in the documentations any explanation to such behaviour.
The first GET request always fires once, the problem is only relevant when calling the second endpoint.

We can't find any reason for this behaviour especially not from our code.
Do you know of any reason why this should happen?
Ravi Dutt SharmaRavi Dutt Sharma
Hi Amir,

Can you please post the code here - the trigger as well as the class which contains future method, so that it will be easier to find out the problem.
paul diracpaul dirac
Did you check if your trigger has a recursive behavior?
If this is the case you can find the solution here:

https://developer.salesforce.com/forums/?id=906F0000000Qtw4IAC
Amir 3Amir 3

@paul dirac  in the logs we only see one call always. the trigger only calls an API and finishes. 

@Ravi Dutt Sharma here is the code:

The trigger:

trigger IntegrationTrigger on Investigation__c (after update) {

   for(Investigation__c investigationNew : Trigger.new)
    {

     Investigation__c investigationOld = Trigger.oldMap.get(investigationNew.ID);
      String oldStatus = investigationOld.Status__c;
      String newStatus = investigationNew.Status__c;
      String externalID = investigationNew.ExternalID__c;
      if (oldStatus !='Completed' && newStatus == 'Completed' && externalID != NULL && externalID != '')
      {
       String token=null;
       IntegrationProcessFailure__c orFailures = new IntegrationProcessFailure__c();
       orFailures.Investigation__c = investigationNew.ID;   
       try{
         Integration.sendInvestigationData(investigationNew.ID);
       }catch(Exception ex){          
         Integration.logError(orFailures, 'Unkonw error has occured: ' + String.valueOf(ex));
         return;
       }
       
      }
    }
    
}



And the class in charge of the API calling:

public class Integration
{

  @future(callout=true)
  public static void sendInvestigationData(String investigationID)
  {
     IntegrationProcessFailure__c orFailures = new IntegrationProcessFailure__c();    
     HttpRequest req = new HttpRequest();
     setRequestAuth(req);
     req.setEndpoint('https://redacted”’);
     req.setMethod('GET');
     req.setHeader('Accept', 'application/json');
     Http http = new Http();
     HTTPResponse res= null;
     try{
       res = http.send(req);
     }catch(Exception ex){
       logError(orFailures, 'Failed to get token! ' + String.valueOf(ex));  
       return;  
     }
     if (res.getStatusCode() != 200) {
      logError(orFailures, 'Failed to get token! Recieved Status code: ' + String.valueOf(res.getStatusCode()));
      return;  
     }

     Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
     String token = (String) results.get('token');
     try{
        sendData(investigationID, token);
     }catch(Exception ex){
        logError(orFailures, 'Unknown error has occured! ' + String.valueOf(ex));   
     }

   public static void setRequestAuth(HttpRequest req)  
   {
     String username = 'redacted';
     String password = 'redacted';
     Blob headerValue = Blob.valueOf(username + ':' + password);
     String authorizationHeader = 'Basic ' + EncodingUtil.base64Encode(headerValue);
     req.setHeader('Authorization', authorizationHeader);
   }
   
   public static void logError(IntegrationProcessFailure__c orFailures, String msg)  
   {
      orFailures.Error_Reason__c = msg;
      insert orFailures;
      sendMail(orFailures);

   }
   
   public static void sendMail(IntegrationProcessFailure__c orFailures)
   {
     List<String> sendTo = new List<String>{‘amir@amir.amir’}; //redacted
     List<Messaging.SingleEmailMessage> mails = new List<Messaging.SingleEmailMessage>();
     Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
     mail.setCharset('UTF-8');
     mail.setToAddresses(sendTo);
     mail.setSenderDisplayName(UserInfo.getName());
     mail.setSubject('Triggered Failed!');
     String htmlBody = '<h4>This is a representation of the IntegrationProcessFailure__c Object: \n';
     htmlBody += JSON.serializePretty(orFailures);
     htmlBody+='</h4>';
     mail.setHtmlBody(htmlBody);
     mails.add(mail);
     Messaging.sendEmail(mails);
   
   }
    
    
   
   public static void sendData(String investigationID, String token)
   {
     String userID = userinfo.getuserID();
     String investigationBody = UtilsObject.GetInvestigationJSON(investigationID);  
     IntegrationProcessFailure__c orFailures = new IntegrationProcessFailure__c();
     orFailures.Investigation__c = investigationID;
     orFailures.Body_Sent__c = investigationBody;
     orFailures.User_ID__c = userID;
     String federationID = [select FederationIdentifier from User where id =: userID].FederationIdentifier;
    if(federationID == null){
      return;
    } 
     
     if(token == null) {
      logError(orFailures, 'getToken request Failed!');
      return;
     }
     
     String endpoint = 'https://redacted.com?user=' + federationID;
     HttpRequest req = new HttpRequest();
     req.setEndpoint(endpoint);
     req.setMethod('POST');
     req.setBody(investigationBody);
     req.setHeader('Authorization','Bearer ' + token);
     req.setHeader('Accept', 'application/json');
     req.setHeader('Content-Type', 'application/json');
     Http http = new Http();
     HTTPResponse res = null;
     try{
      res = http.send(req);
     }catch(Exception ex){
       logError(orFailures, 'Failed to send Data. Error is ' + String.valueOf(ex));
       return;
     }
     if (res.getStatusCode() != 200) {
      logError(orFailures, 'Failed to send Data! Recieved Status code: ' + String.valueOf(res.getStatusCode()));
      return;  
     }
  
     Map<String, Object> responseBody = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
     orFailures.Response__c = res.getBody();
     if (res.getStatusCode() != 200){
       logError(orFailures, 'Status Code is not 200. Received ' + String.valueOf(res.getStatusCode()));
       return;
     }
     
     if(responseBody.get('Success') == false){
       String msg = (String)responseBody.get('Message');
       logError(orFailures, msg);
       return;
     }
   }
}

paul diracpaul dirac
I had issues in the past performing DML (insert in this case) in a @future class that is launched from a trigger. I wonder that you didn't get any other error (pending operations).
I will also check if the behavior of the service is the same if launched out of the trigger context (fe.g. rom postman i mean) in order to exclude the possibility.