Skip to content

Callout

The Callout step sends a mid-call request to a Salesforce Apex REST class and makes the response available to subsequent steps in the flow. Use it when routing logic depends on Salesforce record data that cannot be determined from the caller’s phone number alone.

Common uses:

  • Automatically create a Salesforce Case at the start of a call
  • Look up an existing Case and route to the owning agent
  • Look up an account’s tier, territory, or status to drive Branch routing
  • Validate an account number entered by the caller via Prompt
  • Retrieve a prioritized list of agents for a Loop step

Configuration

Callout step configured with caller phone and queue name inputs, and a Case output variable
FieldDescription
PathThe URL path of the Apex REST class — must match the urlMapping in your @RestResource annotation (e.g., RingDNACaseCreate).
Apex InputKey-value pairs passed to the Apex method as named parameters. Values can be hardcoded strings or call flow variables (e.g., {{call.from}}). Each key must match a parameter name in the method signature exactly.
Saved OutputThe variable namespace for the response map. Use dot notation to reference individual keys in subsequent steps: {{outputName.keyName}}.

Apex class requirements

Every Callout class follows the same structural pattern. Revenue.io calls it via @HttpPost and passes your Apex Input key-value pairs as named method parameters.

@RestResource(urlMapping='/YourClassName/*')
global class YourClassName {

    @HttpPost
    global static Map<String, String> yourMethod(String param1, String param2) {
        Map<String, String> response = new Map<String, String>();

        // Your logic here

        return response;
    }
}

Key requirements:

  • The urlMapping path (without the leading / and trailing /*) is what you enter in the Callout step’s Path field.
  • The method must be global static and return Map<String, String>.
  • Each Apex Input key you configure in the step must match a parameter name in the method signature exactly — including case.
  • The method name can be anything; Revenue.io calls the @HttpPost method on the class.
  • Every key you add to the response map becomes available as {{savedOutput.keyName}} in subsequent steps.

Example 1: Create a Case on every call

This class creates a new Salesforce Case for each inbound call, links it to the matching Contact and Account if found via SOSL, and returns the Case number for use in downstream steps.

Callout step configuration:

FieldValue
PathRingDNACaseCreate
Apex Inputcaller = {{call.from}}
Apex InputcallKey = {{call.callKey}}
Apex Inputpriority = High (or any valid Case Priority picklist value)
Apex InputrecTypeId = False (or a valid 15/18-character Salesforce Record Type ID)
Saved OutputnewCase

Apex class:

@RestResource(urlMapping='/RingDNACaseCreate/*')
global class RingDNACaseCreate {

    @HttpPost
    global static Map<String, String> createCase(String caller, String callKey, String priority, String recTypeId) {
        Map<String, String> response = new Map<String, String>();

        // Strip country prefix for SOSL phone search
        String cleanPhone = caller.deleteWhitespace().remove('+1').remove('+');

        // Find most-recently-created Contact matching this phone number
        List<List<SObject>> results = [FIND :cleanPhone IN PHONE FIELDS
            RETURNING Contact(Id, Email, AccountId ORDER BY CreatedDate DESC)];
        Contact con = ((Contact[])results[0]).size() > 0 ? ((Contact[])results[0])[0] : null;

        // Validate and apply Record Type if provided
        Boolean useRecType = String.isNotBlank(recTypeId)
            && !recTypeId.containsIgnoreCase('False')
            && !recTypeId.containsIgnoreCase('None');

        // Create the Case
        Case c = new Case(
            Status        = 'Open',
            Origin        = 'Phone',
            Subject       = 'Inbound Call - ' + cleanPhone,
            Description   = 'Inbound Call - ' + cleanPhone + ' - Call ID: ' + callKey,
            SuppliedPhone = cleanPhone,
            Priority      = priority,
            ContactId     = con != null ? con.Id : null,
            AccountId     = con != null ? con.AccountId : null
        );
        if (useRecType) c.RecordTypeId = recTypeId;
        insert c;

        c = [SELECT CaseNumber FROM Case WHERE Id = :c.Id];
        response.put('caseNumber', c.CaseNumber);
        return response;
    }
}

Using the output:

Reference {{newCase.caseNumber}} in any step that follows the Callout — for example, in an SMS step to send the caller their Case number:

“Your support case {{newCase.caseNumber}} has been created. An agent will be in touch shortly.”

Example 2: Look up an existing open Case

This class searches for an open Case matching the caller’s phone number. It returns whether a Case was found, and if so, its number, current status, and owner — enabling the flow to route the caller directly to the agent who already owns their Case.

Callout step configuration:

FieldValue
PathRingDNAAutoCaseLookup
Apex Inputcaller = {{call.from}}
Saved Outputlookup

Apex class:

@RestResource(urlMapping='/RingDNAAutoCaseLookup/*')
global class RingDNAAutoCaseLookup {

    @HttpPost
    global static Map<String, String> findAutoCase(String caller) {
        Map<String, String> response = new Map<String, String>();

        // Strip country prefix for SOSL phone search
        String cleanPhone = caller.remove('+1').remove('+').trim().deleteWhitespace();

        // Find most recent open Case with a matching SuppliedPhone
        List<List<SObject>> results = [FIND :cleanPhone IN PHONE FIELDS
            RETURNING Case(Id, OwnerId, CaseNumber, Status, SuppliedPhone
                WHERE IsClosed = false
                AND SuppliedPhone = :cleanPhone
                ORDER BY CreatedDate DESC LIMIT 1)];
        Case[] cases   = (Case[])results[0];
        Case caseFound = cases.size() > 0 ? cases[0] : null;

        if (caseFound != null) {
            response.put('caseExists', 'TRUE');
            response.put('caseNumber', caseFound.CaseNumber);
            response.put('caseOwner',  caseFound.OwnerId);
            response.put('caseStatus', caseFound.Status);
        } else {
            response.put('caseExists', 'FALSE');
        }

        return response;
    }
}

Using the output:

Place a Branch step after the Callout and evaluate {{lookup.caseExists}}:

  • Branch value TRUE → Route to the case-owning agent using {{lookup.caseOwner}} in a Dial step set to PhoneNumber or by passing the ID to a second Callout. Optionally greet the caller with their Case number: “Welcome back. I’ll connect you to the agent handling Case {{lookup.caseNumber}}.”
  • Branch value FALSE → Route to a general support queue as normal.

See Call Flow Variables for full documentation on referencing Callout output in downstream steps.

Example 3: Priority queue routing by membership level

This example routes Gold and Platinum members to a dedicated priority queue using a single shared Apex endpoint called twice — first attempting to identify the caller automatically by phone, then falling back to a member number entered via Prompt.

Flow design:

  1. Callout 1 — Pass the caller’s phone number to MemberLookup. If matched, return their membership level and identified = TRUE.
  2. Branch on identified — If TRUE, proceed directly to routing. If FALSE, prompt for a member number.
  3. Prompt — Ask the caller to enter their member number. Save the response as memberNumber.
  4. Callout 2 — Pass the entered member number to the same MemberLookup endpoint. Return their membership level.
  5. Branch on memberLevel — Route Gold and Platinum to a priority queue; all others to the standard queue.

Because both callout steps use the same Saved Output name (memberLookup), the final Branch always evaluates {{memberLookup.memberLevel}} regardless of which path the call took.


The MemberLookup Apex class

One class handles both lookups. It checks whether memberNumber is populated — if so, it queries by member number; otherwise it searches by phone. Both paths return the same identified and memberLevel keys.

@RestResource(urlMapping='/MemberLookup/*')
global class MemberLookup {

    @HttpPost
    global static Map<String, String> findMember(String caller, String memberNumber) {
        Map<String, String> response = new Map<String, String>();

        Contact con = null;

        if (String.isNotBlank(memberNumber)) {
            // Called after Prompt — look up by member number
            List<Contact> contacts = [
                SELECT Membership_Level__c
                FROM Contact
                WHERE Member_Number__c = :memberNumber
                LIMIT 1
            ];
            if (!contacts.isEmpty()) con = contacts[0];
        } else {
            // Initial call — look up by phone number
            String cleanPhone = caller.deleteWhitespace().remove('+1').remove('+');
            List<List<SObject>> results = [FIND :cleanPhone IN PHONE FIELDS
                RETURNING Contact(Id, Membership_Level__c ORDER BY CreatedDate DESC LIMIT 1)];
            List<Contact> contacts = (List<Contact>)results[0];
            if (!contacts.isEmpty()) con = contacts[0];
        }

        if (con != null && String.isNotBlank(con.Membership_Level__c)) {
            response.put('identified', 'TRUE');
            response.put('memberLevel', con.Membership_Level__c);
        } else {
            response.put('identified', 'FALSE');
            response.put('memberLevel', 'Standard');
        }

        return response;
    }
}

Callout 1: Identify by phone

Step configuration:

FieldValue
PathMemberLookup
Apex Inputcaller = {{call.from}}
Apex InputmemberNumber = (leave blank)
Saved OutputmemberLookup

After this step: Place a Branch step evaluating {{memberLookup.identified}}.

  • TRUE — Caller matched by phone. Skip to the final routing branch.
  • FALSE — No match. Connect to a Prompt step.

Prompt: Collect member number

On the FALSE branch, place a Prompt step:

  • Prompt audio: “We couldn’t find your account. Please enter your member number followed by the pound sign.”
  • Save Response key: memberNumber

Connect the Any Response path to Callout 2.


Callout 2: Identify by member number

Step configuration:

FieldValue
PathMemberLookup
Apex Inputcaller = {{call.from}}
Apex InputmemberNumber = {{memberNumber}}
Saved OutputmemberLookup

The class receives a non-blank memberNumber and takes the SOQL branch instead of the SOSL phone search.


Final routing branch

Both paths end with the same Branch step evaluating {{memberLookup.memberLevel}}:

BranchValueDestination
Priority membersGold,PlatinumDial → Priority queue
Everyone elseAny ValueDial → Standard queue
Membership_Level__c and Member_Number__c are custom Salesforce field API names used in this example. Replace them with the actual API names from your org. The membership level values (Gold, Platinum) must also match your picklist values exactly — Branch comparisons are case-sensitive.