Um callback avisa o seu servidor quando uma execução termina, para que você não precise consultar o status nem manter um stream de eventos aberto. O Ephemeral envia uma requisição POST assinada com o objeto de execução depois que a execução chega a um status final.
Solicitar um callback
Adicione callback à requisição da execução.
callback.urlstringobrigatóriocallback.secretstringobrigatório{
"workflow": "investigate-payment",
"input": { "payment_id": "pay_123" },
"max_cost_micros": 500000,
"callback": {
"url": "https://example.com/hooks/ephemeral",
"secret": "3f9a6c1e8b2d47f0a5c3e9d1b7f2a6c4"
}
}O segredo nunca é retornado por nenhuma leitura, incluindo a definição da execução. Os callbacks não fazem parte dos fluxos de trabalho salvos; defina um em cada execução que precisar.
O que você recebe
O corpo é o objeto de execução como está no momento da entrega, enviado como JSON com estes cabeçalhos:
| Cabeçalho | Valor |
|---|---|
X-Ephemeral-Run | O ID da execução. |
X-Ephemeral-Timestamp | Hora Unix, em segundos, em que esta tentativa foi assinada. |
X-Ephemeral-Signature | sha256= seguido do HMAC-SHA256 em hexadecimal do carimbo de data e hora, um ponto e o corpo bruto, com o seu segredo como chave. |
Content-Type | application/ |
<X-Ephemeral-Timestamp>.<raw request body>Verificar a assinatura
Calcule o HMAC sobre os bytes brutos do corpo antes de interpretá-lo e compare em tempo constante.
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);
// Registre run.id primeiro e ignore-o se já o tiver processado.
res.sendStatus(204);
});Cada tentativa é assinada com a hora atual, então rejeitar carimbos de data e hora antigos limita a repetição de requisições sem rejeitar novas tentativas legítimas.
Entrega e novas tentativas
Qualquer resposta 2xx conta como entregue. Qualquer outra, incluindo um timeout ou um redirecionamento, é tentada de novo: primeiro após 10 segundos e depois com o intervalo dobrando a cada vez, até cerca de 43 minutos entre as tentativas. As entregas pendentes são armazenadas de forma durável, então uma reinicialização do mecanismo não as perde.
X-Ephemeral-Run para tornar o tratamento idempotente.billing_pending for true no corpo, leia a execução de novo mais tarde com GET /v1/runs/{id} para obter o custo final.