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.
§ 05.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": "whk_5f1b8a9c-...",
"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.
§ 05.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. |
§ 05.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: evt_01HX...
Content-Type: application/json
{
"id": "evt_01HX...",
"type": "instance.running",
"created_at": "2026-05-08T17:01:30Z",
"data": {
"instance": {
"id": "ins_01HX...",
"status": "running",
"gpu_type": "h100_sxm",
"region": "US",
...
}
}
}Respond with any 2xx status (we recommend 204 No Content) within 10 seconds. Anything else, or a timeout, triggers a retry — see below.
§ 05.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();
});§ 05.5Retry policy¶
We retry any non-2xx response or timeout with exponential backoff: 5 attempts over roughly 24 hours.
After the final attempt fails, the delivery is marked dead_lettered and stops retrying. You can inspect delivery history (success, failure, response status, response body excerpt) via GET /v1/webhook-endpoints/{id}/deliveries.
Stuck on a 4xx response? Use the dashboard's webhook inspector to see the exact request/response and click retry after fixing your endpoint. Or pull from the deliveries endpoint above and resubmit programmatically.