Подключение к Zapier using.Net WebHooks как RESThooks

Я изучаю создание "Zap-приложения" и задаюсь вопросом, сделал ли кто-нибудь это с помощью новых .Net Webhooks. Кажется, у них есть" шаблон", запрошенный RESTHooks, механизм Subcription/Publish. Не так много примеров его работы, и я хотел проверить, прежде чем тратить дни на его реализацию и найти его несовместимым.

фактические примеры кода подключения к Zapier было бы здорово!

1 ответов


взял немного исследований,но у меня, наконец, есть крючки для отдыха Zapier. Не так прямо, как я надеялся (скорее всего, я немного медленный в понимании). Поддержка клиентов была отличной и дружелюбной, поэтому не стесняйтесь присылать им свои вопросы. Также, как только вы получите эту работу, она очень мощная, хотя их обычный механизм webhook также работает и не требует от вас создания приложения Zap. На момент написания этой статьи я не вытолкнул приложение, хотя это работает на месте. Это предполагает, что вы начали создавать свое собственное приложение Zapier на панели мониторинга разработчика. Это довольно просто, поэтому я не буду рассматривать здесь.

это объяснение будет охватывать только создание одного триггера (хотя вы создаете еще один частный для поддержки аутентификации пользователя, а я создал другой для динамических целей раскрывающегося списка) и только как RESThook с базовой аутентификацией. Основы:

1. создать веб-перехватчиков чтобы разрешить Zapier добавлять, обновлять и удалять подписки на ваши действия. При "подписке" Zapier не должен опрашивать ваш веб-крюк, а когда подписанное действие происходит на вашей стороне, вы отвечаете на URL-адрес, который Zapier дал вам во время процесса подписки.

2. создать таблицу БД это регистрирует эти подписки, сохраняя данные, которые вам нужно иметь возможность затем опубликовать ответ на Zapier предоставил URL-адрес, когда действие запускается на вашей стороне.

3. когда действие запускается, распознайте это и опубликуйте данные вы сказали аккаунтам, которые вы хотели бы отправить. Zapier довольно умен и сопоставит данные (JSON или XML) для вас, поэтому при подключении к другому приложению пользователь может сопоставлять между ними.

Итак, еще несколько деталей. Это было сделано в C# на .Net, но я думаю, что концепции должны работать так же хорошо на любом другом языке или платформе.

сначала RESTHooks. Ниже приведен пример методов RESTHook. Обратите внимание, что я потратил дни, пытаясь выяснить сторону сценариев Zapier, поэтому я не совсем доволен соглашениями об именах, но, надеюсь, вы поймете эту идею.

в этом сценарии есть концепция "формы", которая является куском данных JSON, которые меня волнуют, Пользователь и учетная запись, к которой принадлежит пользователь. Все они имеют уникальный ID в Система. Наконец, сама подписка имеет идентификатор. Когда вы подписываетесь, конкретный пользователь в определенной учетной записи подписывается на определенную форму для отправки в Zapier при выполнении определенного триггера (отправки этой формы).

RESTHooks:

сначала RouteConfig который отображает путь к методу для выполнения. Вы можете увидеть все методы, которые я реализовал. Некоторые из них не используются и просто включили для возможного использования в будущем (например, обновление подписки).

    // 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") }
    );

соответствующий этому код (также я вытащил обработку ошибок, чтобы уменьшить размер кода):

    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" };

        }
   }

таблица БД, которая будет содержать подписки, выглядит следующим образом. Я оставлю в стороне чтение и письмо, так как у вас может быть другой механизм.

   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();

чтобы быть ясным, значение / использование для следующих столбцов:

  • Id - уникальный идентификатор подписки. Будет использоваться для отписаться
  • методами accountid - идентификатор учетной записи для подписки пользователей. Если вы хотите, чтобы все это работало на уровне учетной записи, просто сделайте это вместо
  • UserId - Id пользователя подписки
  • EventType - тип события, на которое должно реагировать ваше действие, например "new_form_submission"
  • TargetURL - целевой URL, который zapier дал вам при подписке. Будет где постить ваш JSON при инициировании действия
  • Код_формы - Id формы, для которой пользователь хочет действие при отправке

так что это код, необходимый для подписки (очевидно, вы не можете просто бросить его там и заставить его работать - оставил хорошую сумму, чтобы сэкономить место)...

Триггер Код

только код остается фактический код триггера-код, который вы выполняете, когда искомое событие встречается в вашем коде. Так пример, когда пользователь отправляет "форму", мы хотим отправить эту форму JSON в Zapier. Теперь, когда у нас есть весь другой код, эта часть довольно проста. Сначала код для обнаружения у нас было представление, которое требует ответа Zapier:

фактический код, который смотрит, чтобы увидеть, если подача формы зарегистрирована / подписана на 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);
            }
        }
    }
}

и, наконец, код для публикации этого ответа обратно в Zapier, который будет анализировать JSON и отправлять его в соответствующий стороны:

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 + "]");
        }
    }
}

хорошо, это должно получить вас большую часть пути туда. Но проводка кода / веб-крючков в Zapier-это то, где становится немного сложнее. Теперь идея состоит в том, чтобы подключить код выше в ваше приложение Zapier с помощью панели разработки. Вам нужно будет начать создавать приложение Zapier. Есть 2 основных триггера, которые вам нужны-основное действие, которое вы пытаетесь реализовать (в этом случае "отправка новой формы"), и аутентификация, чтобы Zapier мог аутентифицировать пользователя, как они создаем Zap (в данном случае "тест Auth"). Я использую базовую аутентификацию, но другие поддерживаются (OAuth и т. д.). Кроме того, я добавил триггер, который вернет список форм, к которым пользователь имеет доступ. Поскольку это не требуется, я не буду показывать, что реализация на экране захватывает:

enter image description here Я не буду показывать проводку для "тестового Auth", так как это прошло довольно гладко (я добавлю его, если кто - то попросит его-бог знает, если кто-нибудь даже прочитает это). Так вот является ли проводка, страница за страницей для "новой формы представления":

Страница 1

enter image description here

Страница 2

здесь я подключаю список форм, который предоставляет список форм, из которых пользователь, создающий Zap, может выбрать. Вероятно, вы можете пропустить это (оставить его пустым), если у вас нет динамических данных, которые вы хотите отобразить. Я включил его для полноты: enter image description here

Страница 3

здесь вы проводите тестовые данные

enter image description here

Страница 4

эта страница позволяет вводить примерные данные. Пропуская это, так как это довольно прямо вперед.

API сценариев

Итак, теперь вы подключили свой первый триггер Zap! Но подождите, мы еще не закончили. Чтобы процесс подписки работал, нам нужно добавить сценарий. Это была самая трудная часть всего процесса и была не очень интуитивной. Так на оригинале главный экран, немного вниз вы увидите API сценариев:

enter image description here

теперь у вас должен быть сценарий для подписки RESTHook. Я не собираюсь вдаваться в подробности, так как у Zapier есть документация по этому вопросу, но хорошо знать, что Zapier хранит данные как часть подписки. Также нам нужно будет сделать еще один шаг проводки после этого...

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;
    }
};

в этом есть немного...но посмотрите на Zapier документация и это должно помочь. Или разместите вопросы здесь, и я постараюсь ответить на них...это становится больше, чем я ожидал!

Управление Настройками Триггера

наконец, нам нужно закончить проводку для подписки...

enter image description here

затем мы настроили методы RESTHook, которые мы создали некоторое время назад:

enter image description here

и это все. Надеюсь, это спасет кого-то время и уроки!