Connect to Zapier using .Net WebHooks as RESThooks

I am creating the "Zap App" and I wonder if anyone did this using the new .Net Webhooks. They seem to have the “template” requested by RESTHooks, by the Subcription / Publish mechanism. There are not many examples of its work, and I wanted to check before I spent days on its implementation and found that it was incompatible.

Actual code examples for connecting to Zapier would be great!

+5
source share
1 answer

Took a bit of research, but I finally have Zapier Rest Hooks. Not as straightforward as I could hope for (most likely I'm a bit involved in the takeover). The customer support was great and friendly, so feel free to send them your questions. Also, once you get this work, it is VERY powerful, although their regular webhook mechanism also works and does not require the creation of a Zap application. At the time of this writing, I have not pushed the application, although it works locally. It is assumed that you started to create your own Zapier application in the developer toolbar. It’s pretty simple, so I won’t cover it here.

This explanation will only cover the creation of a single trigger (although you create another private one to support user authentication, and I created another for dynamic drop-down purposes) and only as a RESThook with Basic Auth. The basics:

1. Create web hosts to allow Zapier to add, update and delete subscriptions to their activities. "Subscribing" Zapier should not poll your webhook, rather, when the signed action happens on your side, you will respond to the URL that Zapier provided to you during the subscription process.

2. Create a DB table that registers these subscriptions, storing the data you need, then send the response back to the supplied Zapier URL when the action starts on your part.

3. When the action is triggered, find out that and publish the data that you told zapier that you are sending. Zapier is pretty smart and will display data (JSON or XML) for you, so when you connect to another application, the user can display between them.

So a few more details. This was done in C # on .Net, but I think that concepts should work just as well in any other language or platform.

RESTHooks first. The following is an example of RESTHook methods. Please note that I spent several days trying to figure out the script version of Zapier, so I'm not quite happy with the naming conventions, but I hope you get this idea.

In this scenario, there is the concept of a “form”, which is part of the JSON data that excites me, the user and the account to which the user belongs. All of them have a unique identifier in the system. Finally, the subscription itself has an identifier. When you sign up, a specific user in a particular account signs a specific form, which will be sent to Zapier when a specific trigger is executed (presentation of this form).

RESTHooks:

First, RouteConfig , which displays the path to the method being executed. You can see all the methods that I implemented. Some of them are not used and are simply included for possible future use (for example, to update a subscription).

  // ZAPIER Webhooks routes.MapRoute( "User_Form_List", "api/zapier/user/formlist", new { controller = "Zapier", action = "RetrieveFormListForUser" }, new { httpMethod = new HttpMethodConstraint("GET") } ); routes.MapRoute( "Authenticate_Subscription", "api/zapier/authenticate", new { controller = "Zapier", action = "WebhookAuthenticate" }, new { httpMethod = new HttpMethodConstraint("GET") } ); routes.MapRoute( "Test_Subscription", "api/zapier/subscription/testdata", new { controller = "Zapier", action = "TestData" }, new { httpMethod = new HttpMethodConstraint("GET") } ); routes.MapRoute( "Create_Submission", "api/zapier/subscription/create", new { controller = "Zapier", action = "CreateSubscription" }, new { httpMethod = new HttpMethodConstraint("GET") } ); routes.MapRoute( "List_Subscriptions", "api/zapier/subscription", new { controller = "Zapier", action = "ListSubscriptions" }, new { httpMethod = new HttpMethodConstraint("GET") } ); routes.MapRoute( "Get_Subscriptions", "api/zapier/subscription/{id}", new { controller = "Zapier", action = "GetSubscription", id = 0 }, new { httpMethod = new HttpMethodConstraint("GET") } ); routes.MapRoute( "Update_Subscription", "api/zapier/subscription/{id}", new { controller = "Zapier", action = "UpdateSubscription", id = 0 }, new { httpMethod = new HttpMethodConstraint("PUT") } ); routes.MapRoute( "Delete_Subscription", "api/zapier/subscription/{id}", new { controller = "Zapier", action = "DeleteSubscription", id = 0 }, new { httpMethod = new HttpMethodConstraint("DELETE") } ); 

Corresponding to this code (also I pulled out error handling to reduce code size):

  public class ZapierController : BaseController //(this inherits from Controller) { private readonly IMyRepository _DBrepository; public ZapierController(IMyRepository repository, ...lots of other autowiring you don't need or care about) : base(logger) { _DBrepository = repository; } #region Zapier Subscriptions // api/zapier/subscription/create : Creates a subscription [HttpGet] public ActionResult CreateSubscription() { ApiResult authresult = Authenticate(); if (authresult.code != 201) { return JsonApiResult(authresult); } // Get the request parameters var reqParams = GetParameters(); // Create the subscription so long as it does not already exist WebhookSubscription sub = new WebhookSubscription(); // _currentUser and _currentAccount are set as part of the authenticate and stored in our base controller sub.AccountId = _currentAccount.Id; sub.UserId = _currentUser.UserId; sub.TargetURL = reqParams["target_url"]; sub.EventType = reqParams["target_event"]; sub.FormId = Int32.Parse(reqParams["form_id"]); sub.IsActive = true; ObjectResult workflowActionRecord = _DBrepository.createWebhookSubscription(sub); sub.Id = workflowActionRecord.objectId; // return the subscription back to Zapier in the result. Zapier will remember it var result = new ApiResult(); result.data.id = workflowActionRecord.objectId; result.data.subscription = sub; result.code = 201; return JsonApiResult(result); } // api/zapier/authenticate : used to test authentication [HttpGet] public ActionResult WebhookAuthenticate() { ApiResult authresult = Authenticate(); var result = new ApiResult(); result.code = 201; return JsonApiResult(result); } // api/zapier/user/formlist : returns list of forms for this user [HttpGet] public ActionResult RetrieveFormListForUser() { ApiResult authresult = Authenticate(); var result = new ApiResult(); List<Form> forms = _DBRepository.FormListRetrieveByUser(_currentUser, false); JsonSerializer serializer = new JsonSerializer(); serializer.Converters.Add(new JavaScriptDateTimeConverter()); serializer.NullValueHandling = NullValueHandling.Ignore; // Again Zapier likes arrays returned JArray objarray = JArray.FromObject(forms); return JsonApiResultDynamic(objarray); } // api/zapier/subscription/testdata : returns test data for zapier [HttpGet] public ActionResult TestData() { ApiResult authresult = Authenticate(); var result = new ApiResult(); JsonSerializer serializer = new JsonSerializer(); serializer.Converters.Add(new JavaScriptDateTimeConverter()); serializer.NullValueHandling = NullValueHandling.Ignore; // Get the request parameters var reqParams = GetParameters(); int chosenFormId = -1; // We need the form Id to proceed if (reqParams != null && reqParams["form_id"] != null) chosenFormId = Int32.Parse(reqParams["form_id"]); else return JsonApiResult(new ApiResult() { code = 403, error = "Form Id Not Found" }); // Get the form by Form Id, and return the JSON...I have removed that code, but make sure the result is place in an Array var resultdata = new[] { myFinalFormJSON }; JArray objarray = JArray.FromObject(resultdata); return JsonApiResultDynamic(objarray); } // api/zapier/subscription : returns list of subscriptions by account [HttpGet] public ActionResult ListSubscriptions() { ApiResult authresult = Authenticate(); // Get a list all subscriptions for the account List<WebhookSubscription> actionData = _DBrepository.accountWebhookSubscriptions(_currentAccount.Id); var result = new ApiResult(); result.code = 201; result.data.subscriptions = actionData; return JsonApiResult(result); } // api/zapier/subscription/{id} : Creates a subscription [HttpGet] public ActionResult GetSubscription(int id) { ApiResult authresult = Authenticate(); // Get a list all subscriptions for the account WebhookSubscription actionData = _DBrepository.getWebhookSubscription(id); var result = new ApiResult(); result.data.subscription = actionData; ; result.code = 201; return JsonApiResult(result); } // api/zapier/subscription/{id} : updates a subscription [HttpPut] public ActionResult UpdateSubscription(int id) { ApiResult authresult = Authenticate(); // get target url and eventy type from the body of request string jsonString = RequestBody(); var json = CommonUtils.DecodeJson(jsonString); // Create the subscription so long as it does not already exist WebhookSubscription sub = _DBrepository.getWebhookSubscription(id); var result = new ApiResult(); if (sub != null) { sub.TargetURL = json.target_url; ; sub.EventType = json.eventType; ObjectResult objResult = _DBrepository.updateWebhookSubscription(sub); result.code = 201; } return JsonApiResult(result); } // api/zapier/subscription/{id} : deletes a subscription [HttpDelete] public ActionResult DeleteSubscription(int id) { ApiResult authresult = Authenticate(); // Delete a subscription _DBrepository.deleteWebhookSubscription(id); var result = new ApiResult(); result.code = 201; return JsonApiResult(result); } // We need to Basic Auth for each call to subscription public ApiResult Authenticate() { // get auth from basic authentication header var auth = this.BasicAuthHeaderValue(); // parse credentials from auth var userCredentials = Encoding.UTF8.GetString(Convert.FromBase64String(auth)); var parts = CommonUtils.SplitOnFirst(userCredentials, ":"); var username = parts[0]; var password = parts[1]; // authenticate user against repository if (!_DBrepository.UserAuthenticate(username, password)) { _logger.Info("Invalid Authentication: " + username); return new ApiResult() { code = 401, error = "invalid authentication" }; } return new ApiResult() { code = 201, error = "successful authentication" }; } } 

The DB table in which the subscriptions will be stored is as follows. I will leave the side of reading and writing, as you may have a different mechanism.

  Create.Table("WebhookSubscription") .WithColumn("Id").AsInt32().Identity().PrimaryKey().NotNullable() .WithColumn("AccountId").AsInt32().NotNullable() .WithColumn("UserId").AsInt32().NotNullable() .WithColumn("EventType").AsString(256).NotNullable() .WithColumn("TargetURL").AsString(1000).NotNullable() .WithColumn("IsActive").AsBoolean().NotNullable() .WithColumn("CreatedOn").AsDateTime().Nullable() .WithColumn("FormId").AsInt32().NotNullable().WithDefaultValue(0); .WithColumn("UpdatedAt").AsDateTime().Nullable(); 

To be clear, value / usage for the following columns:

  • Id is a unique subscription identifier. Will be used to unsubscribe
  • AccountId - account identifier for user subscription. If you want all of them to work at the account level, you could simply do this instead
  • UserId - user subscription identifier
  • EventType The type of event your action responds to, for example, "new_form_submission"
  • TargetURL . The destination URL that zapier gave you when signing up. Will there be a placement of your JSON when the action is triggered.
  • FormId - identifier of the form in which the user wants to perform an action when submit

So, this is the code needed to subscribe (obviously, you can't just throw it in there and make it work - left a good amount to save space) ...

Trigger code

Only the code on the left is the actual trigger code — the code that you execute when the event search is found in your code. For example, when a user submits a "form", we want to submit this JSON form to Zapier. Now that we have all the other code, this part is pretty simple. First, the code, in order to discover that we got the view, requires a Zapier response:

Actual code that looks if registered / registered on Zapier:

 public BusinessResult FormSubmitted(string jsonString) { var json = CommonUtils.DecodeJson(jsonString); var account = _DBrepository.AccountRetrieveById(_currentUser.AccountId.Value); // Assumes user has bee authenticated // inject additional meta data into json and retrieve submission/alert settings var form = _DBformRepository.FormRetrieveById((int)json.formId); // Lookup Subscription Webhooks List<WebhookSubscription> subscriptions = _DBrepository.accountWebhookSubscriptions(account.Id); if (subscriptions != null && subscriptions.Count > 0) { foreach (WebhookSubscription sub in subscriptions) { if (sub.EventType.Equals("new_form_submission") && sub.FormId == form.Id) { _webhookService.NewFormSubmission(sub, jsonString, form.Name, account.Name, account.Id); } } } } 

And finally, the code to send the response back to Zapier, which will parse the JSON and send it to the appropriate parties:

 public class WebhookService : IWebhookService { protected readonly IRepository _DBrepository; public WebhookService(IRepository repository) { _DBrepository = repository; } public void NewFormSubmission(string formResultJSON) { throw new NotImplementedException(); } public void NewFormSubmission(WebhookSubscription subscription, string formResultJSON, string formName, string accountName, int accountId) { // Now post to webhook URL string response; using (var client = new WebClient()) { client.Headers[HttpRequestHeader.ContentType] = "application/json"; // Needs to be an array sent to Zapier response = client.UploadString(subscription.TargetURL, "POST", "[" + formResultJSON + "]"); } } } 

Well, that should get you most of the way. But posting code / webhooks in Zapier is where it gets a little trickier. The idea now is to link code in your Zapier application using the development panel. You will need to start creating the Zapier app. You need two main triggers: the main action that you are trying to implement (in this case, “Submit a new form”) and authentication, so that Zapier can authenticate the user as he creates Zap (in this case, “Test Auth”), I use Basic Auth, but others are supported (OAuth, etc.). In addition, I added a trigger that will return a list of forms that the user has access to. Since this is not required, I will not show that the implementation is fixed on the screen:

enter image description here I will not show the wiring for Test Auth, as it went quite smoothly (I will add it if someone asks for it - heaven knows, someone will even read it). So, here is the posting, page by page for "Submit a New Form":

Page 1

enter image description here

Page 2

Here I am attaching a list of forms that provides a list of forms that the user who creates Zap can select. You can probably skip this (leave it blank) if you don't have the dynamic data you want to display. I have included it for completeness: enter image description here

Page 3

Here you connect test data

enter image description here

Page 4

On this page you can enter sample data. Skipping this, as it is pretty straight forward.

Scripting API

So, now you have connected your first Zap Trigger! But wait, we are not done. To make the subscription process, we need to add some script. It was the hardest part of the whole process and was not very intuitive. So, on the initial main screen, a little lower, you will see the scripting API :

enter image description here

You should now have a script to subscribe to RESTHook. I am not going to understand in detail, since Zapier has documentation about this, but it’s good to know that Zapier stores data as part of the subscription. Also after that we will need to take another connection step ...

 var Zap = { pre_subscribe: function(bundle) { bundle.request.method = 'GET'; bundle.request.headers['Content-Type'] = 'application/x-www-form-urlencoded'; bundle.request.params = { target_url: bundle.subscription_url, target_event:bundle.event, form_id:bundle.trigger_fields.form_id }; bundle.request.data = $.param({ target_url: bundle.subscription_url, target_event:bundle.event, form_id:bundle.trigger_fields.form_id }); return bundle.request; }, post_subscribe: function(bundle) { // must return a json serializable object for use in pre_unsubscribe var data = JSON.parse(bundle.response.content); // we need this in order to build the {{webhook_id}} // in the rest hook unsubscribe url return {webhook_id: data.id}; }, pre_unsubscribe: function(bundle) { bundle.request.method = 'DELETE'; bundle.request.data = null; return bundle.request; }, new_form_submission_pre_poll: function(bundle) { bundle.request.method = 'GET'; bundle.request.headers['Content-Type'] = 'application/x-www-form-urlencoded'; bundle.request.params = bundle.trigger_fields; bundle.request.data = $.param({ form_id:bundle.trigger_fields.form_id }); return bundle.request; } }; 

There is little for this ... but look at the Zapier documentation and this should help. Or ask questions here and I will try to answer them ... this is getting bigger than I expected!

Manage trigger settings

Finally, we need to finish the wiring for the subscription ...

enter image description here

Then we create the RESTHook methods that we created some time ago:

enter image description here

And it's all. Hope this saves you time and lessons!

+15
source

Source: https://habr.com/ru/post/1242309/


All Articles