1

Getting the error below while introducing a new app. The new app is attempting to update all accounts and this is happening. Most likely because it is maxing out the callout limit. I can deactivate it temporarily, but I would prefer to make the the code more stable in order to avoid prevent this from happening again. Any help?

UNIQUE ERRORS UPDATING THE ACCOUNT RECORDS:

AccountChangeToCerbWebhook: execution of AfterUpdate caused by: System.AsyncException: Future method cannot be called from a future or batch method: CerbWebhookRequest.Post(String, String) () Please Specify the Campaign for this Trade Show. Required fields are missing: [Parent_Company__c]

We have a trigger:

    trigger AccountChangeToCerbWebhook on Account (After insert) {
  String webhook_url = 'https://HIDDEN CONTENT';

  for(Account a : Trigger.new) {
    Map<String, Object> fields = a.getPopulatedFieldsAsMap();

    string body = '_=';

    for(String fieldKey : fields.keySet()) {
      Object fieldValue = fields.get(fieldKey);
      String paramValue = '';

      if(fieldValue instanceof Datetime) {
        paramValue = String.valueOf(((DateTime)fieldValue).getTime()/1000);
      } else if(
        fieldValue instanceof Boolean
        || fieldValue instanceof Date
        || fieldValue instanceof Decimal
        || fieldValue instanceof Double
        || fieldValue instanceof ID
        || fieldValue instanceof Integer
        || fieldValue instanceof Long
        || fieldValue instanceof String
        || fieldValue instanceof Time
        ) {
        paramValue = String.valueOf(fieldValue);
      }

      body += '&' + fieldKey + '=' + EncodingUtil.urlEncode(paramValue, 'UTF-8');
    }
      //if(!Test.isRunningTest()){
          CerbWebhookRequest.Post(webhook_url, body);
      //}

  }
}

Here is the class:

    public with sharing class CerbWebhookRequest {
    @future (callout=true)
    static public void Post(String url, String body) {
        HttpRequest req = new HttpRequest();
        HttpResponse res = new HttpResponse();
        Http http = new Http();

        req.setEndpoint(url);
        req.setMethod('POST');
        req.setBody(body);

        try {
            if(!Test.isRunningTest()){
                res = http.send(req);
            }

        } catch(System.CalloutException e) {
            System.debug('Callout error: '+ e);
            System.debug(res.toString());
        }
    }
}
1
  • 1
    Are you sure this is your exact code? The error says after update, whereas your trigger is written after insert. Does this trigger happen to be called from a batch or another future method?
    – Jayant Das
    Jan 16, 2019 at 16:48

2 Answers 2

4

If you are using a batch to update all records, then you cannot call a @future method from within that batch. You should add a method which will do the processing synchronously instead. That way, when you are already asynchronous (such as in a batch), you can just run the synchronous code. That would look roughly like the below:

public class MyClass
{
    static Boolean shouldProcessAsync()
    {
        return !system.isFuture() && !system.isBatch() && !system.isQueueable() &&
            Limits.getLimitFutureCalls() > Limits.getFutureCalls();
    }

    public static void doStuff(List<MyObject__c> records)
    {
        if (records.isEmpty()) return;

        if (shouldProcessAsync())
        {
            doStuffAsync(new Map<Id, SObject>(records).keySet());
        }
        else
        {
            // logic
        }
    }
    @future(callout=true)
    static void doStuffAsync(Set<Id> recordIds)
    {
        doStuff([
            SELECT Name
            FROM MyObject__c
            WHERE Id IN :recordIds
        ]);
    }
}
1
  • I am happy to take a further look in chat.
    – Adrian Larson
    Jan 16, 2019 at 18:42
2

Looks like you have two separate errors here, neither of which is related to the callout limit (although you're right to worry about that; more below).

AccountChangeToCerbWebhook: execution of AfterUpdate caused by: System.AsyncException: Future method cannot be called from a future or batch method: CerbWebhookRequest.Post(String, String)

Your callout logic needs to be context-aware. If you're in a batch class - it sounds like your package is running a batch class which executes updates on Account - you should make the callout synchronously. Adrian's answer describes a good architecture to adopt for this handling.

Please Specify the Campaign for this Trade Show. Required fields are missing: [Parent_Company__c]

This is a different error that typically indicates your existing data does not meet invariants required by your validation rules. Since the Accounts are being updated, the validation rules will all be evaluated and will block the update if they add an error. You can fix this in several ways.

  1. Fix the data.
  2. Deactivate the validation rule.
  3. Add an exclusion to the validation rule so that it does not run on a specific User or Profile, ideally by referencing a Custom Permission that you place on the data load or integration user via a Permission Set.

Most likely because it is maxing out the callout limit

It's not, but you're right to worry about that because you're making one callout per Account, and the callout limit is 50 per transaction. So if the batch class is running at a batch size of 200, you have a problem there. If you have control over the batch size, you can turn it down at the cost of increased processing time.

The integration architecture you're using is inherently limited, though. It's never a good idea to fire one future call per transaction record.

4
  • I just wrote a comment on something similar, My guess for the error since the error says after update but the trigger fires in after insert, is that there is a different trigger calling a future method to create an account which then fires the account trigger causing the first error.
    – gNerb
    Jan 16, 2019 at 16:54
  • Oh, nice catch. I would be willing to bet that the poster has an after update trigger that calls the exact same webhook in the same way, and his "new app" is running a batch process that executes update DML. We'll see.
    – David Reed
    Jan 16, 2019 at 16:55
  • I didn't work on this project so I'm doing some digging here. I don't see an after update, but see what you mean. The new app may have the after update, but the code is hidden.
    – user56320
    Jan 16, 2019 at 17:17
  • Could you edit your question to include a complete stack trace?
    – David Reed
    Jan 16, 2019 at 17:19

You must log in to answer this question.