Firebase 예약 함수: 유료 API를 하루 한 번 호출하기
onSchedule과 cron, timeZone으로 Firebase 예약 함수를 선언하고, API 키는 secrets에 두고, 유료 호출마다 scheduleTime으로 키를 만드세요.

Firebase 예약 함수는 타이머에 맞춰 실행됩니다. firebase-functions/v2/scheduler의 onSchedule로 함수를 선언하고 "every day 00:00"처럼 Unix crontab 구문이나 App Engine 구문으로 스케줄을 지정하면, 배포할 때 Firebase가 그 시각마다 함수를 호출하는 Cloud Scheduler 작업을 만듭니다. Firebase는 스케줄링 로직을 어떻게 설계하느냐에 따라 함수가 여러 번 트리거될 수 있으며, 이전 인스턴스가 아직 실행 중일 때 다음 인스턴스가 실행될 수 있다고도 경고합니다. 그러므로 유료 API를 호출하는 예약 함수는 호출마다 예약 시각으로 키를 만들어야 합니다.
Firebase 관련 내용은 Firebase의 함수 예약 페이지와 출처에 나열한 레퍼런스 페이지에서, Sume 관련 내용은 Format 호출하기 (영문), 실행과 결과 (영문), 웹훅 (영문)에서 가져왔습니다. 모두 2026-09-28에 확인했습니다. Sume에는 Firebase 전용 연동이 없으며, 함수가 일반 HTTPS 호출을 한 번 보냅니다. Supabase에서 같은 작업을 하는 방법은 Supabase cron Edge Function: AI 영상 예약 실행에서 다룹니다.
API를 호출하는 예약 함수는 어떻게 작성하나요?
스케줄 문자열만 넘기지 말고 옵션 객체를 넘겨, 스케줄이 실행될 시간대인 timeZone과 secrets를 설정하세요. 스케줄은 unix-cron 형식으로 쓰세요. 이 형식의 작업에서 event.scheduleTime은 RFC 3339 UTC로 표시한 작업의 예약 시각이며, 그 날짜로 Idempotency-Key를 만듭니다. 수동으로 트리거하면 이 필드에 예약 시각 대신 실행 시각이 담기므로, 같은 UTC 날짜에 직접 실행해도 같은 키를 보냅니다. 아래 함수는 뉴욕 시각 06:00에 Sume Format 실행 하나를 시작합니다.
const { onSchedule } = require("firebase-functions/v2/scheduler");
const { logger } = require("firebase-functions");
exports.dailyRecap = onSchedule(
{ schedule: "0 6 * * *", timeZone: "America/New_York", secrets: ["SUME_API_KEY"] },
async (event) => {
const day = event.scheduleTime.slice(0, 10); // the slot's UTC date
const res = await fetch("https://api.sume.com/v1/formats/acme/daily-recap/runs", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUME_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `daily-recap-${day}`,
},
body: JSON.stringify({
input: { day },
communication: { webhook_url: "https://example.com/hooks/sume" },
}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${res.status} ${error.code}`);
logger.log("run", data.id, data.idempotency_hit ? "replay" : "new");
},
);예약 함수는 왜 두 번 실행되나요?
Firebase 문서는 스케줄링 로직에 따라 이전 인스턴스가 아직 실행 중일 때 다음 인스턴스가 시작될 수 있다고 경고합니다. 문서의 샘플은 태스크를 직접 실행하는 곳으로 Cloud Scheduler 콘솔도 안내하며, retryCount는 실패한 실행의 재시도 횟수를 정합니다. 같은 UTC 날짜의 반복은 모두 같은 키와 본문을 보내므로, Sume는 원래 영수증과 idempotency_hit: true를 담아 200으로 응답합니다. 두 번째 실행도, 두 번째 청구도 없습니다.
input과 webhook_url은 그 회차에 고정하세요. 같은 키에 다른 본문을 보내면 409 idempotency_conflict가 돌아오고 아무것도 실행되지 않기 때문입니다. 나머지 재전송 사례는 AI 영상 API 멱등성 키에서 다루며, Sume Format 실행 실패에서 설명하듯 failed로 끝난 실행에는 새 키가 필요합니다.
API 키는 어디에 두나요?
Firebase CLI를 통해 Cloud Secret Manager에 둡니다. firebase functions:secrets:set SUME_API_KEY를 실행하면 값을 묻습니다. secrets: ["SUME_API_KEY"]로 바인딩하고 배포하세요. secrets 옵션에 시크릿을 나열한 함수만 그 값을 환경 변수로 받으며, 값을 바꾸면 그 시크릿을 참조하는 함수를 모두 다시 배포해야 합니다. Firebase는 .env 파일이 API 키를 저장하는 안전한 방법이 아니라고 말하고, Sume 문서는 키를 프론트엔드 JavaScript와 모바일 앱에 두지 않도록 하므로, Firebase 클라이언트 앱에서는 절대 API를 호출하지 마세요.
예약 함수는 얼마나 오래 실행될 수 있나요?
최대 30분이지만, 실행은 최대 90분까지 걸릴 수 있습니다. 생성 요청 직후에 반환하세요. 기다리기를 멈춰도 실행과 그 지출은 절대 멈추지 않으며, 실행이 끝나면 Sume가 결과를 전달합니다.
| 한도 | 값 | 출처 |
|---|---|---|
timeoutSeconds를 설정하지 않은 경우 | 60초 | Firebase |
| 예약 함수의 최대값 | 1,800초(30분) | Firebase |
| 롱폼 영상 | 15분에서 30분 걸리는 작업 | Sume |
| 실행 마감 | 생성 후 최대 90분. 그 뒤 failed로 확정 | Sume |
완성된 영상은 어떻게 받나요?
HTTP 함수로 받습니다. 그 URL을 communication.webhook_url에 넣으면, 실행이 완료되거나 실패할 때 Sume가 서명된 format.run.terminal 영수증 하나를 그곳으로 POST합니다. 워크스페이스 서명 시크릿은 두 번째 Firebase 시크릿으로 두고 그 함수의 secrets에도 나열하세요. 시크릿을 나열하지 않은 함수는 undefined 값을 받으므로, 값이 비어 있으면 검증을 거부하세요.
파싱하기 전에 원시 바이트를 대상으로 <timestamp>.<raw_body>에 대한 HMAC-SHA256 서명을 검증하고, 이벤트를 기록한 뒤, 10초 안에 2xx 중 아무 응답이나 보내세요. 나머지 검사, 즉 시크릿 교체 때 추가되는 sume-v1= 항목, 타임스탬프 허용 범위, 중복 제거는 Sume 영상 실행용 서명된 웹훅에서 다룹니다.
출처
관련 글
연동 카테고리의 다른 글
- Hermes Agent MCP 서버: config.yaml에 Sume 추가
config.yaml의 mcp_servers 아래에 Sume 호스팅 MCP 서버를 추가해 Hermes Agent에 연결하세요. API 키 헤더나 OAuth를 쓰고, 유료 도구는 승인을 받게 합니다.
- Hono 웹훅: 원본 본문(raw body)으로 서명 검증하기
c.req.text()로 원본 본문을 읽어 HMAC 서명을 검증한 뒤, 그 문자열을 JSON.parse하고 204로 응답하세요. Workers, Bun, Deno, Node에서 동작합니다.
- 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는 백오프로 재시도하세요.
작성자 Sume