Microsoft Teams Trigger

Microsoft Teams supports incoming webhooks. I would like to propose adding a trigger for incoming webhooks to n8n, allowing to process incoming messages.

Incoming webhooks work by adding a “virtual user” to a channel you can simply at-mention to invoke:

@MyWebhook hello world!

On writing this message to a channel with a connected incoming webhook, an HTTP request will be sent to the webhook’s endpoint. The endpoint must reply (within 5 seconds) by responding with a message payload.

I think this fits very neatly in n8n.

After implementing this myself (and hitting some annoyances), I know how an implementation should look like and what it should be capable of:

  1. Credentials: Teams signs all requests using HMAC. Creating the HMAC requires to encrypt the raw request body with the base64-decoded binary secret provided by Teams when creating the webhook.
    In n8n, the user should be able to create a Credential containing the secret that is mandatory to use the trigger node.
    The official Node.js code example does the following to encrypt the message:

    const sharedSecret = "base64-encoded-secret-here"; // e.g. "+ZaRRMC8+mpnfGaGsBOmkIFt98bttL5YQRq3p2tXgcE="
    const bufSecret = Buffer(sharedSecret, "base64");
    var auth = this.headers['authorization']; // e.g. HMAC GLYzq3qGYZGFW8fwL6QANHwYR1+LugxZBr0pkVskAJw=
    var msgBuf = Buffer.from(payload, 'utf8');
    var msgHash = "HMAC " + crypto.createHmac('sha256', bufSecret).update(msgBuf).digest("base64");
    
    var valid = msgHash === auth;
    

    Note that the secret buffer is created passing the base64 encoding here, a shortcut to create a binary buffer of the base64-decoded text.

  2. Message parsing: Teams provides lots of meta data for the message. An (redacted) example:

     {
         "type": "message",
         "id": "1632705139912",
         "timestamp": "2021-09-15T12:33:21.4552654Z",
         "localTimestamp": "2021-09-15T14:33:21.4552654+02:00",
         "serviceUrl": "https://smba.trafficmanager.net/de/",
         "channelId": "msteams",
         "from": {
             "id": "29:1yjvN8w7reCs4g99tEYTXy5f5O677C6AQ5W_Q_MrYWohVf5xFd3cNoe3n9C-o_vGcnNC9W1j40Pv9F1UA2V-qxw",
             "name": "Radiergummi",
             "aadObjectId": "96d44937-2276-49a2-8e4a-8f4db393fea5"
         },
         "conversation": {
             "isGroup": true,
             "id": "19:[email protected];messageid=1631707925358",
             "name": null,
             "conversationType": "channel",
             "tenantId": "04f72222-515b-448b-bc89-1a36640a88bf"
         },
         "recipient": null,
         "textFormat": "plain",
         "attachmentLayout": null,
         "membersAdded": [
         ],
         "membersRemoved": [
         ],
         "topicName": null,
         "historyDisclosed": null,
         "locale": "de-DE",
         "text": "<at>MyWebhook</at> hello world",
         "speak": null,
         "inputHint": null,
         "summary": null,
         "suggestedActions": null,
         "attachments": [
             {
                 "contentType": "text/html",
                 "contentUrl": null,
                 "content": "<div><div><span itemscope=\"\" itemtype=\"http://schema.skype.com/Mention\" itemid=\"0\">MyWebhook</span> hello world</div> </div>",
                 "name": null,
                 "thumbnailUrl": null
             }
         ],
         "entities": [
             {
                 "type": "clientInfo",
                 "locale": "de-DE",
                 "country": "DE",
                 "platform": "Mac",
                 "timezone": "Europe/Berlin"
             }
         ],
         "channelData": {
             "teamsChannelId": "19:[email protected]",
             "teamsTeamId": "19:[email protected]",
             "channel": {
                 "id": "19:[email protected]"
             },
             "team": {
                 "id": "19:[email protected]"
             },
             "tenant": {
                 "id": "04f72222-515b-448b-bc89-1a36640a88bf"
             }
         },
         "action": null,
         "replyToId": null,
         "value": null,
         "name": null,
         "relatesTo": null,
         "code": null,
         "localTimezone": "Europe/Berlin"
     }
    

    What’s rather annoying is the <at>{{Webhook Name}}</at> inclusion in the message; but even worse: If you copy-paste text from the chat into your message, the text property may look like the following:

    <div itemprop="copy-paste-block"><at>MyWebhook</at> copy-pasted text 
    

    Note that only the “copy-pasted text” part was copy-pasted, and the <div> not being closed is not a typo - Teams really passes that as the message text…

    While I’m all in favour of not messing with received data, this is still massively annoying and cumbersome to solve using existing tools. I also don’t think a Teams Trigger node should make a function mandatory, just to perform the ever-same cleanup task.

    Therefore, I would like to see an additional property with a parsed version of the message text, sans any HTML code fragments.
    Maybe even with an option to remove At-Mentions?

  3. Send responses: Incoming webhooks have to answer with a reply to the original message. The options of the Webhook node were useful for this (particularly Response Mode), but it would be neat to have a default option in case no response is created during the workflow. A minimal response looks like the following:

    { "type": "message", "text": "Workflow triggered successfully" }
    

    I’d suggest implementing the following options:

    • Response Data: Selector with options “Reply Property: Returns the content of the reply property” (default), “First Entry JSON: Returns the JSON data of the first entry of the last node”, and “All Entries: Returns all entries of the last node”.
    • Reply property: Text field, default reply. Only visible if the Reply Property option has been selected for the response data. Allows to provide the name of a property provided by the last node that will contain the reply message payload.
    • Fallback response: Text field, default { "type": "message", "text": "Workflow triggered successfully" }. A fallback response in case the response data option is set to use the reply property but that isn’t set, or the last node doesn’t return data.

I’m happy to help with this if I can!

We kinda do the same with the telegram node. It has a resource called “callback” and operations to reply. This helps to simplify the creation of telegram bots.

I think you want an extension of the current Microsoft node, rather than the creation of a trigger. With the webhook node you can catch the request. The not so fun part it’s to verify the signature and build the message you want to reply with.

1 Like

I see where you’re coming from, although “doing something if someone sent a message to my bot” sounds a lot like a trigger to me…
You’re way deeper into this though. Are webhooks expecting a response really that rare?