Durable Functions 외부 이벤트 대기: AI 영상 웹훅
Durable Functions 오케스트레이터는 웹훅 함수가 발생시키는 외부 이벤트를 기다리고, POST가 오지 않는 실행에 대비해 지속성 타이머와 경쟁시킵니다.

Azure Durable Functions에서 오케스트레이터는 wait_for_external_event(JavaScript에서는 waitForExternalEvent)로 외부 이벤트를 기다리고, 클라이언트 함수는 raise_event로 오케스트레이션의 인스턴스 ID에 그 이벤트를 발생시킵니다. 이 대기에는 기본적으로 기한이 없으므로 지속성 타이머와 경쟁시키세요. AI 영상 작업을 기다리려면 액티비티가 작업을 시작하고, 작업의 웹훅이 도착하면 HTTP 트리거 함수가 이벤트를 발생시킵니다.
Azure 관련 내용은 Microsoft Learn의 외부 이벤트, 지속성 타이머, 코드 제약 조건, 바인딩, Durable Functions HTTP API, HTTP 트리거 페이지와 Python 오케스트레이션 컨텍스트, 클라이언트 레퍼런스에서, Sume 관련 내용은 Format 호출하기 (영문), 실행과 결과 (영문), Run 웹훅 (영문)에서 가져왔습니다. 모두 2026-09-27에 확인했습니다. Sume에는 Azure 전용 커넥터가 없으며, 함수가 Sume의 HTTPS API를 직접 호출합니다.
각 구성 요소는 Sume 영상 실행과 어떻게 맞물리나요?
오케스트레이터 하나, 액티비티 두 개, HTTP 트리거 함수 하나로 이루어지며, 각각 맡은 일이 하나씩입니다. 오케스트레이터 코드는 결정적이어야 하므로 네트워크 호출은 액티비티에 둡니다.
| 구성 요소 | Durable Functions 규칙 | Sume 쪽 |
|---|---|---|
start_sume_run 액티비티 | 외부로 나가는 네트워크 호출은 오케스트레이터가 아니라 액티비티에 둡니다. | 같은 Idempotency-Key, 같은 본문이면 원래 실행이 돌아오며 두 번째 청구는 없습니다. |
wait_for_external_event("SumeRunEnded") | 무기한 기다립니다. 오케스트레이터가 수신 대기하기 전에 발생한 이벤트는 수신 대기를 시작할 때까지 큐에 보관됩니다. | Sume는 실행이 완료되거나 실패할 때 한 번 POST합니다. |
task_any로 경쟁시키는 create_timer | 이벤트가 이기면 타이머를 취소하세요. 그러지 않으면 타이머가 발동할 때까지 인스턴스가 살아 있습니다. | 실행은 created_at으로부터 90분 뒤 failed로 강제 종료됩니다. |
raise_event를 호출하는 HTTP 함수 | 이벤트는 최소 한 번 전달됩니다. raise_event는 404나 400 응답에서 예외를 던집니다. | 10초 안에 2xx로 응답하세요. Sume는 최대 10회 시도까지 재시도합니다. |
오케스트레이터 코드는 어떤 모습인가요?
Python v2 프로그래밍 모델에서 오케스트레이터는 실행을 시작하고, 이벤트를 타이머와 경쟁시키며, 어느 쪽이 이기든 실행을 읽습니다. current_utc_datetime을 쓰면 재생할 때마다 기한이 같게 유지됩니다.
start_sume_run(instance_id)는Idempotency-Key를 인스턴스 ID로,communication.webhook_url을 웹훅 함수 URL 뒤에?instance=<instance_id>를 붙인 값으로 설정해 Format 실행을 POST하고,data.id를 반환합니다. 기본 URL과SUME_API_KEY는 앱 설정에서 읽는데, 오케스트레이터는 앱 설정을 직접 읽어서는 안 됩니다.read_sume_run(run_id)는/v1/format-runs/{run_id}를 GET하고, 영수증에서status,primary_output_url,error를 반환합니다.
import azure.functions as func
import azure.durable_functions as df
from datetime import timedelta
myApp = df.DFApp(http_auth_level=func.AuthLevel.ANONYMOUS)
@myApp.orchestration_trigger(context_name="context")
def video_orchestrator(context: df.DurableOrchestrationContext):
run_id = yield context.call_activity("start_sume_run", context.instance_id)
deadline = context.current_utc_datetime + timedelta(minutes=100)
timeout_task = context.create_timer(deadline)
ended_task = context.wait_for_external_event("SumeRunEnded")
winner = yield context.task_any([ended_task, timeout_task])
if winner == ended_task:
timeout_task.cancel()
# Event or timeout (a canceled run never POSTs): read the receipt once.
return (yield context.call_activity("read_sume_run", run_id))웹훅 함수는 이벤트를 어떻게 발생시키나요?
Sume는 <timestamp>.<raw_body>에 HMAC-SHA256으로 서명하므로, 파싱하기 전에 req.get_body()의 원본 바이트를 확인하세요. 빈 시크릿은 거부하고, 시계 오차는 오 분까지 허용하며, sume-v1= 항목 중 하나라도 일치하면 수락하고, 비교에는 hmac.compare_digest를 쓰세요. 그런 다음 URL에 지정된 인스턴스에 이벤트를 발생시키세요.
- 인증 수준은
anonymous로 두고 서명에 의존하세요. Sume의 전달에는 Azure 키가 아니라 Sume 자체의 서명 헤더가 실리므로, 함수 키를 쓰려면 URL의code매개변수에 실어야 하고, 모든 영수증에 그 URL이webhook_delivery.url로 표시됩니다. - Durable Functions HTTP API의 기본 제공
raiseEventURL을 Sume에 넘기지 마세요. 이 URL에는 모든 Durable Functions HTTP API에 접근할 수 있는 시스템 키가 담겨 있고, 그 경로에는 Sume의 서명을 확인하는 곳이 없습니다. raise_event는 404나 400 응답에서 예외를 발생시킵니다. 예외를 잡고 그래도2xx로 응답하세요. 그 실행은 오케스트레이터의 타이머 경로가 처리합니다.
import hashlib, hmac, json, os, time
@myApp.route(route="sume-webhook", methods=[func.HttpMethod.POST])
@myApp.durable_client_input(client_name="client")
async def sume_webhook(req: func.HttpRequest, client: df.DurableOrchestrationClient):
raw = req.get_body() # the exact bytes Sume signed
secret = os.environ.get("SUME_COM_WEBHOOK_SIGNING_SECRET", "").encode()
ts = req.headers.get("x-sume-webhook-timestamp", "")
if not secret or not ts.isdecimal() or abs(time.time() - int(ts)) > 300:
return func.HttpResponse(status_code=401)
expected = "sume-v1=" + hmac.new(secret, ts.encode() + b"." + raw, hashlib.sha256).hexdigest()
entries = req.headers.get("x-sume-webhook-signature", "").split(",")
if not any([hmac.compare_digest(e.strip(), expected) for e in entries]):
return func.HttpResponse(status_code=401)
event = json.loads(raw)
try:
await client.raise_event(req.params.get("instance"), "SumeRunEnded", {"run_id": event["run_id"]})
except Exception:
pass # instance gone: its timer path reads the run
return func.HttpResponse(status_code=204)Durable Functions는 기다리는 동안 타임아웃되나요?
아니요, 타임아웃되지 않습니다. 외부 이벤트 대기 API는 무기한 기다리고, 기다리는 동안 함수 앱을 언로드할 수 있으며, Consumption 플랜에서는 오케스트레이터가 외부 이벤트를 기다리는 동안 요금이 발생하지 않습니다. 타임아웃되는 것은 개별 함수 실행입니다. 액티비티는 앱의 함수 타임아웃 안에 끝나야 하며, 기본값은 Flex Consumption 플랜에서 30분, 레거시 Consumption 플랜에서 5분입니다. Sume 실행을 시작하는 것은 HTTPS 호출 한 번이므로, 영상에 드는 몇 분은 함수가 실행되는 시간이 아니라 기다리는 시간입니다.
여러분이 정하는 타임아웃은 타이머의 타임아웃입니다. 100분 타이머는 Sume의 90분 기한보다 오래 가므로, read_sume_run이 확인할 때쯤이면 실행은 끝나 있습니다. POST를 보내지 않는 실행도 이 읽기로 알게 됩니다. 취소된 실행은 웹훅을 보내지 않고, 엔드포인트가 10회 시도를 모두 거부하면 아무것도 전달되지 않은 채 실행은 그대로 남습니다. 이벤트가 이기면 타이머를 취소하세요. Durable Functions는 타이머가 아직 남아 있는 동안에는 오케스트레이션을 Completed로 표시하지 않습니다.
출처
- Format 호출하기 (영문)
- 실행과 결과 (영문)
- Run 웹훅 (영문)
- 웹훅 (영문)
- Azure Durable Functions: 외부 이벤트 (2026-09-27 확인)
- Azure Durable Functions: 지속성 타이머 (2026-09-27 확인)
- Azure Durable Functions: 오케스트레이터 코드 제약 조건 (2026-09-27 확인)
- Azure Durable Functions: 바인딩 (2026-09-27 확인)
- Azure Durable Functions: HTTP API 레퍼런스 (2026-09-27 확인)
- Azure Durable Functions Python: DurableOrchestrationContext (2026-09-27 확인)
- Azure Durable Functions Python: DurableOrchestrationClient (2026-09-27 확인)
- Azure Functions: HTTP 트리거 (2026-09-27 확인)
- Azure Functions: 호스팅 옵션 (2026-09-27 확인)
- Azure Functions: 함수 앱 설정 구성 (2026-09-27 확인)
- Python: hmac 모듈 (2026-09-27 확인)
관련 글
연동 카테고리의 다른 글
- Express 웹훅 서명 검증: raw body와 100kb 한도
Sume 웹훅 라우트에 type은 application/json, limit은 1 MiB보다 크게 설정한 express.raw를 붙이고, 원본 Buffer를 verifyWebhook에 넘기세요.
- FastAPI 오래 걸리는 작업: 202와 Job ID로 응답하기
FastAPI에서 오래 걸리는 작업은 요청을 열어 둔 채 기다리지 말고 Job ID와 함께 202로 응답하세요. AI 영상 Job이라면 작업은 Sume가 하고 결과는 웹훅으로 옵니다.
- Gemini CLI MCP 서버: Sume 호스팅 MCP 추가하기
httpUrl과 환경 변수에서 읽는 API 키 헤더로 Sume 호스팅 MCP 서버를 Gemini CLI에 추가하고, 도구는 허용 목록으로 추린 뒤 호출 전에 확인하세요.
- GitHub Actions: Sume Format으로 릴리스 영상 만들기
GitHub 릴리스가 게시되면 Sume Format 실행을 시작하고, 릴리스 노트를 input으로 넘기고, 영상이 준비될 때까지 폴링한 뒤 릴리스에 첨부하세요.
작성자 Sume