Using Axios to post a message in Microsoft Teams

Viewed 978

UPDATED QUESTION 16-10-2020 I simplified the code so it doesn't contain an if statement anymore to run the postMessageToTeams function and I added the error response I receive in the catch. Hopefully this makes it easier to debug.

It works when I run the script from my Visual Studio terminal but it doesn't when running it from my cmder. So I reckon it has something to do with my cmder settings.

Goal

I'm running some automated tasks through testcafe and would like to be notified in Microsoft Teams when a certain task is done. To do so, I created a webhook in my channel and wanted to send a POST request to my webhook URL. However, when doing so, I receive a 405 error. When trying it out in Postman it does work with the same JSON data.

My code (I slightly altered the code from https://gist.github.com/johnsibly/806a7190fbdc4b439ed040f327e6e679 for this test)

const axios = require('axios'); // axios must be installed via npm i axios
const webhookURL = "https://outlook.office.com/webhook/xxxxx"; // this holds my webhook URL

postMessageToTeams();

async function postMessageToTeams(title, message) {
    const card = {
      "@type": "MessageCard",
      "@context": "http://schema.org/extensions",
      "themeColor": "0072C6",
      "summary": "Summary description",
      "sections": [
        {
          "activityTitle": "Testcafe",
          "text": "Testcafe is talking to you"
        }
      ]
    };

    try {
      const response = await axios.post(webhookURL, card, {
        headers: {
          'content-type': 'application/vnd.microsoft.teams.card.o365connector',
          'content-length': `${card.toString().length}`,
        },
      });
      return `${response.status} - ${response.statusText}`;
    } catch (err) {
        console.log(err)
      return err;
    }
  }

Error response

{
  status: 405,
  statusText: 'Method Not Allowed',
  headers: {
    'cache-control': 'no-cache',
    pragma: 'no-cache',
    allow: 'POST',
    'content-length': '43',
    'content-type': 'text/plain; charset=utf-8',
    expires: '-1',
    server: 'Microsoft-IIS/10.0',
    'request-id': 'b565e1ec-79a2-4fc1-99e1-56e8caa98a48',
    'strict-transport-security': 'max-age=31536000; includeSubDomains; preload',
    'x-calculatedbetarget': 'AM0PR02MB5570.eurprd02.prod.outlook.com',
    'x-backendhttpstatus': '405',
    'x-aspnet-version': '4.0.30319',
    'x-cafeserver': 'AM3PR07CA0145.EURPRD07.PROD.OUTLOOK.COM',
    'x-beserver': 'AM0PR02MB5570',
    'x-proxy-routingcorrectness': '1',
    'x-proxy-backendserverstatus': '405',
    'x-powered-by': 'ASP.NET',
    'x-feserver': 'AM3PR07CA0145',
    date: 'Fri, 16 Oct 2020 07:58:34 GMT',
    connection: 'close'
  },
  config: {
    url: 'https://outlook.office.com/webhook/xxxx',
    method: 'post',
    data: '{"@type":"MessageCard","@context":"http://schema.org/extensions","themeColor":"0072C6","summary":"Summary description","sections":[{"activityTitle":"Testcafe","text":"Testcafe is talking to you"}]}',
    headers: {
      Accept: 'application/json, text/plain, */*',
      'User-Agent': 'axios/0.20.0'
    },
    transformRequest: [ [Function: transformRequest] ],
    transformResponse: [ [Function: transformResponse] ],
    timeout: 0,
    adapter: [Function: httpAdapter],
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
    maxContentLength: -1,
    maxBodyLength: -1,
    validateStatus: [Function: validateStatus]
  },
  request: <ref *1> ClientRequest {
    _events: [Object: null prototype] {
      socket: [Function (anonymous)],
      abort: [Function (anonymous)],
      aborted: [Function (anonymous)],
      connect: [Function (anonymous)],
      error: [Function (anonymous)],
      timeout: [Function (anonymous)],
      prefinish: [Function: requestOnPrefinish]
    },
    _eventsCount: 7,
    _maxListeners: undefined,
    outputData: [],
    outputSize: 0,
    writable: true,
    _last: true,
    chunkedEncoding: false,
    shouldKeepAlive: false,
    useChunkedEncodingByDefault: false,
    sendDate: false,
    _removedConnection: false,
    _removedContLen: false,
    _removedTE: false,
    _contentLength: 0,
    _hasBody: true,
    _trailer: '',
    finished: true,
    _headerSent: true,
    socket: TLSSocket {
      _tlsOptions: [Object],
      _secureEstablished: true,
      _securePending: false,
      _newSessionPending: false,
      _controlReleased: true,
      _SNICallback: null,
      servername: 'outlook.office.com',
      alpnProtocol: false,
      authorized: true,
      authorizationError: null,
      encrypted: true,
      _events: [Object: null prototype],
      _eventsCount: 9,
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: 'outlook.office.com',
      _readableState: [ReadableState],
      readable: true,
      _maxListeners: undefined,
      _writableState: [WritableState],
      writable: false,
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: undefined,
      _server: null,
      ssl: [TLSWrap],
      _requestCert: true,
      _rejectUnauthorized: true,
      parser: null,
      _httpMessage: [Circular *1],
      [Symbol(res)]: [TLSWrap],
      [Symbol(asyncId)]: 15,
      [Symbol(kHandle)]: [TLSWrap],
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0,
      [Symbol(connect-options)]: [Object]
    },
    _header: 'GET /webhook/xxxx HTTP/1.1\r\n' +
      'Accept: application/json, text/plain, */*\r\n' +
      'User-Agent: axios/0.20.0\r\n' +
      'Host: outlook.office.com\r\n' +
      'Connection: close\r\n' +
      '\r\n',
    _onPendingData: [Function: noopPendingOutput],
    agent: Agent {
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      defaultPort: 443,
      protocol: 'https:',
      options: [Object],
      requests: {},
      sockets: [Object],
      freeSockets: {},
      keepAliveMsecs: 1000,
      keepAlive: false,
      maxSockets: Infinity,
      maxFreeSockets: 256,
      maxCachedSessions: 100,
      _sessionCache: [Object],
      [Symbol(kCapture)]: false
    },
    socketPath: undefined,
    method: 'GET',
    maxHeaderSize: undefined,
    path: '/webhook/xxxx',
    _ended: true,
    res: IncomingMessage {
      _readableState: [ReadableState],
      readable: false,
      _events: [Object: null prototype],
      _eventsCount: 3,
      _maxListeners: undefined,
      socket: [TLSSocket],
      httpVersionMajor: 1,
      httpVersionMinor: 1,
      httpVersion: '1.1',
      complete: true,
      headers: [Object],
      rawHeaders: [Array],
      trailers: {},
      rawTrailers: [],
      aborted: false,
      upgrade: false,
      url: '',
      method: null,
      statusCode: 405,
      statusMessage: 'Method Not Allowed',
      client: [TLSSocket],
      _consuming: false,
      _dumped: false,
      req: [Circular *1],
      responseUrl: 'https://outlook.office.com/webhook/xxxx',
      redirects: [],
      [Symbol(kCapture)]: false
    },
    aborted: false,
    timeoutCb: null,
    upgradeOrConnect: false,
    parser: null,
    maxHeadersCount: null,
    reusedSocket: false,
    _redirectable: Writable {
      _writableState: [WritableState],
      writable: true,
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      _options: [Object],
      _ended: true,
      _ending: true,
      _redirectCount: 1,
      _redirects: [],
      _requestBodyLength: 197,
      _requestBodyBuffers: [],
      _onNativeResponse: [Function (anonymous)],
      _currentRequest: [Circular *1],
      _currentUrl: 'https://outlook.office.com/webhook/xxxx',
      _isRedirect: true,
      [Symbol(kCapture)]: false
    },
    [Symbol(kCapture)]: false,
    [Symbol(kNeedDrain)]: false,
    [Symbol(corked)]: 0,
    [Symbol(kOutHeaders)]: [Object: null prototype] {
      accept: [Array],
      'user-agent': [Array],
      host: [Array]
    }
  },
  data: 'Invalid webhook request - GET not supported'
}

If you'd need the entire error message in order to properly debug this, please let me know.

Postman

Body (raw) of POST request

{
  "@type": "MessageCard",
  "@context": "http://schema.org/extensions",
  "themeColor": "0072C6",
  "summary": "Summary description",
  "sections": [
    {
      "activityTitle": "Testcafe",
      "text": "Testcafe is talking to you"
    }
  ]
}

Response

1, and the message is posted in my Teams channel

1 Answers

You should let axios handle the calculation of the payload length, and therefore remove this line:

'content-length': `${card.toString().length}`

Your resulting script from your post would look like:

const axios = require('axios'); // axios must be installed via npm i axios
const webhookURL = "https://outlook.office.com/webhook/xxxxx"; // this holds my webhook URL

postMessageToTeams();

async function postMessageToTeams(title, message) {
    const card = {
      "@type": "MessageCard",
      "@context": "http://schema.org/extensions",
      "themeColor": "0072C6",
      "summary": "Summary description",
      "sections": [
        {
          "activityTitle": "Testcafe",
          "text": "Testcafe is talking to you"
        }
      ]
    };

    try {
      const response = await axios.post(webhookURL, card, {
        headers: {
          'content-type': 'application/vnd.microsoft.teams.card.o365connector'
        },
      });
      return `${response.status} - ${response.statusText}`;
    } catch (err) {
        console.log(err)
      return err;
    }
  }

Found this working for me.

Related