Add new webhooks and customize webhook payload

Ever wonder how to add new webhook types? Or how to reduce the payload? Here is the tutorial for the plugin authors. It demos how to add the session and user notification event types as well as customization to the payload. You can also check the plugin on GitHub while reading.

If you’d like to have a new webhook type supported by the team, bring up a feature request instead.

Before started, make sure you already understand webhook guide.

New webhook event type

A new webhook event type is defined in the database so that Ember client and webhook can find relevant information.

1.Seeding

Add a seed file in the following path of the plugin db/fixtures/001_custom_web_hook.rb.

WebHookEventType.seed do |b|
  b.id = 100 # start with a relative large number so it doesn't conflict with the core type
  b.name = "notification"
end

WebHookEventType.seed do |b|
  b.id = 101
  b.name = "session"
end

Then putting SeedFu.fixture_paths << Rails.root.join("plugins", "discourse-webhooks-example", "db", "fixtures").to_s into the plugin.

Admin dashboard needs the text to display the new event type. Adding them in config/locales/client.<language-code>.yml:

en:
  admin_js:
    admin:
      web_hooks:
        notification_event:
          name: "Notification Event"
          details: "When there is a new notification."
        session_event:
          name: "Session Event"
          details: "When there is a login or logout."

2. Connect with the internal DiscourseEvent or hook on your own

add_model_callback(:notification, :after_commit, on: :create) do
  # you can enqueue web hooks anywhere outside the AR transaction
  # provided that web hook event type exists
  WebHook.enqueue_hooks(:notification, # event type name
                        notification_id: self.id, # pass the relevant record id
                        # event name appears in the header of webhook payload
                        event_name: "notification_#{Notification.types[self.notification_type]}_created")
end

%i(user_logged_in user_logged_out).each do |event|
  DiscourseEvent.on(event) do |user|
    WebHook.enqueue_hooks(:session, user_id: user.id, event_name: event.to_s)
  end
end

3. Final step: Sidekiq Jobs

Adding a new method to the Jobs::EmitWebHookEvent:

Jobs::EmitWebHookEvent.class_eval do
  # the method name should always be setup_<event type name>(args)
  def setup_notification(args)
    notification = Notification.find_by(id: args[:notification_id])
    return if notification.blank? # or raise an exception if you like

    # here you can define the serializer, you can also create a new serializer to prune the payload
    # See also: `WebHookPostSerializer`, `WebHookTopicViewSerializer`
    args[:payload] = NotificationSerializer.new(notification, scope: guardian, root: false).as_json
  end

  def setup_session(args)
    user = User.find_by(id: args[:user_id])
    return if user.blank?
    args[:payload] = UserSerializer.new(user, scope: guardian, root: false).as_json
  end
end

An aside note, the payload is sent as if you are an administrator browsing a Discourse site. Be careful for what you sent.

Payload customization

There are two ways to reduce the payload size.

  1. Define a custom serializer.
  2. Uses plugin filter.

The first one is explicit to do. The second one involves a plugin API where you have the power to modify the payload. This enables the possibility to slice the JSON, i.e. @Lapinot suggested.

Plugin::Filter.register(:after_build_web_hook_body) do |instance, body|
  if body[:session]
    body[:user_session] = body.delete :session
  end

  body # remember to return the object, otherwise the payload would be empty
end

Final aside note, a {{plugin-outlet name="web-hook-fields"}} is now available in the web hook configuration page.

The plugin code is available under GitHub - erickguan/discourse-webhooks-example · GitHub.

Thanks @erlend_sh to encourage me writing this tutorial and @tgxworld for sorting out the internals.

26개의 좋아요

@fantasticfears Note that it isn’t recommended to run migrations in plugins. Instead I’ve updated the howto to create a fixture file that will automatically be run when db:migrate is invoked.

7개의 좋아요

Fixture is better than migration but it doesn’t work with the plugin now. The plugin system includes only db/migrate but not fixture.

Ah ha I forgot to include the secret sauce.

10개의 좋아요

I didn’t know there’s a new API register_seedfu_fixtures! Updated.

8개의 좋아요

what do i have to do to make it just notification webhook and remove the session event ?

Can someone offer a hint here?

I need to submit this JSON payload when a new user is created:

{"email_address":"address of new user", "status": "subscribed"}

I guess I don’t know how to create a serializer.

This is easy.

class MySerializer < ApplicationSerializer
  attributes :email_address, :status
end

my_object = MyClass.new(email_address: "address of new user", status: "subscribed")
MySerializer.new(my_object).as_json
4개의 좋아요

@fantasticfears Thanks for the writeup!

It needs a bit of tweaking to take account of the changes introduced in this commit.

  • You no longer need a setup_ method in Jobs::EmitWebHookEvent.

  • There’s now a WebHook a class method for formatting custom payloads via a serializer:

    WebHook.enqueue_object_hooks(type, object, event, Serializer) 
    

Also, I’m wondering about the thinking behind namespacing the payload with the event type? This makes it a little harder to use endpoints you don’t fully control, which expect certain attributes in the body.

def build_web_hook_body(args, web_hook)
   ....
   if ping_event?(event_type)
     body[:ping] = 'OK'
   else
     body[event_type] = args[:payload]
   end
  ...
end
2개의 좋아요

Can you help me update the guide?

This is mainly for possible extension. Additional information can be serialized to other keys. But we relies on serializers now so it’s more or less useless.

2개의 좋아요

@fantasticfears
안녕하세요.

웹훅을 사용하여 Discourse를 APP에 연결하려고 합니다.

대부분의 설정은 완료했는데, 아직 하나만 남았습니다.

Discourse에서 새 주제를 생성하면, 많은 매개변수를 포함하는 페이로드가 APP로 전송됩니다.

하지만 Slack에서는 ‘text’ 매개변수만 수신됩니다. 따라서 웹훅을 사용할 때 페이로드를 사용자 정의하고 싶습니다.

Discourse에서 웹훅을 사용할 때 페이로드를 어떻게 변경할 수 있을까요?

현재 상태 (AS-IS)

{
  "post": {
    "id": 19,
    "name": "user",
    "username": "user",
    "avatar_template": "/letter_avatar_proxy/v2/letter/u/c0e974/{size}.png",
    "created_at": "2018-07-20T06:24:33.205Z",
    "cooked": "<p>Cool, now i have you, haha</p>",
    "post_number": 6,
    "post_type": 1,
    "updated_at": "2018-07-20T06:24:33.205Z",
    "reply_count": 0,
    "reply_to_post_number": null,
    "quote_count": 0,
    "avg_time": null,
    "incoming_link_count": 0,
    "reads": 0,
    "score": 0,
    "topic_id": 11,
    "topic_slug": "this-is-new-topic",
    "topic_title": "This is new topic",
    "display_username": "user",
    "primary_group_name": null,
    "version": 1,
    "user_title": null,
    "moderator": false,
    "admin": true,
    "staff": true,
    "user_id": 1,
    "hidden": false,
    "trust_level": 1,
    "deleted_at": null,
    "user_deleted": false,
    "edit_reason": null,
    "wiki": false,
    "topic_posts_count": 6
  }
}

목표 상태 (TO-BE)

{
   "topic_title": "This is new topic"
}

감사합니다.

안녕하세요, 이 가이드와 샘플 플러그인을 따라 커스텀 웹훅을 만들려고 하고 있지만, 새로 만든 커스텀 이벤트 유형이 웹훅 UI에 표시되지 않습니다. 이 가이드에 링크된 플러그인을 로컬 개발 인스턴스에 설치해 보았지만, 예제 플러그인의 이벤트도 표시되지 않습니다. 2017년부터 현재까지 이 부분에 영향을 미치는 변경 사항이 있었을 것 같은데요.

이 토픽의 예제 플러그인을 작동시키는 방법을 아시는 분이 계신가요? 새 항목이 /admin/api/web_hooks 응답에 표시되지 않기 때문에, 문제가 데이터베이스 시딩 코드에 있을 것 같습니다. 현재로서는 이 가이드가 정상적으로 작동하지 않습니다. 감사합니다!

안녕하세요! 도움이 필요할 것 같습니다.

커스텀 웹훅을 추가하려고 하고 있습니다. 웹훅은 정상적으로 작동하며 페이로드를 웹훅 URL로 올바르게 전송합니다. 다만, 관리자 화면에서 웹훅을 구성할 때 이벤트를 활성화하는 것만 아직 성공하지 못했습니다. 위의 튜토리얼을 따라 픽스처를 등록했지만, 관리자 화면에 표시되지 않습니다.

몇 가지 방법을 시도해 보았습니다:

SeedFu.fixture_paths << Rails.root.join("plugins", "<plugin_name>", "db", "fixtures").to_s
register_seedfu_fixtures(Rails.root.join("plugins", "<plugin_name>", "db", "fixtures").to_s)

픽스처:

WebHookEventType.seed do |b|
    b.id = 101
    b.name = "remove_like"
end

또한 로케일 파일에 이것도 추가했습니다:

en:
  js:
    plugin_name:
      placeholder: placeholder
    admin:
      web_hooks:
        remove_like_event:
          name: "Remove Like Event"
          details: "When a user marks a post as the accepted or unaccepted answer."

무엇을 놓치고 있는 건가요? 아니면 다른 방식으로 처리해야 하나요?

음, 캐시 문제인 것 같네요. 다른 Discourse 인스턴스에 설치했을 때는 제대로 표시되었거든요.