We use Gravity Forms on our website and it works pretty well. Whenever a form is completed, we receive an email – except sometimes we don’t. Just recently, I missed a few enquiries because of a configuration change on our site.
To stop this from happening, I went looking for alternative notification options for Gravity Forms that didn’t just rely on an email making it from our website to my inbox. I remembered that Zapier had a connector, however I was disappointed to discover that it only works on Developer licenses which cost $199 USD a year, and we’re running a $39 single site license.
Luckily Gravity Forms has some easy to follow API documentation that allow us to connect directly to our site’s forms and entries via REST methods.
This solution demonstrates how to build an Azure Function app in C# that retrieves the entries from a Gravity Form and sends them to Microsoft Flow. A Microsoft Flow checks each entry against a SharePoint list and if it doesn’t exist, it adds it.
The benefits of this solution is that it’s completely serverless and almost free (depending on the App Service plan). Also since it’s in Microsoft Flow, you can do anything you want with the form entries. You could create a task in Planner, add a message to Microsoft Teams channel, or pipe them directly into Dynamics 365 or Mailchimp.
The first step is to enable access to your Gravity Forms via the API.
Enable the Gravity Forms API and retrieve the form info
- Sign into your site’s WordPress Admin Panel and visit the Settings section of Gravity Forms
- Enable the API. Retrieve and make a note of the Public API Key and Private API Key.
- Set the Impersonate account user. Your Function App will have the same form access permissions as the user that you choose here
- Visit the form that you’d like to retrieve the entries for and make a note of the Form ID (eg. 1)
- Make a note of all the fields in the form. We’ll be adding these as columns to a SharePoint List.
- It’s also worth making a note of each field’s corresponding ID (eg. 1.3, 2, 3 etc) since the JSON representation of each field uses this and not the field name.
Create a SharePoint List to receive the form data
- Sign into your SharePoint site with your Office 365 Account.
- Visit Site Contents and create a new SharePoint list with an appropriate name.
- You can rename the Title column to something else if Title isn’t appropriate. I changed mine to First Name.
- Create columns to match the field names in your Gravity Forms form. Here’s the field names and types that we’re using:
Create a Function App in Visual Studio 2017
In previous tutorials, we’ve created Azure Functions directly in the browser. This time we’ll be using Visual Studio to test and deploy our functions.
Open Visual Studio 2017 and make sure that it’s up to at least version 15.3. You’ll need to ensure that Azure Development tooling is installed, and that you can create Azure Function Apps. See here for a list of prerequisites.
- Go to File, New Project, Visual C#, Cloud, Azure Functions then create a Function App and give it a name.
If Visual Studio is completely up to date, you’ve installed all the prerequisites, but you still can’t see an option to create Azure Functions, you may need to go to Tools > Extensions and Updates > Updates > Visual Studio Marketplace and install the Azure Functions and Web Jobs Tools update.
- An Azure Function app is pretty much an Azure Web App, and each Function App can contain multiple functions. To add a Function to our Function App, right click on your project in the Solution Explorer, choose Add, New Item.
- Then select Azure Function and give your function a name – I’ve called this one GravityForms_Enquiries. Click Add.
- Choose Timer trigger. You’ll also want to specify how often you’d like the function app to run using CRON scheduling. The default value means that your function will execute every 5 minutes. In our published function, we’re going to check for form entries every 4 hours. While we’re debugging, we’re checking every minute – just until we’re ready to publish.
- Your Function should look like this
- Copy and paste the following code into your function. Replace the string placeholders in the RunAsync method with your own values, and make sure that you update your namespace and function app name (if you didn’t choose GravityForms_Enquiries too).
using System; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Host; using System.Net.Http; using System.Threading.Tasks; using System.Web; using System.Security.Cryptography; using System.Net.Http.Headers; using System.Text; namespace GCITSFunctions { public static class GravityForms_Enquiries { [FunctionName("GravityForms_Enquiries")] public static void Run([TimerTrigger("0 0 */4 * * *")]TimerInfo myTimer, TraceWriter log) { //Change the Timer Trigger above to "0 * * * * *" when debugging to avoid waiting too long for it to execute. log.Info($"C# Timer trigger function executed at: {DateTime.Now}"); string content = RunAsync().Result; // Remove the comments from the below four lines once you've retrieved the HTTP POST URL from Microsoft Flow and added it in. //HttpClient client = new HttpClient(); //HttpContent jsoncontent = new StringContent(content, Encoding.UTF8, "application/json"); //string flowRequest = "<Enter flow HTTP POST URL HERE>"; //var result = client.PostAsync(flowRequest, jsoncontent).Result; } static async Task<string> RunAsync() { HttpClient client = new HttpClient(); // Add the public and private keys for Gravity Forms string publicKey = "<Enter Gravity Forms Public API Key>"; string privateKey = "<Enter Gravity Forms Private API Key>"; string method = "GET"; // Specify the form ID of the form you're retrieving entries for string formId = "1"; string route = string.Format("forms/{0}/entries", formId); /* Paging specifies the number of entries that will be retrieved from your form in this call, eg. 1000. You can make this higher or lower if you like. It will retrieve the most recent entries first. */ string paging = "&paging[page_size]=1000"; string expires = Security.UtcTimestamp(new TimeSpan(0, 1, 0)).ToString(); string signature = GenerateSignature(publicKey, privateKey, method, route); /* Replace gcits.com with your own domain name. If the call doesn't work initially, you may need to make sure that 'pretty' permalinks are enabled on your site. See here for more information: https://www.gravityhelp.com/documentation/article/web-api/ */ string url = string.Format("//gcits.com/gravityformsapi/{0}?api_key={1}&signature={2}&expires={3}{4}", route, publicKey, signature, expires, paging); client.BaseAddress = new Uri(url); client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var response = await client.GetAsync(client.BaseAddress); string content = response.Content.ReadAsStringAsync().Result; return content; } public static string GenerateSignature(string publicKey, string privateKey, string method, string route) { string expires = Security.UtcTimestamp(new TimeSpan(0, 1, 0)).ToString(); string stringToSign = string.Format("{0}:{1}:{2}:{3}", publicKey, method, route, expires); var sig = Security.Sign(stringToSign, privateKey); return (sig); } } public class Security { public static string UrlEncodeTo64(byte[] bytesToEncode) { string returnValue = System.Convert.ToBase64String(bytesToEncode); return HttpUtility.UrlEncode(returnValue); } public static string Sign(string value, string key) { using (var hmac = new HMACSHA1(Encoding.ASCII.GetBytes(key))) { return UrlEncodeTo64(hmac.ComputeHash(Encoding.ASCII.GetBytes(value))); } } public static int UtcTimestamp(TimeSpan timeSpanToAdd) { TimeSpan ts = (DateTime.UtcNow.Add(timeSpanToAdd) - new DateTime(1970, 1, 1, 0, 0, 0)); int expires_int = (int)ts.TotalSeconds; return expires_int; } } }
- Update the TimerTrigger to ‘0 * * * * *’ while we debug
- Add a reference to your function for System.Web by right clicking on your project and choosing Add, Reference.
- Scroll down to System.Web, check the box and click OK.
- Next we need to add a connection string for an Azure Storage account into the local.settings.json file. You can retrieve a connection string from an existing storage account via the Azure Portal, or by downloading Azure Storage Explorer from www.storageexplorer.com, signing in and copying the Connection String from the bottom left properties section. I recommend downloading Storage Explorer anyway since it’s a handy tool for working with Azure Storage accounts. If you don’t have a storage account, you’ll need to make one.
- Once you’ve got the Connection string, paste it in the AzureWebJobsStorage value of the local.settings.json file.
Get your Gravity Form entries as JSON
In order for us to use your form entries in Microsoft Flow, we’ll need to show Flow what your form entries look like in JSON format. To do this, we’ll use Fiddler.
- Download Fiddler from www.telerik.com/fiddler and install and run it.
- Fiddler allows you to analyse your computer’s internet traffic, as well as a bunch of other things. You might see a lot of activity from irrelevant processes, you can right click and filter these processes out.
- Once your Fiddler stream is a little less busy, we’ll run the function app. Return to Visual Studio and press F5.
- You’ll see the Azure Functions Core Tools window appear. This is a local version of the Azure Functions runtime that allows you to debug Azure Functions on your own computer before you deploy them.
- Wait for your Azure function to execute. It should display some text that looks like this:
- Now switch over to Fiddler and locate the call that it just made to your website. If all goes well, you should see a row with a result of 200 to your domain.
- Click this row, and choose to decode it on the bottom right.
- Select the Raw tab, and triple click the JSON result at the bottom to select it all, then copy this into Notepad for later. This is the JSON representation of your Gravity Forms form entries that we can use in Microsoft Flow.
Create a Microsoft Flow to receive the Gravity Forms entries
- Visit flow.microsoft.com and sign in with your Office 365 account.
- Create a new Blank flow, give it a name and start it with a Request Trigger. Then click Use sample payload to generate schema
- Paste the JSON payload that we saved from Fiddler and click Done.
- Add an action step so that we can save the flow and retrieve the HTTP POST URL. In this example I added a Notification action.
- Click Create Flow, then copy the URL that was created next to HTTP POST URL.
- Switch back over to Visual Studio 2017 and paste the HTTP POST URL in the placeholder for the flowRequest string variable. Next uncomment out the last four lines of the Run method.
- Run the Function again to confirm that it’s sending the JSON payload to Microsoft Flow. You should see a row in Fiddler that looks like this:
- Inspecting the call on the top right under the Raw tab shows that it sent the JSON payload:
- When you return to Microsoft Flow, you should see a recent successful Flow run.
- Open the flow run to see that the payload was received by the Request step.
Use Microsoft Flow to add the entries to a SharePoint list
- Remove the action below the Request trigger and add an Apply to each step. Add the entries output from the popout menu to the ‘Select an output’ field. Then add a SharePoint – Get Items action into the Apply to each step and select or enter your SharePoint site, then choose the SharePoint list you created earlier.
- Click Show advanced options and add GFID eq ‘id’ into the Filter Query field. Where ‘id’ is the id output from the Request trigger. Be sure to include the single quotes. This step checks the SharePoint List for existing entries with the same Gravity Forms entry ID.
- Next add a Condition step, and click Edit in advanced mode. Copy and paste the following into this field:
@empty(body('Get_items')?['value'])
- This checks whether any items were returned from SharePoint that match that Gravity Forms ID. If there weren’t any, we’ll create one.
- Your flow layout should now look like this:
- In the Yes section, add a SharePoint – Create Item action. Select or enter your SharePoint site, and choose the relevant SharePoint list. Refer to your notes on which fields match which field IDs, then drag the form values into the corresponding SharePoint fields.
- I also added an Office 365 – Send an email action below this one so I get an extra email notification. You may want to wait until you’ve already imported all existing form entries before you add this one. If you’ve got hundreds of entries that haven’t been added to SharePoint, you’ll get hundreds of emails.
- Click Update Flow, return to Visual Studio 2017 and run your Function app again (press F5).
- Once it successfully completes, close the Azure Functions Core Tools window and head back over to Microsoft Flow to see it process. It should display the following when it’s done:
- Next, visit your SharePoint list. You should now have the data from all Website Enquiry form entries in a single location. This data can now be used for all sorts of Microsoft Flows and business processes.
Publish your Function App to Azure
To make sure that your form data stays up to date, we need to publish our Function App to Azure.
- Switch to Visual Studio, and update the timer on your function app to ‘0 0 */4 * * *‘ to make sure it doesn’t keep running each minute in the cloud
- Now, right click on your project name and click Publish
- Click Azure Function App, and choose Create New. (If you already have an existing Azure Function App, you can Select Existing, and specify the function app you’d like to deploy to.)
- Since we’re creating a new Azure Function App we need to specify some details. As mentioned earlier, Function Apps are just Azure Web Apps. To deploy them, we need to create or choose an App Service.
- Give your Function App an App Name, select your Azure subscription, choose or create a Resource Group, App Service Plan and Storage Account.
- When creating an App Service plan, you can choose from the following sizes. Pricing varies depending on the underlying VM size, however the Consumption plan costs pretty much nothing. Choosing Consumption doesn’t give you a dedicated VM, and function runs are limited to 5 minutes duration. This applies to all functions within your Function App.
- Once you’re happy with your settings, click OK, then Create, and wait for your App Service to deploy.
- When it finishes, click Publish. Your function app is now deploying to Azure.
- Sign in to https://portal.azure.com to see it in action under Web Apps. By default, your functions are in Read Only mode, and you won’t be able to view or edit the underlying C# code.
- To keep track of your function’s activity, you can see function runs in the Monitor Section.
Leave a Reply
Want to join the discussion?Feel free to contribute!