---
title: 웹훅
description: Sume에서 서명된 종료 Job 이벤트를 받아보세요.
---

Job이 종료 상태(terminal)에 도달했을 때 서버가 알림을 받아야 한다면 웹훅을 사용하세요.

**이 페이지는 생성 Job에 대한 문서입니다.** Sume에는 웹훅 표면이 두 가지 있습니다.

| You called | You get | Documented at |
|---|---|---|
| `POST /v1/models/...` 또는 `/v1/avatar-1.0/generate` 같은 모델 엔드포인트 | `job.completed` / `job.failed` / `job.canceled` | 이 페이지 |
| Action, Format, Agent Completion run 엔드포인트 | `action.run.terminal` / `format.run.terminal` / `agent.run.terminal` | [Run 웹훅](/agents/run-webhooks) |

이벤트 집합은 겹치지 않고 payload도 다릅니다. run 웹훅은 Job 결과가 아니라 전체 run receipt를 담습니다. 서명 방식은 동일하므로 검증기 하나로 둘 다 처리할 수 있습니다.

## 웹훅 URL과 함께 제출하기

`mode: "webhook"`와 `webhook_url`을 보내세요.

```bash
curl -X POST https://api.sume.com/v1/avatar-1.0/generate \
  -H "Authorization: Bearer $SUME_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: webhook-avatar-001" \
  -d '{
    "avatar_handle": "webhook_presenter",
    "input": {
      "type": "prompt",
      "prompt": "A friendly presenter in neutral studio lighting"
    },
    "mode": "webhook",
    "webhook_url": "https://example.com/sume/webhook"
  }'
```

웹훅 URL은 공개 HTTPS URL이어야 합니다. Localhost, private-network, non-HTTPS URL은 거절됩니다.

웹훅은 네 가지 통신 모드 중 하나입니다. `async`, `sync`, `subscribe`와 어떻게 다른지, 그리고 웹훅과 함께 유지해야 할 폴링 폴백은 [통신 모드](/workflows/jobs-and-results)에서 살펴보세요.

## 이벤트

Sume는 **종료 Job 이벤트만** 보냅니다. 진행 상황이나 부분 전달은 없습니다.

| Event | When it is sent |
|---|---|
| `job.completed` | Job이 완료되어 공개 결과를 사용할 수 있을 때입니다. |
| `job.failed` | Job이 공개 오류와 함께 실패했을 때입니다. |
| `job.canceled` | Job이 canceled 상태에 도달했을 때입니다. |

## Payload

```json
{
  "event": "job.completed",
  "request_id": "job_...",
  "job_id": "job_...",
  "status": "OK",
  "payload": {
    "artifacts": [
      {
        "id": "artifact_...",
        "url": "https://media.sume.com/artifacts/...",
        "type": "image",
        "content_type": "image/png"
      }
    ]
  }
}
```

실패·취소 웹훅은 `status: "ERROR"`를 쓰고 `error` 객체를 포함합니다.

## 서명 헤더

웹훅 서명이 설정돼 있으면 Sume는 원본 JSON body를 아래 문자열에 대해 HMAC SHA-256으로 서명합니다.

```text
<timestamp>.<raw_body>
```

헤더:

```text
x-sume-webhook-timestamp: 1780000000
x-sume-webhook-signature: sume-v1=<hex_signature>
```

타임스탬프가 재생 허용 창을 벗어나면 콜백을 거절하세요. 5분이 합리적 기본값입니다.

서명 시크릿은 대시보드의 **웹훅** 탭(`/dashboard/webhooks` — 표시 후 복사)에서 직접 확인하거나, `account:read` 범위를 가진 API 키로 `GET /v1/webhooks/signing-secret`을 호출해 받을 수 있습니다. 워크스페이스별로 파생된 값이므로 플랫폼 공용 값이 아니라 여러분 전용입니다. 전달 워커가 서명할 때 쓰는 것과 같은 이름인 `SUME_COM_WEBHOOK_SIGNING_SECRET`으로 저장하세요. Job 웹훅과 [Run 웹훅](/agents/run-webhooks)은 이 시크릿 하나를 공유하므로 검증기 하나로 둘 다 처리할 수 있습니다.

모든 전송에는 `x-sume-webhook-secret-fingerprint` 헤더가 실리고, 영수증의 `webhook_delivery.signing_secret_fingerprint`에도 같은 값이 담깁니다. 서명 검증이 실패하면 대시보드에서 시크릿 옆에 표시된 지문과 비교하세요. 시크릿 자체를 주고받을 필요가 없습니다.

## TypeScript에서 검증하기

```ts
import crypto from "node:crypto";

export function verifySumeWebhook({
  rawBody,
  timestamp,
  signatureHeader,
  secret,
  toleranceSeconds = 300,
}: {
  rawBody: string;
  timestamp: string;
  signatureHeader: string;
  secret: string;
  toleranceSeconds?: number;
}) {
  const ts = Number(timestamp);
  if (!Number.isFinite(ts)) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - ts) > toleranceSeconds) {
    return false;
  }

  const digest = crypto
    .createHmac("sha256", secret)
    .update(`${ts}.${rawBody}`)
    .digest("hex");
  const expected = `sume-v1=${digest}`;
  const actualBuffer = Buffer.from(signatureHeader);
  const expectedBuffer = Buffer.from(expected);
  if (actualBuffer.length !== expectedBuffer.length) return false;

  return crypto.timingSafeEqual(actualBuffer, expectedBuffer);
}
```

## 전달 동작

이벤트를 내구성 있게 저장한 뒤 `2xx`를 반환하세요. 네트워크 오류와 non-2xx는 시도가 소진될 때까지 재시도됩니다. 서버 쪽 idempotency 키로는 `job_id`를 사용하세요.

| | |
|---|---|
| 재시도 | 최대 **10회**입니다. |
| 간격 | 지수 백오프가 아니라 시도 사이의 고정 지연(기본 30초)입니다. |
| 타임아웃 | 시도당 10초입니다. 느린 엔드포인트는 이 예산을 소진하고 재시도를 부릅니다. |

10회가 모두 거부되면 남는 것은 실패한 *전달*이며, Job 자체는 실제 종료 상태에 그대로 도달해 있습니다. 전달은 최적화이지 유일한 복구 경로가 아닙니다. 도착하지 않은 이벤트를 위해 `status_url` 폴링을 쓸 수 있게 유지하세요. 전달 카운터는 Job 객체와 Job 이벤트에서 확인할 수 있습니다.

[Run 웹훅](/agents/run-webhooks)도 같은 10회 상한을 쓰지만 일정이 다릅니다. `*.run.terminal` 전달은 그 페이지가 기준입니다.

## 다음

- [Run 웹훅](/agents/run-webhooks) — Action, Format, Agent Completion run에 같은 서명 방식
