Webhook 이벤트 들여다보기

오케스트레이터 2018.4 버전부터 webhook 기능이 추가되었습니다. Webhook을 등록하고 알림을 받을 이벤트를 선택하면 오케스트레이터에서 해당 이벤트가 발생할 때마다 설정된 URL로 HTTP POST request를 던져줍니다. 오케스트레이터에서 특정 이벤트가 트리거될 때마다 외부 시스템과 연동하여 수행할 작업이 있을 경우 매우 유용하게 사용할 수 있습니다. 2019.4 버전 기준으로 알림을 제공하는 이벤트는 아래와 같습니다.

image
image
image
image
image
image

이벤트 알림 request의 HTTP 헤더 부분은 아래와 같이 구성되어 있습니다.

{
  'x-uipath-signature': 'TDYnMlKc5xUyWtXWHWJ3/IHPpve6i5T3FHYy5TsbLvw=',
  'user-agent': 'UiPath-Orchestrator/Webhooks-2019.4.3.0',
  'content-type': 'application/json; charset=utf-8',
  host: 'listener.mycorp.com:8080',
  'content-length': '946',
  connection: 'Keep-Alive'
}

x-uipath-signature는 내가 설정한 webhook으로부터 오는 request가 맞는지를 검증하기 위하여 필요합니다. Webhook을 등록할 때 제공한 secret을 키로 하여 request body에 HMAC-SHA256 해시를 적용한 값입니다. request를 받으면 request body에 해시를 적용하여 연산한 값과 이 값이 동일한지 확인한 후 request를 처리해야 합니다. 이것을 검증하는 C# 코드는 아래와 같이 구성할 수 있습니다.

using System;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
 
 
public async Task<bool> IsValidRequestAsync(HttpRequestMessage request, string secret)
{
    if (!request.Headers.TryGetValues("X-UiPath-Signature", out var headerValues))
        return false;
 
    var orchestratorSignature = Convert.FromBase64String(headerValues.First());
    using (var sha = new HMACSHA256(key: Encoding.UTF8.GetBytes(secret)))
    {
        var computedSignature = sha.ComputeHash(await request.Content.ReadAsByteArrayAsync());
        return ByteArrayEquals(orchestratorSignature, computedSignature);
    }
}

request body는 이벤트마다 구성이 다를 수 있습니다. job.created 이벤트에 대한 request body는 아래와 같이 구성되어 있습니다.

{
  Type: 'job.created',
  EventId: '731574ab3db74941b4a33a465bf3593f',
  Timestamp: '2019-05-29T14:09:13.3726452Z',
  StartInfo: {
    ReleaseKey: 'fec77120-4211-48e5-a9c4-f24a14b533fc',
    Strategy: 'Specific',
    RobotIds: [ 1 ],
    JobsCount: 0,
    Source: 'Manual'
  },
  Jobs: [
    {
      Id: 18,
      Key: '45284110-f11f-408d-aeb5-e2b3dbdb7089',
      State: 'Pending',
      Source: 'Manual',
      SourceType: 'Manual',
      BatchExecutionKey: 'cce461a1-45f9-48a6-a3e5-9bf4e9b0c632',
      ReleaseName: 'Hello_GenericEnv',
      Type: 'Unattended',
      Robot: [Object],
      Release: [Object],
      InputArguments: null,
      OutputArguments: null
    }
  ],
  TenantId: 1,
  OrganizationUnitId: 1,
  UserId: 2
}

오케스트레이터에서 job을 하나 실행시키면 job.created 이벤트와 job.started 이벤트가 연달아 발생하는 것을 볼 수 있는데, 이것은 job이 생성되자마자 로봇에서 곧바로 실행되었기 때문입니다. 만약 로봇이 다른 job을 실행하고 있는 상태라면 job.created 이벤트는 곧바로 발생하지만 job.started 이벤트는 현재 실행중인 job이 종료된 후 새로 지시한 job이 실행을 시작할 때 발생하게 됩니다. job을 생성(create)하여도 항상 곧바로 실행이 시작(start)되는 것은 아니기 때문에 job.created 이벤트와 job.started 이벤트로 이것을 구분하고 있다는 점에 주의할 필요가 있습니다.

아래와 같이 코드를 구성하면 Node.js를 이용하여 request를 직접 확인할 수 있습니다.

const { createServer } = require('http');
const { createHmac } = require('crypto');
 
const PORT = 8080;
const WEBHOOK_SECRET = '<same secret as configured in Orchestrator>';

const isValidRequest = (body /* Buffer */, secret /* string */, expectedSignature /* string */) =>
    expectedSignature == null || createHmac('sha256', secret)
        .update(body)
        .digest('base64') === expectedSignature;

const server = createServer((req, resp) => {

    let body = new Buffer([]);

    req.on('data', chunk => body = Buffer.concat([body, chunk]));

    req.on('end', () => {

        if (!isValidRequest(body, WEBHOOK_SECRET, req.headers['x-uipath-signature'])) {
            console.error('Invalid signature');
            resp.statusCode = 401; // Unauthorized
        } else {

            let payload = JSON.parse(body.toString('utf8'));

            // Process request
            console.log(payload);

            resp.statusCode = 202; // Accepted
        }

        resp.end();
    })
})

server.listen(PORT);
3 Likes

유용한 정보 고맙습니다