Events and webhooks

Find out when a deployment becomes ready, fails, is suspended or ends, and when your balance runs low, without polling each deployment. There are two ways to get the same events:

  • Poll one feed: GET /platform/events?after=<cursor>. It works from anywhere, including an agent on a laptop that can't receive requests.
  • Webhooks: we POST each event to your HTTPS endpoint as it happens.

Event types

typeWhen
deployment.readyA rental or private model is running. After a resume too: previous_state is then suspended.
deployment.failedIt could not start, or stopped with an error; error says why. Nothing is billed for a start that failed.
deployment.suspendedIt was paused: suspended_reason is balance (credit ran out) or idle (a private model nobody called).
deployment.terminatedIt was stopped for good: deleted, or max_runtime_minutes reached.
balance.lowYour balance lasts under 24 hours at the current rate. Sent again under 2 hours.
capacity.availableGPUs you asked to hear about are free now (see capacity alerts).

We also email you when a rental that was running stops by itself (a deployment.failed with previous_state: "ready"), once per batch, so you don't need anything set up to hear about it.

An event:

{
  "id": 4182,
  "type": "deployment.suspended",
  "created_at": "2026-10-30T09:12:44.103000+00:00",
  "data": {
    "deployment_id": "d869d719-9d86-4131-9d25-41bdc9d8d5a8",
    "name": "rig-03",
    "workload_id": "6f0c…",
    "state": "suspended",
    "previous_state": "ready",
    "error": "Insufficient balance — top up your credits to resume",
    "suspended_reason": "balance"
  }
}

For capacity.available, data has alert_id, gpu_model, gpu_count, count, available_gpus and max_gpus_per_deployment (the most free on one machine).

For balance.low, data has balance_cents, balance_usd, usd_per_hour, hours_left, runs_out_at, under_hours (24 or 2) and running (what is being billed). Top up, or let auto-resume bring suspended rentals back.

Poll the feed

AFTER=0
while true; do
  PAGE=$(curl -s "$GC_API/platform/events?after=$AFTER" -H "Authorization: Bearer $GC_KEY")
  echo "$PAGE" | jq -c '.events[]'
  AFTER=$(echo "$PAGE" | jq '.next_after')
  [ "$(echo "$PAGE" | jq '.has_more')" = true ] || sleep 15
done
  • Keep next_after and pass it as after next time. after=0 returns everything still kept; events are kept 30 days.
  • types=deployment.ready,deployment.failed returns only those types. limit is up to 500 (default 100). has_more: true means call again right away.
  • An event appears about 5 seconds after the change, so that the order you read is final.
  • A key with just the read scope can poll the feed.

Webhooks

Create one with a full-access API key:

curl -s -X POST "$GC_API/platform/webhooks" \
  -H "Authorization: Bearer $GC_KEY" -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/green-compute", "events": ["deployment.ready", "deployment.failed", "deployment.suspended", "deployment.terminated"]}'

The reply has the webhook's webhook_id and its secret. The secret is shown only this once, so store it. Leave out events to get every type. POST /platform/webhooks/{id}/test sends a webhook.test event straight away and tells you whether your endpoint answered.

How delivery works:

  • The URL must be https:// and resolve to a public address. Redirects are not followed.
  • Each event is the JSON body of one POST, with the headers Green-Compute-Event (the type), Green-Compute-Event-Id and Green-Compute-Signature.
  • Answer with any 2xx status within 10 seconds. Anything else counts as a failure. The event is retried after 10 s, 20 s, 40 s and so on, up to an hour apart, and later events wait, so they always arrive in order.
  • Delivery is at least once: the same event can arrive twice. Use id to skip repeats.
  • A webhook that keeps failing for 3 days is turned off (active: false, with the reason in disabled_reason). Fix the endpoint, then PATCH /platform/webhooks/{id} with {"active": true}, and it delivers what it missed.
  • Up to 5 webhooks per account. A new webhook gets events from the moment it is created; the feed has the earlier ones.

Check the signature

Green-Compute-Signature looks like t=1793345564,v1=5f3a…: v1 is the hex HMAC-SHA256 of <t>.<raw body>, keyed with your webhook's secret. Check it against the raw body, before parsing the JSON:

import hashlib, hmac, time

def verify(secret: str, raw_body: bytes, header: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    signed = parts["t"].encode() + b"." + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    fresh = abs(time.time() - int(parts["t"])) <= tolerance
    return fresh and hmac.compare_digest(expected, parts["v1"])

Calls

CallDoes
GET /platform/eventsEvents after after, oldest first. Optional types and limit.
POST /platform/webhooksCreates a webhook: url, optional events. Returns its secret once.
GET /platform/webhooksYour webhooks, with active, failures, last_error and last_success_at.
PATCH /platform/webhooks/{id}Changes url or events, or turns it off and on with active.
DELETE /platform/webhooks/{id}Deletes it.
POST /platform/webhooks/{id}/testSends a test event now: {"delivered": true, "status_code": 200}.