Using data or data_template section of REST notifier

I have a REST notifier set up to post to a Slack channel using their ‘incoming webhooks’ feature.

notify:
  - name: slack
    platform: rest
    method: POST_JSON
    resource: <slack incoming webhook URL>
    verify_ssl: true
    message_param_name: 'text'
    data:
      attachments: 'attachments'

The posts you can make are quite flexible; they describe the format of the JSON that you can send.

The REST notifier documentation states that I can use a ‘data’ or ‘data_template’ section to specify a “dictionary of extra parameters to send to the resource.” but when I specify extra parameter names there, the calls to actually call that notifier service fail with:

  - service: notify.slack
    data:
      message: "This is a test"
      attachments: >
        [
          { 
            "callback_id": "my callback id", 
            "actions":
              [
                {
                  "name": "action name", 
                  "text": "action text", 
                  "type": "button", 
                  "value": "action value"
                }
              ]
          }
        ]
Failed to call service notify/slack. extra keys not allowed @ data['attachments']

If I leave out the “attachments” parameter, the call succeeds, so I’m guessing that I’ve specified the extra parameters incorrectly in either my service call or service definition. Can anyone help me out?

I believe you want data inside your data. I also took the liberty of translating the json to yaml.

  - service: notify.slack
    data:
      message: "This is a test"
      data:
        attachments:
        - callback_id: my callback id
          actions:
          - name: action name
            text: action text
            type: button
            value: action value

I’ll try the data in my data. YAML or JSON makes no difference to me. At least when I write JSON I know exactly what slack is receiving.

But wait…you have no mention of ‘attachments’ in your example. AT some point, I have to be sure that the JSON that gets sent to slack has those values in an “attachments” value.

Yep, left it out. My mistake, check the post out again.

Yaml is always in json form when read into the code. You don’t need to input it as json to recieve json. Input it in the easier setup always. Which is typically yaml.

These modifications make the service call succeed, but I still only end up with the message, and none of the fancy bits. Here’s the YAML I’m using in the service dev tool:

message: "This is a test"
data:
  attachments:
    - title: "asdf"
      fallback: "(test)"
      callback_id: "my-callback-id"
      color: "#3AA3E3"
      attachment_type: "default"
      actions:
        - name: "action name"
          text: "action text"
          type: "button"
          value: "action value"

I think I need to keep digging here. This is definitely an improvement over not working at all, but damn it, I want fancy notifications :slight_smile: My goal is to get actionable notifications via Slack.

Yeah, I never really realized that they are actually the same. Huh.

For what it’s worth, I could not get this working with the REST notify component. However, when I moved to the more flexible REST command, I did.

rest_command:
  post_slack:
    url: <slack incoming webhook URL>
    content_type: 'application/json'
    verify_ssl: true
    method: 'post'
    timeout: 20
    payload: >
      {%- if attachments is not defined -%}
        {%- set attachments = '[]' -%}
      {%- endif -%}
      {
        "text": "{{ message }}",
        "attachments": {{ attachments }}
      }

Then I can call it as such:

- service: rest_command.post_slack
  data_template:
    message: >
      Garage door has been left open.
    attachments: >
      {
        "fallback": "(options for closing the garage)",
        "title": "Close the garage door?",
        "callback_id": "garage_left_open",
        "color": "#3AA3E3",
        "attachment_type": "default",
        "actions": [
          {
            "text": "Close it",
            "name": "close_garage_door",
            "type": "button",
            "value": "Close it"
          },
          {
            "text": "Leave it",
            "name": "dismiss",
            "type": "button",
            "value": "Leave it"
          }
        ]
      }

The issue I have now with slack is that it will only call back to a single URL, so I have to determine when the automation runs, what callback is actually being triggered. My solution so far was to just trigger a script for every callback that I have, and let the script have a condition which checks slack’s JSON for the right callback_id.

automation:
  - alias: slack_webhook
    trigger:
      - platform: webhook
        webhook_id: <webhook ID>
    condition:
      - condition: template
        value_template: >
          {{ trigger.json.token == '<slack app verification token>' }}
      - condition: template
        value_template: >
          {{ trigger.json.type == 'interactive_message' }}
    action:
      - service: script.garage_left_open_callback
        data_template:
          trigger: '{{ trigger }}'
      - service: script.laundry_done_callback
        data_template:
          trigger: '{{ trigger }}'

Then the scripts look like this:

script:
  garage_left_open_callback:
    sequence:
      - condition: template
        value_template: >
          {{ trigger.json.callback_id == 'garage_left_open' }}
      - service_template: >
          {%- if (trigger.json.actions[0].name == 'close_garage_door') -%}
            cover.close_cover
          {%- endif -%}
        data_template:
          entity_id: cover.garagedoor

Except that the trigger parameter doesn’t get passed to the script as a JSON object; it comes in a a string containing JSON-formatted text. That brought me back to this thread, which would be super useful.

Yeah, but why… just send what you need to. (also, fixed an error that you’d get when you don’t have a service to send in your template.

script:
  garage_left_open_callback:
    sequence:
      - condition: template
        value_template: >
          {{ callback_id == 'garage_left_open' and name == 'close_garage_door' }}
      - service: cover.close_cover
        data:
          entity_id: cover.garagedoor
      - service: script.garage_left_open_callback
        data_template:
          callback_id: '{{ trigger.json.callback_id }}'
          name: '{{ trigger.json.actions[0].name }}'

Yeah, I’ve seen the error, but it doesn’t do anything in that case, which is the intent :slight_smile:
While writing those posts, I came up with a potential solution to my issue…I’ll respond later if it works.

I’m not sure how this even works to be honest, unless they do special parsing:

      {%- if attachments is not defined -%}
        {%- set attachments = '[]' -%}
      {%- endif -%}
      {
        "text": "{{ message }}",
        "attachments": {{ attachments }}
      }

Not to mention, you can optimize it and yamlize it.

        text: "{{ message }}"
        attachments: "{{ attachments if attachments is defined else [] }}"

I found that syntax on StackOverflow, but I think I like yours better. Much more concise.

FWIW, using my hacked in to_json and from_json filters, I now have actionable notifications working in Slack.

First, here’s the rest_command for the slack API that I use.

rest_command:
  slack_api:
    url: https://slack.com/api/{{ api }}
    content_type: 'application/json; charset=utf-8'
    verify_ssl: true
    method: 'post'
    timeout: 20
    headers:
      Authorization: !secret slackbot_token
    payload: '{{ payload }}'

Now an example of sending a notification message that would offer choices. I know I can do all this with YAML rather than JSON, but in some cases I use template logic to offer different options in the attachments. I haven’t figured out how to do this with YAML. Note the “callback_id” and the “name” values of the actions. They’ll be used later on.

- service: rest_command.slack_api
  data_template:
    api: 'chat.postMessage'
    payload: >
      {
        "text": "Garage left open",
        "channel": "<channel ID for your notification>",
        "attachments": [
          {
            "fallback": "(options for closing the garage door)",
            "title": "Close the garage door?",
            "callback_id": "cover_left_open_callback",
            "color": "#3AA3E3",
            "attachment_type": "default",
            "actions": [
              {
                "text": "Close it",
                "name": "cover.garagedoor",
                "type": "button",
                "value": "Close it"
              },
              {
                "text": "Leave it",
                "name": "",
                "type": "button",
                "value": "Leave it"
              }
            ]
          }
        ]
      }

Next comes the webhook trigger that gets hit when slack calls you back after clicking on one of the buttons:

automation:
 - alias: slack_webhook
    trigger:
      - platform: webhook
        webhook_id: <YOUR WEBHOOK ID>
    condition:
      - condition: template
        value_template: >
          {% set payload = trigger.data.payload | from_json %}
          {{ (payload.token == '<YOUR SLACK VERIFICATION TOKEN>') and (payload.type == 'interactive_message') }}
    action:
      # I set my 'callback_id' to be the last part of the script entity
      # I want to run when the callback is called
      - service_template: >
          {%- set payload = trigger.data.payload | from_json -%}
          script.{{ payload.callback_id }}
        data_template:
          payload: '{{ trigger.data.payload }}'
      # Then I delete the original message with the buttons and post
      # another message saying that the original message was handled
      # with the choice that the Slack user made.
      - service: rest_command.slack_api
        data_template:
          api: 'chat.delete'
          payload: >
            {%- set payload = trigger.data.payload | from_json -%}
            {
              "channel": "{{ payload.channel.id }}",
              "ts": "{{ payload.message_ts }}"
            }
      - service: rest_command.slack_api
        data_template:
          api: 'chat.postMessage'
          payload: >
            {%- set payload = trigger.data.payload | from_json -%}
            {
              "channel": "{{ payload.channel.id }}",
              "text": "{{ payload.original_message.text }}\nHandled with '{{ payload.actions[0].value }}' by <@{{ payload.user.id }}>"
            }

Lastly, here’s an example of one of my callback scripts. This would be the part that actually performs the action that the user chose:

script:
  cover_left_open_callback:
    sequence:
      # Ensure that the value is not an empty string
      # I use empty strings to mean "don't do anything".
      # Otherwise, this value will be the entity_id of the cover that 
      # was left open.
      - condition: template
        value_template: >
          {%- set pl = payload | from_json -%}
          {{ not (pl.actions[0].name == "") }}
      - service: cover.close_cover
        data_template:
          entity_id: >
            {%- set pl = payload | from_json -%}
            {{ pl.actions[0].name }}
1 Like