Webhooks.
Subscribe to instance lifecycle events. We deliver signed JSON over HTTPS, retry with exponential backoff, and give you tools to inspect every delivery attempt.
§ 06.1Register an endpoint¶
Create a webhook endpoint via POST /v1/webhook-endpoints (scope webhooks: write).
curl -X POST https://api.demo.gpu.ai/v1/webhook-endpoints \
-H "Authorization: Bearer gpuai_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"url": "https://hooks.acme.com/gpuai",
"event_types": [
"instance.creating",
"instance.running",
"instance.terminated",
"instance.failed"
]
}'{
"id": "5f1b8a9c-2d3e-4a7b-9c1d-8e0f2a3b4c5d",
"url": "https://hooks.acme.com/gpuai",
"secret": "f9c1a3...64hex...", // ← store this; never shown again
"event_types": ["instance.creating", "instance.running",
"instance.terminated", "instance.failed"],
"enabled": true,
"created_at": "2026-05-08T17:00:00Z"
}Manage endpoints with GET /v1/webhook-endpoints (list) and DELETE /v1/webhook-endpoints/{id}. See the reference for the full CRUD surface.
§ 06.2Event types¶
v1 emits four instance lifecycle events. Subscribe to whichever subset you need; pass them in event_types at creation.
| Type | Fires when |
|---|---|
instance.creating | Operation accepted; placement in flight. |
instance.running | Instance booted and SSH connection is live. |
instance.terminated | Customer terminate, supplier-side stop, or end-of-spot preemption. |
instance.failed | Provisioning failed or the instance entered an unrecoverable error state. |
§ 06.3Delivery format¶
We POST a single JSON event to your URL. Each delivery carries two headers you care about:
Gpuai-Signature: HMAC-SHA256 over<timestamp>.<body>. See verification below.Gpuai-Event-Id: a stable ID per event. Use it to deduplicate when retries arrive after you've already processed the original.
POST https://hooks.acme.com/gpuai
Gpuai-Signature: t=1746732102,v1=5f1b8a9c0d1e2f3a4b5c6d7e8f9a0b1c...
Gpuai-Event-Id: dc062a1e-cd94-4196-8c28-2e244d927f3f
Content-Type: application/json
{
"id": "dc062a1e-cd94-4196-8c28-2e244d927f3f",
"type": "instance.running",
"created_at": "2026-05-08T17:01:30Z",
"data": {
"instance": {
"id": "gpu-a3c4d3a1",
"status": "running",
"gpu_type": "h100_sxm",
"region": "us-east",
...
}
}
}Respond with any 2xx status (we recommend 204 No Content) within 10 seconds. Anything else, or a timeout, triggers a retry (see below).
§ 06.4Verify signatures¶
Always verify the Gpuai-Signature header before trusting the payload. The format is two comma-separated parts:
Gpuai-Signature: t=<unix_seconds>,v1=<hex(hmac_sha256(secret, "<t>.<body>"))>The HMAC input is the literal string <timestamp>.<raw body bytes>. Make sure your framework gives you the unparsed body, not the JSON-decoded object.
import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.GPUAI_WEBHOOK_SECRET; // stored at creation
// Capture the raw body so we can HMAC it byte-for-byte.
app.use(express.raw({ type: "application/json" }));
app.post("/gpuai", (req, res) => {
const header = req.header("Gpuai-Signature") ?? "";
const parts = Object.fromEntries(
header.split(",").map((kv) => kv.split("=")),
);
const t = Number(parts.t);
const v1 = parts.v1;
// 1. Replay protection: timestamp must be within ±5 minutes.
if (Math.abs(Date.now() / 1000 - t) > 300) {
return res.status(400).send("stale signature");
}
// 2. Recompute and compare in constant time.
const expected = crypto
.createHmac("sha256", SECRET)
.update(`${t}.${req.body}`)
.digest("hex");
if (
!crypto.timingSafeEqual(
Buffer.from(v1, "hex"),
Buffer.from(expected, "hex"),
)
) {
return res.status(400).send("bad signature");
}
// 3. Process the event.
const event = JSON.parse(req.body.toString());
console.log(event.type, event.data);
res.status(204).end();
});§ 06.5Retry policy¶
We retry any non-2xx response or timeout with exponential backoff: 5 attempts spaced 30s, 5m, 30m, 2h, 12h — so a delivery that never succeeds is given up on about 2.5 hours after the first try.
After the final attempt fails, the delivery is marked dead and stops retrying.
Because a dead delivery is not retried, treat repeated failures as something to fix on your side: return a 2xx as soon as you have durably accepted the event, and do the slow work afterwards rather than inside the 30-second per-attempt timeout.