Appearance
Signing Outgoing Requests
When a job has signing_secret set, Krafter signs every outgoing request with HMAC-SHA256. Three headers are added to the request:
| Header | Value |
|---|---|
x-krafter-signature | sha256=<hex> — lowercase hex HMAC-SHA256 of the request body |
x-krafter-job-id | The job's UUID |
x-krafter-timestamp | Unix epoch seconds at the moment of dispatch |
The signature covers the request body (body field, or empty string if unset). Custom headers and the timestamp are not part of the signed payload.
Verification (Node.js)
js
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyKrafterCronRequest(req, secret) {
const header = req.headers["x-krafter-signature"] ?? "";
const expected =
"sha256=" +
createHmac("sha256", secret).update(req.rawBody).digest("hex");
const a = Buffer.from(header);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}req.rawBody must be the exact bytes Krafter sent — re-serializing parsed JSON will not match. Capture the raw body before any body-parser middleware.
Verification (Elixir)
elixir
def verify(body, header, secret) do
expected =
"sha256=" <>
(:crypto.mac(:hmac, :sha256, secret, body) |> Base.encode16(case: :lower))
Plug.Crypto.secure_compare(header, expected)
endStorage
The signing_secret value is encrypted at rest using Krafter.Encrypted.Binary. After a job is created, subsequent reads of the job (GET /cron/jobs/:id, list endpoints) do not echo the secret — it is omitted from the response. To rotate, send a new value via Update Job.
Replay protection
WARNING
The current signature covers the request body only. The x-krafter-timestamp header is provided for future use but is not yet part of the signed payload. Treat duplicate deliveries as possible until a future signing version covers the timestamp.
If your endpoint is sensitive to replay, deduplicate on x-krafter-job-id plus your own request-id header (set via the job's headers field) or an idempotency key carried in the body.