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
Joseph AJoseph A 

Making HttpRequest "before update" - Callout from triggers not supported

Hi,
I need to run some verifications in trigger "before update" against a remote API and perform future actions based on its response . I found that Apex class function cannot return any response due to synchronous / asynchronous issues between triggers and HttpResponse. Is there any way to go around?

Best Answer chosen by Joseph A
VaasuVaasu
Recursive trigger, account update is calling trigger and future method again. Avoid calling trigger again using static flags

Refer  https://help.salesforce.com/articleView?id=000133752&language=en_US&type=1
 

All Answers

VaasuVaasu
You can not make a callout in trigger context unless it is from Future method. 
Joseph AJoseph A
I know I cannot. I'm asking if there any other solution
Raj VakatiRaj Vakati
 Sample code is here.You can try something like this 

 
trigger HelloWorld on Lead (before update) {
    if(trigger.isupdate && trigger.isbefore){
        //testhttp.getLimits(trigger.new[0].id);
        // System.executeBatch(new CaseQueuebleJob(trigger.new[0].id));
        CaseQueuebleJob job = new CaseQueuebleJob(trigger.new[0].id);
        System.enqueueJob(job);
        
        
    }
    
}
 
public class CaseQueuebleJob implements Queueable, Database.AllowsCallouts{
    public String leadids ;
    public CaseQueuebleJob(String leadids){
        leadids = leadids;
    }
    
    public void execute(QueueableContext context) {
        System.debug('-----leadids'+leadids);
        Http h = new Http();
        HttpRequest httpReq = new HttpRequest();
        httpReq.setMethod('GET');
        httpReq.setEndpoint('http://services.groupkt.com/country/get/iso2code/IN');
        HttpResponse res = h.send(httpReq);
        System.debug('res'+res);
        // return res.getBody();
        Lead l = [Select Id ,Description, Name from Lead Limit 1];
        l.Description = res.getBody();
        update l ;
        
    }
    
}


 
VaasuVaasu
Here are few things.

1. How the trigger gets called? Is it from VFpage/Class or standard page?
2. Can the first call be asynchronous? 

Option 1: If triggers gets called from apex class, then before saving record make a callout and store the response and save record. Now, in trigger you would know the response of the first call so based on that you can make asynchronous callout using future methods.

Option 2: Use Future method to make callout and read the response and make another callout, all calls will be asynchronous so you have to decide if this fits for your scenario.
 
Joseph AJoseph A
I'm new to SF and I assume it called from a standard page. Here is my problem:
When a sales rep trying to update customer email, the system should verify if email doesn't belong to another customer and this verification must be running agains a remote database where email address is primary key. 

Here is what I have so far:
 
trigger EmailChange on Account (before update) {
    for(Account a : trigger.new){
      if( Trigger.oldMap.get( a.Id ).Email__c != Trigger.newMap.get( a.Id ).Email__c ){
         string APIResponse = APIConnect.makePostCallout('email',Trigger.newMap.get( a.Id ).Email__c);
         //a.addError(APIResponse);
         // add more functionality here
         System.debug(APIResponse);
      }
    }
}

public class APIConnect {
    public static string makePostCallout(string mode, string var) {
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndpoint('https://myserver.com/sftest/');
        request.setMethod('POST');
        request.setHeader('Content-Type', 'application/json;charset=UTF-8');
        request.setBody('{"key":"3434545","mode":"'+mode+'","var":"'+var+'"}');
        HttpResponse response = http.send(request);
        return response.getBody();
        // Parse the JSON response
        //if (response.getStatusCode() != 201) {
        //    System.debug('The status code returned was not expected: ' + response.getStatusCode() + ' ' + response.getStatus());
        //} else {
        //    System.debug(response.getBody());
        //    return response.getBody();
        //}
    }        
}

 
VaasuVaasu
 Lets be clear on few things,
  
  You cannot get callout response in trigger in realtime, since it is async response will be asycnhronous.That means in the future method  you have to write a logic to read the response and based on that you can update your account email address field. If it errors out you can send an email or create a new field on account to store the response if you want. 
  
  Here is the sample.
  
trigger EmailChange on Account (before update) {
    for(Account a : trigger.new){
      if( Trigger.oldMap.get( a.Id ).Email__c != Trigger.newMap.get( a.Id ).Email__c ){
         //You cannot get response here, it is async so you cannot read response 
         string APIResponse = APIConnect.makePostCallout('email',Trigger.newMap.get( a.Id ).Email__c,a.Id);
         //a.addError(APIResponse);
         // add more functionality here
         System.debug(APIResponse);

      }
    }
}

public class APIConnect {
    @future(callout=true)
    public static string makePostCallout(string mode, string var,String accountID) {
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndpoint('https://myserver.com/sftest/');
        request.setMethod('POST');
        request.setHeader('Content-Type', 'application/json;charset=UTF-8');
        request.setBody('{"key":"3434545","mode":"'+mode+'","var":"'+var+'"}');
        HttpResponse response = http.send(request);
        //Read response.
        if(sucesfulresponse)
         {
           //Update email address on account 
           Account accRecord  = new Account(id= accountid, Email__c = var);
           update accRecord;
         }
        else
        {
          //Error
          
          //you can send an email
          
          //Or ceatea new field to store the response.
           Account accRecord  = new Account(id= accountid, newfield = response.getBody());
           update accRecord;
          
        }
        

    }        
}
Joseph AJoseph A
Seems to be working. Thanks a lot!
Another question: is there any way to display a system dialog / alert if an update fails?
VaasuVaasu
Not in your scenario, this is Async. You can send out an email if update fails.
Joseph AJoseph A
I'm getting "Update failed.. CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, EmailCHange: execution of BeforeUpdate caused by SystemAsyncException: Fututre method cannot be called from a future or batch method;" when I try to rever changes 
        if (response.getStatusCode() != 201 && response.getBody() == '1'){
            // update success
         }else{
            // update failed
            Account accRecord  = new Account(id= accountid, Email__c = old_email);
            update accRecord;
        } 

Is it because I'm trying to update the same affected row? 

Thanks!
VaasuVaasu
Recursive trigger, account update is calling trigger and future method again. Avoid calling trigger again using static flags

Refer  https://help.salesforce.com/articleView?id=000133752&language=en_US&type=1
 
This was selected as the best answer
Joseph AJoseph A
Thanks a lot
 
VaasuVaasu
Please mark it as solved if you have no fiurther questions.
VaasuVaasu
Joseph,

Why do you have to update it to old email address if it fails? You dont have to do update email address again with the same value as it has not changed.

Recursive flags would not work in this scenario, trigger is getting fired from ASYNC method and that resets all static flags( as new transaction).

you can skip calliing future method by checking system.isFuture(), this tells you if code is executed by future method.

if(!System.isFuture())
   APIConnect.makePostCallout('email',Trigger.newMap.get( a.Id ).Email__c,a.Id);