Hono 웹훅: 원본 본문(raw body)으로 서명 검증하기
c.req.text()로 원본 본문을 읽어 HMAC 서명을 검증한 뒤, 그 문자열을 JSON.parse하고 204로 응답하세요. Workers, Bun, Deno, Node에서 동작합니다.

Hono에서 웹훅을 처리하려면, 어떤 코드도 본문을 파싱하기 전에 await c.req.text()로 원본 본문을 읽고, 바로 그 문자열로 서명을 검증한 뒤, 같은 문자열을 JSON.parse하고 빠르게 응답하세요. Sume 웹훅이라면 @sume-com/sdk의 verifyWebhook이 WebCrypto로 검증하므로, Hono 라우트 하나가 Cloudflare Workers, Bun, Deno, Node.js에서 모두 동작합니다.
Hono 관련 내용은 HonoRequest, Context, Adapter Helper, Body Limit 문서와 Stripe Webhook 예제에서, Sume 관련 내용은 웹훅 검증, TypeScript SDK 페이지, Run 웹훅 (영문)에서 가져왔습니다. 모두 2026-09-28에 확인했습니다. Sume는 Hono 미들웨어를 제공하지 않습니다. 아래 라우트는 SDK를 호출하는 평범한 Hono 코드입니다. 프레임워크 없이 특정 런타임에 맞춘 수신기는 Cloudflare Workers로 Sume 영상 웹훅을 Queue에 넣기와 Supabase Edge Function의 Sume 웹훅에 있습니다.
Hono에서 원본 요청 본문은 어떻게 가져오나요?
c.req.text()를 호출하세요. Hono의 Stripe 예제에 따르면 서명 검증에는 수정되지 않은 원본 요청 본문이 필요하며, Hono에서는 context.req.text()로 가져옵니다. 요청 객체에는 다른 읽기 메서드도 있지만, 필요한 바이트를 그대로 유지하는 것은 그중 일부뿐입니다.
| 호출 | 반환하는 것 | 서명된 웹훅에서는 |
|---|---|---|
c.req.text() | 문자열로 된 원본 요청 본문 | 이것을 검증한 뒤 같은 문자열을 JSON.parse |
c.req.arrayBuffer() | ArrayBuffer로 된 요청 본문 | 이것도 가능. verifyWebhook은 ArrayBuffer를 받음 |
c.req.json() | 파싱된 application/json 본문 | 검증에 쓰지 말 것. 파싱했다가 다시 직렬화한 객체는 검증되지 않음 |
c.req.raw | 원본 Request 객체 | 이 객체의 headers를 verifyWebhook에 전달 |
hono/request에서 import한 cloneRawRequest(c.req) | 검증기(validator)나 HonoRequest 메서드가 본문을 소비한 뒤에도 얻을 수 있는 원본 Request의 복제본 | 미들웨어가 본문을 먼저 읽었을 때 사용 |
Hono 라우트에서 Sume 웹훅은 어떻게 검증하나요?
Sume는 <timestamp>.<raw_body>에 HMAC-SHA256으로 서명하고 x-sume-webhook-signature에 sume-v1=<hex>를 담아 보냅니다. verifyWebhook은 원본 본문, 헤더, 서명 시크릿을 받으며, 잘못된 전달에는 예외를 던지지 않고 false를 반환합니다. 상수 시간으로 비교하고, HMAC을 계산하기 전에 재전송 허용 시간(기본값 300초)을 적용합니다. 비동기 함수이므로 await하세요.
시크릿은 Hono의 env(c)로 읽고, event로 라우팅한 뒤 중복을 제거하세요. 실행 웹훅은 request_id, Job 웹훅은 job_id가 기준입니다.
import { Hono } from "hono";
import { env } from "hono/adapter";
import { verifyWebhook } from "@sume-com/sdk";
type Env = { SUME_COM_WEBHOOK_SIGNING_SECRET: string };
const app = new Hono();
app.post("/hooks/sume", async (c) => {
const body = await c.req.text(); // raw, before any JSON.parse
const ok = await verifyWebhook({
body,
headers: c.req.raw.headers,
secret: env<Env>(c).SUME_COM_WEBHOOK_SIGNING_SECRET,
});
if (!ok) return c.text("bad signature", 401);
const event = JSON.parse(body); // the verified string, never re-serialized
if (event.event === "format.run.terminal") await recordOnce(event.request_id, event);
else if (event.event?.startsWith("job.")) await recordOnce(event.job_id, event);
return c.body(null, 204); // fast 2xx, unknown events included
});
export default app;같은 라우트가 Workers, Bun, Deno, Node.js에서 모두 동작하나요?
네. Hono는 Cloudflare Workers, Deno, Bun, Node.js를 포함한 모든 JavaScript 런타임에서 동작하며, 같은 코드가 모든 플랫폼에서 실행된다고 밝힙니다. Node.js에서는 Hono 가이드대로 Node.js 어댑터, 즉 @hono/node-server의 serve(app)로 앱을 실행합니다. @sume-com/sdk에는 fetch와 WebCrypto가 필요하므로 Node 18+, Bun, Deno, Cloudflare Workers 중 하나여야 합니다. verifyWebhook은 node:crypto 대신 WebCrypto를 쓰며, 그래서 Workers와 Deno에서도 import할 수 있습니다. Hono의 env(c)는 Node.js와 Bun에서는 process.env를, Deno에서는 Deno.env를, Cloudflare에서는 시크릿을 포함한 Worker 바인딩을 읽습니다.
라우트는 언제 무엇으로 응답해야 하나요?
- 검증에 실패하면 아무것도 파싱하기 전에
401로 응답합니다. - 이벤트를 기록한 뒤
204로 응답하고, 느린 작업은 그다음에 합니다. Sume는 시도당 10초를 허용하고 느린 엔드포인트에는 재시도하므로request_id나job_id로 중복을 제거하세요. 재시도에서도 이 값들은 반복됩니다. - 모르는 이벤트 타입에도
204로 응답합니다. Sume 문서에 따르면 그래야 새로 추가된 이벤트 타입이 500과 재시도 폭주로 번지지 않습니다. - 라우트에 Hono의 Body Limit 미들웨어를 붙인다면
maxSize를 1 MiB보다 크게 설정하세요. Sume는 1 MiB까지의 실행 영수증은 본문에 그대로 담고, 그보다 크면payload: null과result_url을 보냅니다. - SDK 없이도 검증 방식은 같습니다.
<timestamp>.<raw_body>에 대한 HMAC-SHA256, 모든sume-v1=항목의 상수 시간 비교, 오래된 타임스탬프와 빈 시크릿 거부입니다. 규칙은 웹훅 보안 모범 사례에 정리되어 있습니다.
출처
관련 글
연동 카테고리의 다른 글
- IntelliJ GitHub Copilot MCP 설정: Sume 추가하기
IntelliJ IDEA의 GitHub Copilot에서는 Agent 모드의 Add MCP Tools로 MCP 서버를 추가합니다. Sume 호스팅 MCP는 API 키 헤더와 함께 servers에 넣으세요.
- fetch 타임아웃 설정: AbortSignal.timeout과 재시도
fetch()에는 timeout 옵션이 없습니다. signal: AbortSignal.timeout(ms)를 넘기고 TimeoutError를 잡은 뒤, 네트워크 오류와 429, 5xx는 백오프로 재시도하세요.
- Jenkins Build periodically: cron 문법과 H 기호
Jenkins의 Build periodically는 cron 필드 5개에 H를 더해 받습니다. H는 작업 이름의 해시로 시작 시각을 분산하며, H 20 * * *는 오후 8시대에 한 번 실행됩니다.
- Kilo Code MCP 서버: kilo.jsonc에 Sume 추가하기
kilo.jsonc의 mcp 키 아래에 Sume 호스팅 MCP 서버를 추가하세요. type은 remote로 두고, Sume URL에 API 키 헤더를 더하거나 OAuth로 로그인합니다.
작성자 Sume