콜백은 실행이 끝났을 때 서버에 알려 주므로, 폴링하거나 이벤트 스트림을 열어 둘 필요가 없습니다. Ephemeral은 실행이 최종 상태에 도달한 뒤 실행 객체를 담은 서명된 POST 요청을 한 번 보냅니다.
콜백 요청
실행 요청에 callback을 추가합니다.
callback.urlstring필수엔드포인트. HTTPS를 사용해야 하며 공용 주소로 해석되어야 합니다. 리디렉션은 따라가지 않습니다.
callback.secretstring필수24~1,024자이며 전송마다 서명하는 데 사용합니다. 무작위 값을 생성해 서버에 보관하세요.
{
"workflow": "investigate-payment",
"input": { "payment_id": "pay_123" },
"max_cost_micros": 500000,
"callback": {
"url": "https://example.com/hooks/ephemeral",
"secret": "3f9a6c1e8b2d47f0a5c3e9d1b7f2a6c4"
}
}시크릿은 실행 정의를 포함해 어떤 조회에서도 반환되지 않습니다. 콜백은 저장된 워크플로에 포함되지 않으므로, 필요한 실행마다 설정하세요.
수신하는 내용
본문은 전송 시점의 실행 객체이며, 다음 헤더와 함께 JSON으로 전송됩니다.
| 헤더 | 값 |
|---|---|
X-Ephemeral-Run | 실행 ID. |
X-Ephemeral-Timestamp | 이번 시도에 서명한 시각의 Unix 시간(초). |
X-Ephemeral-Signature | sha256= 뒤에, 타임스탬프, 마침표, 원본 본문을 시크릿을 키로 해서 계산한 HMAC-SHA256의 16진수 값이 붙습니다. |
Content-Type | application/ |
<X-Ephemeral-Timestamp>.<raw request body>서명 검증
본문을 파싱하기 전에 원본 바이트로 HMAC을 계산하고, 상수 시간 비교로 대조하세요.
import crypto from "node:crypto";
import express from "express";
const app = express();
app.post("/hooks/ephemeral", express.raw({ type: "application/json" }), (req, res) => {
const timestamp = req.get("X-Ephemeral-Timestamp") ?? "";
const signature = req.get("X-Ephemeral-Signature") ?? "";
const expected =
"sha256=" +
crypto
.createHmac("sha256", process.env.EPHEMERAL_CALLBACK_SECRET)
.update(timestamp + ".")
.update(req.body)
.digest("hex");
const valid =
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
if (!valid || !fresh) return res.sendStatus(401);
const run = JSON.parse(req.body);
// Record run.id first and skip it if you have already handled it.
res.sendStatus(204);
});시도마다 현재 시각으로 서명하므로, 오래된 타임스탬프를 거부하면 정상적인 재시도는 막지 않으면서 재전송 공격을 제한할 수 있습니다.
전달과 재시도
2xx 응답은 모두 전달된 것으로 간주합니다. 타임아웃이나 리디렉션을 포함한 그 밖의 응답은 재시도합니다. 처음에는 10초 후에 시도하고, 이후 간격이 매번 두 배로 늘어 시도 사이의 간격이 최대 약 43분이 됩니다. 대기 중인 전달은 영구 저장되므로 엔진이 재시작되어도 유실되지 않습니다.
실행 ID로 중복을 제거하세요
엔드포인트가 같은 실행을 두 번 이상 받을 수 있습니다. 예를 들어 전달을 처리했지만 응답이 유실된 경우입니다.
X-Ephemeral-Run을 사용해 처리를 멱등하게 만드세요.콜백이 정산이 끝나기 전에 도착할 수 있습니다. 본문의
billing_pending이 true이면, 나중에 GET /v1/runs/{id}로 실행을 다시 읽어 최종 비용을 확인하세요.