Webhooks
Get a callback when a job finishes, instead of polling.
Instead of polling for a result, you can have roasOS POST to your server when a job completes.
Set it up
Pass a webhook_url when you confirm a generation:
{
"type": "image",
"prompt": "...",
"model": "nano-banana-pro",
"quote_id": "...",
"webhook_url": "https://your-app.com/roasos/webhook"
}
When the job finishes, we POST the result to that URL.
Verify every delivery
Each request includes three headers:
X-RoasOS-Signature—v1=<hex>, an HMAC-SHA256 signature.X-RoasOS-Timestamp— unix seconds when the signature was made.X-RoasOS-Delivery-Id— a stable id, the same across retries. Use it to skip duplicates.
To confirm a request really came from us:
- Check the timestamp is recent (e.g. within 5 minutes) to block replays.
- Compute
HMAC_SHA256(secret, timestamp + "." + rawBody)as lowercase hex, using the raw request body and your key’s webhook secret. - Compare it to the
v1=value with a constant-time comparison.
import crypto from "node:crypto";
function verify(rawBody, headers, secret) {
const timestamp = headers["x-roasos-timestamp"];
const signature = headers["x-roasos-signature"].replace("v1=", "");
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
);
}
Handle retries
Deliveries are at-least-once — you may get the same event more than once. Dedupe on X-RoasOS-Delivery-Id and make your handler idempotent.