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
POSTeach event to your HTTPS endpoint as it happens.
Event types
type | When |
|---|---|
deployment.ready | A rental or private model is running. After a resume too: previous_state is then suspended. |
deployment.failed | It could not start, or stopped with an error; error says why. Nothing is billed for a start that failed. |
deployment.suspended | It was paused: suspended_reason is balance (credit ran out) or idle (a private model nobody called). |
deployment.terminated | It was stopped for good: deleted, or max_runtime_minutes reached. |
balance.low | Your balance lasts under 24 hours at the current rate. Sent again under 2 hours. |
capacity.available | GPUs 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_afterand pass it asafternext time.after=0returns everything still kept; events are kept 30 days. types=deployment.ready,deployment.failedreturns only those types.limitis up to 500 (default 100).has_more: truemeans 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
readscope 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 headersGreen-Compute-Event(the type),Green-Compute-Event-IdandGreen-Compute-Signature. - Answer with any
2xxstatus 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
idto skip repeats. - A webhook that keeps failing for 3 days is turned off (
active: false, with the reason indisabled_reason). Fix the endpoint, thenPATCH /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
| Call | Does |
|---|---|
GET /platform/events | Events after after, oldest first. Optional types and limit. |
POST /platform/webhooks | Creates a webhook: url, optional events. Returns its secret once. |
GET /platform/webhooks | Your 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}/test | Sends a test event now: {"delivered": true, "status_code": 200}. |
Green Compute