Inngest 이벤트 대기: Sume 영상 실행이 끝나면 재개

step.run에서 Sume 실행을 시작하고, transform으로 웹훅을 Inngest 이벤트로 바꾼 뒤, 실행 id를 기준으로 2시간 타임아웃을 둔 step.waitForEvent로 기다리세요.

읽는 시간 6분Sume
전체 글

Inngest 함수가 Sume 영상 실행을 기다리게 하려면 communication.webhook_url을 Inngest 웹훅 URL로 설정해 step.run() 안에서 실행을 시작하고, 그 웹훅의 transform이 Sume의 POST를 sume/format.run.terminal 이벤트로 바꾸게 한 뒤, async.data.run_id를 여러분의 실행 id와 맞춰 보는 if 표현식으로 step.waitForEvent()를 호출하세요. 이 호출은 이벤트를 반환하며, 타임아웃이 먼저 지나면 null을 반환합니다.

Inngest 관련 내용은 Inngest의 step.waitForEvent() 레퍼런스, 이벤트 기다리기 가이드, 웹훅 이벤트 수신, 사용 한도 페이지에서, Sume 관련 내용은 Format 호출하기 (영문), 실행과 결과 (영문), Run 웹훅 (영문)에서 가져왔습니다. 모두 2026-09-27에 확인했습니다. Sume에는 Inngest 전용 연동이 없으며, 함수가 fetch로 Sume의 REST API를 직접 호출합니다. Trigger.dev waitpoint 토큰으로 같은 대기를 구현하는 방법은 Trigger.dev 웹훅 대기에 있습니다.

Sume 실행에는 어떤 waitForEvent 옵션이 맞나요?

step.waitForEvent(id, options)는 일치하는 이벤트가 도착하거나 타임아웃에 도달할 때까지 함수를 일시 중지합니다. Format 실행에는 다음과 같이 설정하세요.

Inngest의 step.waitForEvent() 레퍼런스와 이벤트 기다리기 가이드, Sume의 실행과 결과 (영문) 페이지 기준, 2026-09-27 확인.
옵션값이유
event"sume/format.run.terminal"웹훅 transform이 반환하는 이름입니다. Sume는 Format 실행이 완료되거나 실패하면 format.run.terminal을 한 번 POST합니다.
ifasync.data.run_id == "<run id>"트리거(event)와 대기 이벤트(async)를 대상으로 하는 CEL 표현식입니다. match는 두 이벤트의 같은 속성을 비교하는데, 트리거에는 실행 id가 없습니다.
timeout"2h"기간 문자열, 밀리초 단위 숫자, 날짜 중 하나입니다. Sume는 created_at으로부터 최대 90분 뒤에 실행을 강제 종료합니다.
결과이벤트 또는 nullnull은 타임아웃이 먼저 지났다는 뜻입니다. API에서 실행을 읽으세요.

Sume 웹훅을 Inngest 이벤트로 어떻게 바꾸나요?

Inngest 대시보드의 Manage에서 Webhooks로 들어가 웹훅을 만드세요. 웹훅에는 고유 URL이 생기며, 이 URL을 communication.webhook_url로 보냅니다. 웹훅의 transform은 Inngest 서버에서 실행되는 JavaScript 함수로, 파싱된 JSON, 헤더, 쿼리 파라미터, 원본 본문 문자열을 받아 name과 data가 있는 이벤트를 반환합니다.

서명된 웹훅에 대한 Inngest의 권장 방식은 원본 본문과 서명을 이벤트에 담고 함수 안에서 검증하는 것입니다. 헤더 이름은 정규화된(canonicalized) 형태로 도착하므로 X-Sume-Webhook-Signature를 읽으세요. id를 넣으면 Inngest는 같은 id를 가진 이후 이벤트를 24시간 동안 무시합니다. 이 id는 이벤트 유형과 관계없이 전역으로 적용되므로, Sume의 event와, 재시도해도 바뀌지 않는 request_id를 조합하세요.

// Inngest dashboard: Manage > Webhooks > your Sume webhook > Transform
function transform(evt, headers = {}, queryParams = {}, raw = "") {
  return {
    id: `sume-${evt.event}-${evt.request_id}`, // request_id repeats on Sume retries
    name: `sume/${evt.event}`, // for example sume/format.run.terminal
    data: {
      run_id: evt.run_id,
      raw, // the exact body Sume signed
      sig: headers["X-Sume-Webhook-Signature"], // canonicalized header names
      ts: headers["X-Sume-Webhook-Timestamp"],
    },
  };
}

기다리는 함수는 어떤 모습인가요?

Inngest는 각 스텝을 별도의 HTTP 요청으로 실행하며, API 호출 같은 비결정적 작업은 step.run() 안에 두어야 합니다. 그래서 생성 호출을 스텝으로 두며, 재시도된 스텝은 같은 Idempotency-Key와 본문을 보내고, Sume는 두 번째 청구 없이 원래 실행으로 응답합니다. 서명 검사도 현재 시각을 읽기 때문에 스텝으로 둡니다. @sume-com/sdk의 verifyWebhook은 어느 sume-v1= 항목이든 받아들이고, 기본적으로 300초의 재전송 허용 시간을 적용합니다. 이벤트가 오지 않았거나 검증되지 않으면 함수는 자신의 키로 GET /v1/format-runs/{run_id}를 읽습니다. 코드는 Inngest TypeScript SDK v4 문법으로 작성했습니다.

import { verifyWebhook } from "@sume-com/sdk";
import { inngest } from "./client";
const API = "https://api.sume.com/v1";
const auth = { Authorization: `Bearer ${process.env.SUME_API_KEY}` };

export const productVideo = inngest.createFunction(
  { id: "product-video", triggers: { event: "shop/video.requested" } },
  async ({ event, step }) => {
    const runId = await step.run("start-run", async () => {
      const res = await fetch(`${API}/formats/acme/product-video/runs`, { method: "POST",
        headers: { ...auth, "Content-Type": "application/json", "Idempotency-Key": `video-${event.data.orderId}` },
        body: JSON.stringify({ input: event.data, communication: { webhook_url: process.env.INNGEST_SUME_WEBHOOK_URL } }) });
      return (await res.json()).data.id as string;
    });
    const done = await step.waitForEvent("wait-for-run", {
      event: "sume/format.run.terminal", timeout: "2h", if: `async.data.run_id == "${runId}"` });
    return step.run("read-receipt", async () => {
      const signed = done !== null && (await verifyWebhook({ body: done.data.raw,
        headers: { "x-sume-webhook-signature": done.data.sig, "x-sume-webhook-timestamp": done.data.ts },
        secret: process.env.SUME_COM_WEBHOOK_SIGNING_SECRET! }));
      const receipt = signed ? JSON.parse(done.data.raw).payload : null; // null over 1 MiB
      return receipt ?? (await (await fetch(`${API}/format-runs/${runId}`, { headers: auth })).json()).data;
    });
  },
);

대기가 시작되기 전에 실행이 끝나면 어떻게 되나요?

Inngest 가이드는 이 점을 분명히 밝힙니다. 대기는 그 코드가 실행된 순간부터 이벤트를 수신하므로, step.waitForEvent()가 실행되기 전에 보낸 이벤트는 매칭되지 않습니다. 대기가 등록되기 전에 끝난 실행은 놓치게 되며, 그러면 함수는 타임아웃이 되어서야 폴백 읽기로 결과를 알게 됩니다. 취소되거나 건너뛴 실행은 웹훅을 아예 보내지 않으므로 같은 읽기가 이 경우도 처리합니다. 읽기가 반환하는 영수증의 status를 확인하세요. 타임아웃을 줄이더라도 실행이 취소되지는 않습니다. 실행은 계속 진행되며 계속 과금됩니다.

어떤 한도가 적용되나요?

다음 세 가지에 대비하세요.

  • 이벤트 크기. Inngest는 이벤트 하나의 크기를 Free에서 256 KiB, Basic에서 512 KiB, Pro에서 3 MiB로 제한하며, 원본 본문은 이벤트 안에 담겨 전달됩니다. Sume는 실행 영수증을 1 MiB까지 인라인으로 담으므로, 더 작은 요금제에서는 큰 영수증이 들어가지 않을 수 있습니다. 폴백 읽기로도 영수증을 가져올 수 있지만, 대기가 타임아웃된 뒤에야 가능합니다.
  • transform 오류. transform이 예외를 던지면 Inngest는 400으로 응답합니다. Sume는 이를 실패한 시도로 보고 재시도하며, 시도는 최대 10회입니다.
  • 키. SUME_API_KEY와 서명 시크릿은 프론트엔드 코드가 아니라 앱의 서버 쪽 환경에 두세요. Sume 문서는 키를 신뢰할 수 있는 서버에 두라고 안내합니다. 시크릿 교체와 재전송은 Sume 영상 실행용 서명된 웹훅을 참고하세요.

출처

관련 글

연동 카테고리의 다른 글

연동 글 전체 보기

작성자 Sume