Supabase Edge Function의 Sume 웹훅: JWT 대신 HMAC

Sume의 웹훅 POST에는 Supabase JWT가 없으므로 Edge Function을 verify_jwt = false로 배포하고, 모든 전달에서 Sume의 HMAC 서명을 확인하세요.

읽는 시간 5분Sume
전체 글

Supabase Edge Function은 해당 함수의 Supabase JWT 검사를 끄면 Sume 영상 웹훅을 받을 수 있습니다. Edge Functions는 기본적으로 유효한 JWT를 요구하는데, Sume의 POST에는 JWT가 없습니다. supabase/config.toml에서 그 함수에 verify_jwt = false를 설정하고, 대신 npm:@sume-com/sdk에서 import한 verifyWebhook으로 모든 전달을 Sume의 HMAC 서명으로 인증하세요.

Supabase 관련 내용은 Supabase의 함수 설정, Stripe 웹훅 처리, 백그라운드 작업, 한도 페이지와 출처에 나열한 관련 페이지에서, Sume 관련 내용은 Run 웹훅 (영문), 웹훅 검증, 실행과 결과 (영문)에서 가져왔습니다. 모두 2026-09-27에 확인했습니다. Supabase용 Sume 전용 커넥터는 없으며, 이 함수는 Sume가 POST를 보내는 Deno 함수일 뿐입니다. 웹훅 규약 전반은 Sume 영상 실행용 서명된 웹훅에 있습니다.

Edge Function은 왜 Sume의 POST를 거부하나요?

Edge Functions는 기본적으로 authorization 헤더에 유효한 JWT를 요구하는데, Sume는 Supabase 토큰을 보내지 않습니다. Supabase는 Stripe 웹훅을 예로 들어 이 경우의 해결책을 문서화해 두었습니다. supabase/config.toml에 verify_jwt = false를 담은 함수별 [functions.sume-webhook] 항목을 두는 것입니다. 로컬에서 서빙할 때는 supabase functions serve의 --no-verify-jwt 플래그가 같은 역할을 합니다.

Supabase는 이렇게 하면 누구나 유효한 JWT 없이 함수를 호출할 수 있다고 경고합니다. 그러니 여기서 Sume 서명 검사는 선택 사항이 아닙니다. 공개 URL과 여러분의 데이터베이스 사이를 지키는 것이 바로 이 검사입니다. Supabase의 Stripe 웹훅 함수도 같은 패턴을 따릅니다. supabase functions new의 스타터 템플릿이 publishable 키나 secret 키를 요구하는 것과 달리, 이 함수는 핸들러를 withSupabase({ auth: 'none' })로 감쌉니다.

Edge Function 코드는 어떤 모습인가요?

Edge Functions는 npm: 지정자로 npm 패키지를 import하며, @sume-com/sdk는 Deno에서 동작합니다. 검증에는 원본 바이트가 필요하므로, Stripe 예제처럼 JSON.parse보다 먼저 await req.text()로 본문을 읽으세요. verifyWebhook은 예외를 던지지 않고 false를 반환합니다.

// supabase/functions/sume-webhook/index.ts
import { withSupabase } from "npm:@supabase/server@^1";
import { verifyWebhook } from "npm:@sume-com/sdk@0.2.0";

// Sume signs every delivery, so deploy with verify_jwt = false.
export default {
  fetch: withSupabase({ auth: "none" }, async (req) => {
    const body = await req.text(); // raw, before JSON.parse
    const ok = await verifyWebhook({
      body,
      headers: req.headers,
      secret: Deno.env.get("SUME_COM_WEBHOOK_SIGNING_SECRET")!,
    });
    if (!ok) return new Response("bad signature", { status: 401 });

    const event = JSON.parse(body);
    if (event.event !== "format.run.terminal") return new Response(null, { status: 204 });
    const fresh = await recordOnce(event.request_id, event); // insert-or-ignore
    if (fresh) EdgeRuntime.waitUntil(notifyUser(event)); // runs after the 204
    return new Response(null, { status: 204 });
  }),
};

함수는 응답한 뒤 얼마나 더 작업할 수 있나요?

EdgeRuntime.waitUntil(promise)는 응답을 막지 않으면서 promise가 끝날 때까지 인스턴스를 계속 실행합니다. 그래도 wall clock, CPU, 메모리 한도의 제한은 받으므로, 알림이나 행 업데이트에는 맞지만 추가 생성을 기다리는 데는 맞지 않습니다. 로컬 테스트에서는 CLI가 요청마다 인스턴스를 종료해 백그라운드 작업이 일찍 멈춥니다. Supabase가 제시하는 해결책은 supabase/config.toml의 [edge_runtime] 아래에 policy = "per_worker"를 두는 것입니다.

Supabase의 한도 페이지와 Sume의 Run 웹훅 (영문), Format API (영문) 페이지 기준, 2026-09-27 확인.
한도값
Sume 전달 시도시도당 10초, 최대 10회
Edge Function 실행 시간(wall clock)Free 150초, 유료 요금제 400초
CPU 시간요청당 2초, 비동기 I/O 제외
요청 유휴 타임아웃150초. 그때까지 응답이 없으면 504 Gateway Timeout
메모리256MB
Sume 롱폼 호스트 영상 실행보통 15~30분

시크릿과 URL은 어디서 얻나요?

첫 실행 전에 둘 다 한 번만 설정하세요.

  • 서명 시크릿은 Sume 대시보드의 웹훅 탭에서 복사하거나, account:read가 있는 키로 GET /v1/webhooks/signing-secret에서 읽으세요.
  • supabase secrets set SUME_COM_WEBHOOK_SIGNING_SECRET=…로 저장하세요. 시크릿은 다시 배포하지 않아도 바로 쓸 수 있으며, 이름은 SUPABASE_로 시작할 수 없습니다. 함수는 Deno.env.get으로 이 값을 읽습니다.
  • supabase functions deploy sume-webhook으로 배포하세요. 그러면 함수가 공개 HTTPS URL인 https://[YOUR_PROJECT_ID].supabase.co/functions/v1/sume-webhook에서 실행됩니다. 실행을 시작할 때 이 URL을 communication.webhook_url로 넘기세요.
  • http://localhost:54321/functions/v1/…의 로컬 서버는 전달을 받을 수 없습니다. Sume는 localhost와 HTTPS가 아닌 URL을 거부하기 때문입니다. localhost에서 Sume 웹훅 테스트하기를 참고하세요.

함수는 무엇을 저장해야 하나요?

저장하는 행의 키는 재시도마다 반복되는 request_id로 하고, payload.primary_output_url을 함께 보관하세요. media.sume.com의 Format 실행 미디어 URL은 계속 유지되지만 URL을 가진 누구에게나 공개되므로, 앱에 사용자별 접근 제어가 필요하다면 프록시하거나 복사해 두세요. 1 MiB를 넘는 영수증은 payload: null로 도착합니다. 또 다른 시크릿으로 저장해 둔 API 키로 error.result_url에서 가져오세요. 나머지 전달 규약은 Sume 영상 실행용 서명된 웹훅에서 다룹니다.

출처

관련 글

연동 카테고리의 다른 글

연동 글 전체 보기

작성자 Sume