Azure Logic Apps — Calling Azure Functions

Integrating Serverless Code into Workflows


Introduction

Azure Functions provide a powerful way to run custom code in response to events or on a schedule. When combined with Logic Apps, you can create sophisticated workflows that leverage the strengths of both platforms — Logic Apps for orchestration and integration, and Azure Functions for complex business logic.

This comprehensive guide covers:

  • Calling functions — Invoking functions from Logic Apps
  • Authentication — Using managed identity for secure access
  • Data passing — Sending and receiving data between systems
  • Error handling — Managing function failures
  • Real-world patterns — Common integration scenarios

How It Works

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│               LOGIC APPS + AZURE FUNCTIONS                          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │                   LOGIC APP WORKFLOW                        │   │
│   │                                                             │   │
│   │   ┌──────────┐                                              │   │
│   │   │ Trigger  │──────┐                                       │   │
│   │   └──────────┘      │                                       │   │
│   │                     ▼                                       │   │
│   │   ┌──────────────────────────────┐                          │   │
│   │   │   Invoke Azure Function      │                          │   │
│   │   │   - URL: /api/processorder   │ ──────────────────────┐  │   │
│   │   │   - Method: POST             │                       │  │   │
│   │   │   - Body: {order data}       │                       │  │   │
│   │   └──────────────────────────────┘                       │  │   │
│   │                     │                                    │  │   │
│   │                     ▼                                    │  │   │
│   │   ┌──────────────────────────────┐                       │  │   │
│   │   │   Process Response           │                       │  │   │
│   │   │   - Use function output      │                       │  │   │
│   │   │   - Continue workflow        │                       │  │   │
│   │   └──────────────────────────────┘                       │  │   │
│   └──────────────────────────────────────────────────────────│──┘   │
│                                │                                │   │
│                                │ HTTPS                          │   │
│                                ▼                                │   │
│   ┌──────────────────────────────────────────────────────────│──┐   │
│   │                   AZURE FUNCTION                            │   │
│   │                                                             │   │
│   │   [HttpTrigger] public async Task<IActionResult>            │   │
│   │   (ProcessOrderRequest request)                             │   │
│   │                                                             │   │
│   │   1. Validate input                                         │   │
│   │   2. Process business logic                                 │   │
│   │   3. Call external services                                 │   │
│   │   4. Return result                                          │   │
│   └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Adding Azure Function Action

Step-by-Step

  1. Open Logic App in Designer
  2. Add new action
  3. Search for "Azure Functions"
  4. Select "Azure Functions" connector
  5. Choose your Function App
  6. Select the function to invoke
  7. Configure HTTP method and parameters

Basic Configuration

{
  "Invoke_Process_Order": {
    "type": "AzureFunction",
    "inputs": {
      "function": {
        "appName": "order-processing-app",
        "functionName": "ProcessOrder",
        "httpMethod": "POST"
      },
      "body": {
        "orderId": "@triggerBody()?['orderId']",
        "customerId": "@triggerBody()?['customerId']",
        "items": "@triggerBody()?['items']"
      }
    }
  }
}

Authentication Options

Option 1: Function Key (Basic)

{
  "Invoke_With_Key": {
    "type": "AzureFunction",
    "inputs": {
      "function": {
        "appName": "my-function-app",
        "functionName": "MyFunction",
        "apiKey": {
          "name": "default",
          "value": "@appsettings('MyFunctionKey')"
        }
      },
      "method": "POST",
      "body": "@triggerBody()"
    }
  }
}

Option 2: Managed Identity (Recommended)

Step 1: Enable Managed Identity on Logic App

# Enable system-assigned identity
az logicapp update \
  --name my-logic-app \
  --resource-group my-rg \
  --identity systemassigned

# Get principal ID
az logicapp show \
  --name my-logic-app \
  --resource-group my-rg \
  --query "identity.principalId"

Step 2: Enable Authentication on Function

public static class Startup : FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder builder)
    {
        builder.Services.AddAuthentication("Bearer")
            .AddMicrosoftIdentityWebApi(
                Configuration["AzureAD:Instance"],
                Configuration["AzureAD:TenantId"],
                Configuration["AzureAD:ClientId"]);
        
        builder.Services.AddAuthorization();
    }
}
public class ProcessOrderFunction
{
    [FunctionName("ProcessOrder")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
        ILogger log)
    {
        // Function is secured with Azure AD
        // Logic App uses managed identity to call
        var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
        // Process...
        return new OkObjectResult(result);
    }
}

Step 3: Add Function Action

In Logic Apps, the action will automatically use managed identity when no API key is specified.


Passing Data to Functions

Sending JSON Data

{
  "body": {
    "orderId": "@triggerBody()?['orderId']",
    "customer": {
      "name": "@triggerBody()?['customerName']",
      "email": "@triggerBody()?['customerEmail']"
    },
    "items": "@triggerBody()?['items']",
    "totalAmount": "@triggerBody()?['total']"
  }
}

Dynamic Content from Multiple Sources

{
  "body": {
    "metadata": {
      "correlationId": "@guid()",
      "timestamp": "@utcNow()",
      "source": "logic-app"
    },
    "order": "@triggerBody()",
    "configuration": {
      "validateStock": true,
      "notifyCustomer": true
    }
  }
}

Handling Function Responses

Access Response Data

{
  "Process_Function_Response": {
    "type": "AzureFunction",
    "inputs": {
      "function": {
        "appName": "order-app",
        "functionName": "ProcessOrder",
        "method": "POST"
      },
      "body": "@triggerBody()"
    },
    "runAfter": {}
  },
  "Check_Result": {
    "type": "Condition",
    "expression": "@equals(outputs('Process_Function_Response').statusCode, 200)",
    "actions": {
      "Continue": {
        "type": "Response",
        "inputs": {
          "body": "@outputs('Process_Function_Response').body"
        }
      }
    },
    "else": {
      "actions": {
        "Handle_Error": {
          "type": "Terminate",
          "inputs": {
            "statusCode": "@outputs('Process_Function_Response').statusCode",
            "message": "@outputs('Process_Function_Response').body"
          }
        }
      }
    }
  }
}

Parse Function Response

{
  "Get_Order_Result": {
    "type": "AzureFunction",
    "inputs": {
      "appName": "orders-app",
      "functionName": "GetOrderDetails",
      "method": "GET"
    },
    "queries": {
      "orderId": "@triggerBody()?['orderId']"
    }
  },
  "Extract_Data": {
    "type": "ParseJson",
    "inputs": {
      "content": "@outputs('Get_Order_Result').body"
    },
    "schema": {
      "type": "object",
      "properties": {
        "orderId": { "type": "string" },
        "status": { "type": "string" },
        "total": { "type": "number" }
      }
    }
  }
}

Error Handling

Retry on Failure

{
  "Invoke_With_Retry": {
    "type": "AzureFunction",
    "inputs": {
      "function": {
        "appName": "my-app",
        "functionName": "ProcessData"
      },
      "method": "POST",
      "body": "@triggerBody()"
    },
    "runAfter": {},
    "retryPolicy": {
      "type": "fixed",
      "count": 3,
      "interval": "PT5S"
    }
  }
}

Catch and Handle Errors

{
  "Try_Invoke": {
    "type": "AzureFunction",
    "inputs": { ... },
    "runAfter": {}
  },
  "Catch_Errors": {
    "type": "Scope",
    "actions": {
      "Log_Error": {
        "type": "Compose",
        "inputs": {
          "error": "@outputs('Try_Invoke').error.message",
          "timestamp": "@utcNow()"
        }
      },
      "Notify_Team": {
        "type": "SendEmail",
        "inputs": {
          "body": "Function call failed: @{outputs('Try_Invoke').error.message}"
        }
      }
    }
  }
}

Real-World Patterns

Pattern 1: Complex Business Logic

┌─────────────────────────────────────────────────────┐
│          COMPLEX ORDER PROCESSING                   │
├─────────────────────────────────────────────────────┤
│                                                     │
│  1. HTTP Trigger (new order)                        │
│  2. Validate Order (Logic Apps)                     │
│  3. Invoke Function: Calculate Total                │
│     - Apply discounts                               │
│     - Calculate tax                                 │
│     - Determine shipping                            │
│  4. Invoke Function: Reserve Inventory              │
│     - Check stock levels                            │
│     - Reserve items                                 │
│  5. Save to Database (Logic Apps)                   │
│  6. Send confirmation (Logic Apps)                  │
│                                                     │
└─────────────────────────────────────────────────────┘

Pattern 2: Data Transformation

{
  "Transform_Data": {
    "type": "AzureFunction",
    "inputs": {
      "appName": "data-transformer",
      "functionName": "TransformOrder",
      "method": "POST"
    },
    "body": {
      "inputFormat": "legacy",
      "outputFormat": "modern",
      "data": "@triggerBody()"
    }
  }
}

Pattern 3: External API Integration

{
  "Call_External_API": {
    "type": "AzureFunction",
    "inputs": {
      "appName": "api-integration",
      "functionName": "SyncWithCRM",
      "method": "POST"
    },
    "body": {
      "customerId": "@triggerBody()?['customerId']",
      "action": "update",
      "data": "@triggerBody()"
    }
  }
}

Best Practices

PracticeDescription
Use Managed IdentityAvoid storing API keys
Design for failureImplement retry and error handling
Keep functions focusedSingle responsibility principle
Use proper loggingTrack function invocations
Version functionsUse function app versioning

Performance Considerations

  • Use async/await in functions for long operations
  • Consider function timeout settings
  • Batch multiple operations when possible
  • Cache function app warm

Related Topics


Azure Integration Hub - Intermediate Level