Share via

Azure function to Salesforce

Rohit Kulkarni 731 Reputation points
2025-09-11T08:25:59.22+00:00

Hello Team,

I need to create the Azure function app for the following steps.

  1. To connect to salesforce.
  2. Whenever a new entry occurs in Salesforce, it should trigger the Function App, which in turn will execute the ADF pipeline.

Please let me know any link for this.

Thanks in advance

Rohit

Azure Functions
Azure Functions

An Azure service that provides an event-driven serverless compute platform.


1 answer

Sort by: Most helpful
  1. Rakesh Mishra 9,695 Reputation points Microsoft External Staff Moderator
    2025-09-11T17:38:59.5266667+00:00

    Hi Rohit Kulkarni,
    Thank you for asking question here. Welcome to Microsoft Q&A platform.

    There is no trigger for Salesforce in Function App. We need to leverage HTTP trigger for Function App and use APIs to trigger ADF run.

    Another easy/low code solution could be instead of using function app, you can use logic app. Logic app has a Salesforce Connector and can call HTTP REST API of ADF for triggering the run.

    Since you specifically asked about Function app, below could be the steps for possible solution.

    Please let me know how it goes and if there are any further question on this.

    Steps for possible solution
    1. In Azure Portal: create Function App (Linux/Windows as you prefer) --> enable System-assigned managed identity.
    2. On the Data Factory resource --> Access control (IAM) --> Add role assignment --> give the Function App the Data Factory Contributor (or Contributor) role at the Data Factory scope. Reference
    3. Deploy the C# Function below (isolated worker) and publish. Copy the Function endpoint URL (it contains a key ?code=...).
    4. In Salesforce, create a Workflow/Flow with an Outbound Message that points to the Function URL (include ?code= query if you used function key). Download WSDL for field layout if needed. Reference
    5. Test by creating a record; check Salesforce Outbound Message Delivery History and Function logs; confirm ADF pipeline run in ADF Monitor. Reference
    Sample Code

    This example extracts the Id element(s) from the outbound SOAP, sends a createRun to ADF with recordId parameter, logs response, and returns the SOAP ACK Salesforce expects.

    // Program.cs / Function file (isolated worker)
    using System.Net;
    using System.Xml.Linq;
    using Azure.Core;
    using Azure.Identity;
    using Microsoft.Azure.Functions.Worker;
    using Microsoft.Azure.Functions.Worker.Http;
    using Microsoft.Extensions.Logging;
    using System.Text;
    using System.Text.Json;
    namespace SalesforceToAdf
    {
        public class SalesforceOutboundListener
        {
            private readonly ILogger _logger;
            private static readonly HttpClient _http = new HttpClient();
            // Replace these values or load from configuration (app settings)
            private readonly string subscriptionId = Environment.GetEnvironmentVariable("ADF_SUBSCRIPTION_ID")!;
            private readonly string resourceGroup = Environment.GetEnvironmentVariable("ADF_RESOURCE_GROUP")!;
            private readonly string factoryName = Environment.GetEnvironmentVariable("ADF_FACTORY_NAME")!;
            private readonly string pipelineName = Environment.GetEnvironmentVariable("ADF_PIPELINE_NAME")!;
            private readonly string apiVersion = "2018-06-01"; // ADF createRun API version
            public SalesforceOutboundListener(ILoggerFactory loggerFactory) =>
                _logger = loggerFactory.CreateLogger<SalesforceOutboundListener>();
            [Function("SalesforceOutboundListener")]
            public async Task<HttpResponseData> Run(
                [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
                FunctionContext context)
            {
                string body = await new StreamReader(req.Body).ReadToEndAsync();
                _logger.LogInformation("Inbound SOAP length: {len}", body?.Length ?? 0);
                // Parse SOAP XML for Salesforce record Id(s)
                List<string> recordIds = new();
                try
                {
                    var doc = XDocument.Parse(body);
                    recordIds = doc.Descendants()
                                   .Where(e => string.Equals(e.Name.LocalName, "Id", StringComparison.OrdinalIgnoreCase))
                                   .Select(e => e.Value.Trim())
                                   .Where(v => !string.IsNullOrEmpty(v))
                                   .Distinct()
                                   .ToList();
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Failed to parse SOAP body.");
                }
                // If none found, continue but log
                if (recordIds.Count == 0)
                {
                    _logger.LogWarning("No Salesforce Id found in payload.");
                }
                else
                {
                    _logger.LogInformation("Found Salesforce Id(s): {ids}", string.Join(", ", recordIds));
                }
                // Call ADF REST API to start pipeline (one run per notification; pass first Id as example)
                string runId = string.Empty;
                if (recordIds.Any())
                {
                    try
                    {
                        // Acquire token using managed identity / DefaultAzureCredential
                        var credential = new DefaultAzureCredential();
                        var tokenRequestContext = new TokenRequestContext(new[] { "https://management.azure.com/.default" });
                        AccessToken token = await credential.GetTokenAsync(tokenRequestContext);
                        // Construct ADF createRun URL
                        var createRunUrl = $"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.DataFactory/factories/{factoryName}/pipelines/{pipelineName}/createRun?api-version={apiVersion}";
                        // Example request body: pass recordId as a pipeline parameter named 'recordId'
                        var bodyObj = new
                        {
                            parameters = new Dictionary<string, object>
                            {
                                ["recordId"] = recordIds.First()
                            }
                        };
                        string jsonBody = JsonSerializer.Serialize(bodyObj);
                        using var message = new HttpRequestMessage(HttpMethod.Post, createRunUrl);
                        message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token.Token);
                        message.Content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
                        var resp = await _http.SendAsync(message);
                        string respText = await resp.Content.ReadAsStringAsync();
                        if (resp.IsSuccessStatusCode)
                        {
                            _logger.LogInformation("ADF createRun succeeded: {status}. Response: {resp}", resp.StatusCode, respText);
                            // Response contains runId JSON like {"runId":"..."}
                            using var docJson = JsonDocument.Parse(respText);
                            if (docJson.RootElement.TryGetProperty("runId", out var runIdEl))
                                runId = runIdEl.GetString() ?? string.Empty;
                        }
                        else
                        {
                            _logger.LogWarning("ADF createRun failed: {status}. Body: {body}", resp.StatusCode, respText);
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "Error when invoking ADF REST API.");
                    }
                }
                // Return SOAP ACK expected by Salesforce
                var response = req.CreateResponse(HttpStatusCode.OK);
                response.Headers.Add("Content-Type", "text/xml; charset=utf-8");
                string ackXml =
    @"<?xml version=""1.0"" encoding=""UTF-8""?>
    <soapenv:Envelope xmlns:soapenv=""http://schemas.xmlsoap.org/soap/envelope/"">
      <soapenv:Body>
        <notificationsResponse xmlns=""http://soap.sforce.com/2005/09/outbound"">
          <Ack>true</Ack>
        </notificationsResponse>
      </soapenv:Body>
    </soapenv:Envelope>";
                await response.WriteStringAsync(ackXml);
                return response;
            }
        }
    }
    

    App settings to configure (Function App -> Configuration)

    • ADF_SUBSCRIPTION_ID = your subscription id
    • ADF_RESOURCE_GROUP = resource group where ADF exists
    • ADF_FACTORY_NAME = Data Factory name
    • ADF_PIPELINE_NAME = pipeline to trigger
    Best-practices
    • Salesforce retries outbound messages — make your processing idempotent (store & dedupe NotificationId or recordId).
    • If you prefer not to handle SOAP, you can build an Apex callout or Flow that posts JSON to the Function instead (then reply with HTTP 200 JSON).
    References

    Was this answer helpful?

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.