> ## Documentation Index
> Fetch the complete documentation index at: https://developers.bottari.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 서명 검증하기

> x-bottari-signature 헤더로 웹훅 요청이 Bottari에서 왔는지 확인합니다.

모든 웹훅 요청에는 서명이 있습니다. 서명을 확인하면 요청이 Bottari에서 왔고 본문이 바뀌지 않았음을 알 수 있습니다.

| 항목 | 값 |
| - | - |
| 헤더 | `x-bottari-signature: sha256=<hex>` |
| 방식 | HMAC-SHA256 |
| 키 | 인증 토큰(`bts_...`) |
| 메시지 | 요청 본문의 원문 바이트 |

> [!WARNING]
>
> 본문을 파싱한 뒤 다시 문자열로 만들어 서명하지 마세요. 받은 바이트를 그대로 사용하세요. 재시도 때는 `attempt_number`가 바뀌므로 시도마다 서명이 다릅니다.

## Node 예제

`node:http`와 `node:crypto`만 사용합니다. 환경 변수 `BOTTARI_WEBHOOK_TOKEN`이 없으면 인증 단계로 보고 받은 본문을 로그에 남깁니다.

```js theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";
import { createServer } from "node:http";

const token = process.env.BOTTARI_WEBHOOK_TOKEN;

function isValidSignature(rawBody, header, secret) {
  if (typeof header !== "string") return false;
  const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(header);
  const b = Buffer.from(expected);
  return a.length === b.length && timingSafeEqual(a, b);
}

createServer((req, res) => {
  const chunks = [];
  req.on("data", (chunk) => chunks.push(chunk));
  req.on("end", () => {
    const rawBody = Buffer.concat(chunks);

    // 인증 단계: 받은 토큰을 화면에 넣은 뒤 환경 변수에 넣으세요.
    if (!token) {
      console.log(rawBody.toString());
      res.writeHead(200).end();
      return;
    }

    if (!isValidSignature(rawBody, req.headers["x-bottari-signature"], token)) {
      res.writeHead(401).end();
      return;
    }

    const event = JSON.parse(rawBody);
    console.log(event.id, event.type, event.entity.id);
    // 시간이 걸리는 작업은 큐에 넣고 바로 응답하세요. 15초 안에 2xx로 응답해야 합니다.
    res.writeHead(200).end();
  });
}).listen(3000);
```

> [!NOTE]
>
> 인증을 마치면 환경 변수를 넣어 로그를 남기는 코드가 실행되지 않게 하세요. 로그에 남은 토큰은 지우세요.

Express를 사용한다면 `express.json()` 대신 `express.raw({ type: "application/json" })`로 받아 원문 바이트를 그대로 사용하세요.

## 확인용 값

구현을 확인할 때 사용하세요. 아래 값은 예시이며 실제 토큰이 아닙니다.

| 항목 | 값 |
| - | - |
| 토큰 | `bts_EXAMPLE_TOKEN_DO_NOT_USE_aaaaaaaaaaaaaaaaaa` |
| 본문 | `{"id":"44444444-4444-4444-8444-444444444444","type":"page.created"}` |
| 서명 | `sha256=276158c4322eef888cc3e3c241818c99deb0b5798224809790870df03f9ffbe6` |

## 인증 토큰 요청의 서명

인증 토큰을 보내는 요청도 그 토큰을 키로 서명합니다. 받는 서버에는 아직 키가 없으므로 이 요청의 서명은 확인할 수 없습니다. 토큰을 화면에 넣어야 구독이 켜지므로, 그 주소에서 토큰을 받은 사람만 인증할 수 있습니다.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.