Ein Callback teilt Ihrem Server mit, wenn eine Ausführung beendet ist, sodass Sie weder abfragen noch einen Ereignis-Stream offen halten müssen. Ephemeral sendet eine signierte POST-Anfrage mit dem Ausführungsobjekt, nachdem die Ausführung einen finalen Status erreicht hat.
Einen Callback anfordern
Fügen Sie callback zur Ausführungsanfrage hinzu.
callback.urlstringerforderlichcallback.secretstringerforderlich{
"workflow": "investigate-payment",
"input": { "payment_id": "pay_123" },
"max_cost_micros": 500000,
"callback": {
"url": "https://example.com/hooks/ephemeral",
"secret": "3f9a6c1e8b2d47f0a5c3e9d1b7f2a6c4"
}
}Das Secret wird von keinem Lesezugriff zurückgegeben, auch nicht von der Ausführungsdefinition. Callbacks sind nicht Teil gespeicherter Workflows; legen Sie einen bei jeder Ausführung fest, die ihn braucht.
Was Sie erhalten
Der Body ist das Ausführungsobjekt im Zustand zum Zeitpunkt der Zustellung, als JSON gesendet mit diesen Headern:
| Header | Wert |
|---|---|
X-Ephemeral-Run | Die Ausführungs-ID. |
X-Ephemeral-Timestamp | Unixzeit in Sekunden, zu der dieser Versuch signiert wurde. |
X-Ephemeral-Signature | sha256= gefolgt vom hexadezimalen HMAC-SHA256 aus Zeitstempel, einem Punkt und dem rohen Body, mit Ihrem Secret als Schlüssel. |
Content-Type | application/ |
<X-Ephemeral-Timestamp>.<raw request body>Die Signatur prüfen
Berechnen Sie den HMAC über die Rohbytes des Bodys, bevor Sie ihn parsen, und vergleichen Sie in konstanter Zeit.
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);
// Zuerst run.id festhalten und überspringen, wenn er schon verarbeitet wurde.
res.sendStatus(204);
});Jeder Versuch wird mit der aktuellen Zeit signiert. Das Ablehnen alter Zeitstempel begrenzt daher Replays, ohne echte Wiederholungen abzulehnen.
Zustellung und Wiederholungen
Jede 2xx-Antwort gilt als zugestellt. Alles andere, auch ein Timeout oder eine Weiterleitung, wird wiederholt: zuerst nach 10 Sekunden, dann mit jeweils verdoppelter Wartezeit bis zu etwa 43 Minuten zwischen den Versuchen. Ausstehende Zustellungen werden dauerhaft gespeichert, ein Neustart der Engine verliert sie also nicht.
X-Ephemeral-Run, um die Verarbeitung idempotent zu machen.billing_pending im Body true, lesen Sie die Ausführung später erneut mit GET /v1/runs/{id}, um ihre endgültigen Kosten zu erhalten.