웹훅

Direct API 생성 작업이 끝나면 서명된 알림을 받고, 서명을 검증한 다음, 생성 작업을 조회해 세부 정보를 확인합니다.

웹훅은 생성 작업이 최종 상태에 도달했을 때 Morphic이 호출하는 사용자의 HTTPS 엔드포인트입니다. 전달은 서명되고, 약 하루 동안 재시도되며, 로그로 남습니다. 각 이벤트는 간결한 알림입니다. 어떤 생성 작업이 어떻게 끝났는지만 알려 주므로, 에셋, 청구 내역, 오류 메시지는 키로 생성 작업을 조회해 확인합니다.

엔드포인트 등록

POST /v1/direct/webhooks에는 generations:write 범위가 필요합니다.

curl -s https://mcp.morphic.com/v1/direct/webhooks \
  -H "Authorization: Bearer $MORPHIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "url": "https://hooks.example.com/morphic",
        "events": ["generation.succeeded", "generation.failed", "generation.cancelled"],
        "description": "Production order pipeline"
      }'
필드규칙
url공개 호스트의 HTTPS(IP 리터럴, 사설 또는 로컬 주소 불가), 최대 2,048자
events하나 이상의 이벤트 유형
description선택 사항, 최대 200자

응답은 201입니다.

{
  "webhook": {
    "id": "01929a10-0c3d-7f44-8a21-0b1c2d3e4f50",
    "url": "https://hooks.example.com/morphic",
    "events": ["generation.succeeded", "generation.failed", "generation.cancelled"],
    "description": "Production order pipeline",
    "created_at": "2026-10-25T08:00:00.000Z",
    "signing_secret": "whsec_4b1e…"
  }
}
  • signing_secret은 이 응답에서만 표시됩니다. API 키처럼 보관하세요.
  • 비밀값을 교체하려면 새 엔드포인트를 등록하고, 호출이 새 엔드포인트의 ID를 사용하도록 바꾼 다음, 이전 엔드포인트를 삭제하세요.
  • 조직은 엔드포인트를 최대 10개까지 보유할 수 있습니다.

엔드포인트 관리

요청범위기능
GET /v1/direct/webhooksassets:read엔드포인트를 { "webhooks": [ … ] } 형태로 나열하며, 비밀값은 절대 포함하지 않음
DELETE /v1/direct/webhooks/{webhook_id}generations:write엔드포인트를 삭제함. 해당 엔드포인트로 아직 보류 중인 전달은 중단됨

호출에 엔드포인트 연결

도구 호출 본문에 webhook_id를 전달하세요. 그 호출이 시작한 모든 생성 작업은 엔드포인트가 구독한 최종 상태에 도달하면 해당 엔드포인트로 전달됩니다. webhook_id가 없는 호출은 웹훅을 보내지 않습니다.

Morphic 서버에서 동영상을 렌더링하는 도구(video_compose, video_* 변환, burn_subtitles)는 아직 webhook_id를 받지 않습니다. 해당 생성 작업은 폴링하세요.

이벤트 유형

이벤트전송 시점
generation.succeeded생성 작업이 출력과 함께 완료됨
generation.failed생성 작업이 실패함. 청구 금액은 환불됨
generation.cancelled생성 작업이 취소됨. 청구 금액은 환불됨
  • 생성 작업마다 이벤트는 최대 하나만 발생합니다.
  • queued와 in_progress는 전달되지 않습니다. 이 상태가 필요하면 폴링하세요.
  • 생성 작업은 시작 후 72시간 동안 감시됩니다. 그 이후에 끝나는 생성 작업은 이벤트를 보내지 않으므로, 직접 조회하세요.

전달 내용

POST /morphic HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
User-Agent: Morphic-Webhooks/1.0
Morphic-Event-Id: 0192a7c0-5d4e-7b3a-8c2d-1e0f9a8b7c6d
Morphic-Delivery-Id: 0192a7c0-6e5f-7c4b-9d3e-2f1a0b9c8d7e
Morphic-Event-Type: generation.succeeded
Morphic-Signature: t=1792919741,v1=5f8a0c…e21b

{
  "id": "0192a7c0-5d4e-7b3a-8c2d-1e0f9a8b7c6d",
  "type": "generation.succeeded",
  "created_at": "2026-10-25T09:15:42.410Z",
  "api_version": "v1",
  "data": {
    "generation": {
      "object": "generation",
      "id": "0192a7b3-a1c4-7e02-8f9d-1a2b3c4d5e6f",
      "tool": "generate_video",
      "status": "succeeded",
      "completed_at": "2026-10-25T09:15:41.902Z",
      "error_code": null,
      "metadata": { "order_id": "4411" }
    }
  }
}

data.generation에는 다음 필드만 포함됩니다.

필드의미
object, id, tool, status, completed_at, metadata생성 작업 객체와 동일
error_codenull, 또는 failed 생성 작업의 코드

이벤트에는 출력, 링크, 청구 내역, 모델, 오류 메시지가 절대 포함되지 않으므로, 전달이 잘못된 곳이나 로그에 남더라도 노출되는 정보가 없습니다. 이벤트를 받으면 키로 생성 작업을 조회하세요. 하나는 GET /v1/direct/generations/{id}로, 여러 개는 일괄 조회로 읽습니다. 이벤트가 전송되는 시점에는 청구 내역에 더 이상 반영될 것이 없으므로, 이 조회 결과가 최종입니다.

헤더의미
Morphic-Event-Id이벤트 ID. 모든 재시도에서 동일하므로 이 값으로 중복을 제거하세요
Morphic-Delivery-Id이번 한 번의 시도. 재시도마다 다름
Morphic-Event-Type본문의 type
Morphic-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256>

이벤트는 보통 생성 작업이 최종 상태에 도달한 후 약 1분 안에 전송됩니다. 실시간 스트림이 아니므로, 기다리고 있는 생성 작업 하나를 가장 빨리 확인하려면 롱 폴링을 사용하세요.

서명 검증

v1은 엔드포인트의 signing_secret을 키로 하여 {t}.{raw request body} 문자열에 대해 계산한 16진수 HMAC-SHA256입니다.

  1. 헤더를 ,로 나누어 t와 v1을 얻습니다.
  2. JSON 파싱이나 재직렬화 전에, 받은 원본 바이트에 대해 HMAC_SHA256(secret, t + "." + raw_body)를 계산합니다.
  3. 상수 시간 비교로 v1과 비교합니다.
  4. 재전송 공격을 막기 위해, t가 현재 시각과 5분 넘게 차이 나면 전달을 거부합니다.
import crypto from 'node:crypto';
import express from 'express';

const app = express();

app.post('/morphic', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('Morphic-Signature') ?? '';
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const expected = crypto
    .createHmac('sha256', process.env.MORPHIC_WEBHOOK_SECRET)
    .update(`${parts.t}.${req.body}`)
    .digest('hex');

  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
  const valid =
    parts.v1?.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));

  if (!fresh || !valid) return res.sendStatus(400);

  const event = JSON.parse(req.body);
  enqueue(event); // process asynchronously, keyed by event.id
  res.sendStatus(204);
});

검증하지 않는 엔드포인트는 열린 문과 같습니다. URL을 알게 된 사람은 누구든 생성 작업이 끝났다고 알릴 수 있습니다. 모든 서명을 검증하고, 5분보다 오래된 타임스탬프는 거부하세요.

응답과 재시도

  • 10초 안에 아무 2xx로 응답한 다음, 작업을 처리하세요.
  • 그 외의 모든 경우는 실패한 시도입니다: 2xx가 아닌 상태, 타임아웃, 연결 거부, TLS 오류.
  • 리디렉션은 따라가지 않으며, 3xx는 실패한 시도입니다.
  • 순서는 보장되지 않으며 이벤트가 두 번 이상 도착할 수 있습니다. 핸들러를 Morphic-Event-Id 기준으로, 또는 생성 작업마다 이벤트가 최대 하나이므로 data.generation.id 기준으로 멱등하게 만드세요.

실패한 시도는 다음 시간 후에 재시도됩니다.

재시도1234567
이전 시도 후 대기 시간1분5분15분1시간3시간6시간12시간

7번째 재시도 후, 즉 첫 시도로부터 약 22시간 후에는 이벤트가 중단됩니다. 생성 작업 자체에는 영향이 없으므로 GET /v1/direct/generations/{id}로 조회하세요.

전달 로그

GET /v1/direct/webhooks/{webhook_id}/events(범위 assets:read)는 엔드포인트의 최근 이벤트 20개를 반환하며, 각 이벤트에는 실제로 전송된 본문과 모든 시도의 결과가 포함됩니다. 생성 작업 하나의 이벤트만 보려면 ?generation_id=…를 추가하세요.

{
  "webhook_events": [
    {
      "id": "0192a7c0-5d4e-…",
      "type": "generation.succeeded",
      "generation_id": "0192a7b3-…",
      "status": "delivered",
      "created_at": "2026-10-25T09:15:42.410Z",
      "payload": { "…": "the body that was sent" },
      "attempts": [
        { "id": "0192a7c0-6e5f-…", "attempted_at": "2026-10-25T09:15:42.900Z", "response_status": 204, "duration_ms": 131, "error": null, "response_body_excerpt": null }
      ]
    }
  ]
}
이벤트 status의미
pending아직 전달되지 않음. 시도가 더 이어질 수 있음
delivered엔드포인트가 2xx로 응답함
abandoned마지막 재시도가 실패했거나 엔드포인트가 삭제됨
skipped엔드포인트가 해당 이벤트 유형을 구독하지 않음

웹훅은 더 저렴하고 빠른 신호일 뿐, 보장은 아닙니다. 소식이 없는 생성 작업은 가끔 폴링하여 안전장치로 삼으세요.


문제가 있거나 궁금한 점이 있으면 언제든지 연락해 주세요. 기꺼이 도와드리겠습니다. 다음 주소로 이메일을 보내 주세요: support@morphic.com.

이 페이지의 내용