gpu.aiDocs
UPDATED 2026.05.11READ 9 MINEDIT ON GITHUB →
CH·05API

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"
    ]
  }'
201 CREATED
{
  "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.

TypeFires when
instance.creatingOperation accepted; placement in flight.
instance.runningInstance booted and SSH connection is live.
instance.terminatedCustomer terminate, supplier-side stop, or end-of-spot preemption.
instance.failedProvisioning 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.
DELIVERY
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:

HEADER
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.