Webhooks: events pushed to your URL
Leave a URL once; the platform POSTs a signed JSON event there whenever something happens to the pod. No polling for running, no polling for expiry.
Set it
On create: add "webhookUrl": "https://ops.example.com/hooks" to POST /api/agents. The reply carries webhookSecret beside agent.
Later, or to rotate the secret:
curl -s -X PUT https://agentspodium.com/api/agents/$ID/webhook -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -d '{"url":"https://ops.example.com/hooks"}'
# {"agent":{…,"webhookUrl":"https://ops.example.com/hooks"},"webhook":{"url":"…","secret":"whsec_…","events":[…]}}
The secret is shown once, by the call that set it. GET /api/agents/:id shows only webhookUrl, webhookLastStatus, webhookLastAt, webhookLastEvent. DELETE /api/agents/:id/webhook removes the hook.
The URL must be an absolute https:// (or http://) address on a public host; loopback, private ranges and URLs with credentials are refused with 400.
Prove it works
curl -s -X POST https://agentspodium.com/api/agents/$ID/webhook/test -H "Authorization: Bearer $TOKEN"
# {"delivery":{"ok":true,"status":200,"attempts":1}}
This sends a test event with all retries and waits for the outcome, so a dead receiver takes about a minute to report ok: false.
What arrives
POST <your url>, Content-Type: application/json, headers:
| header | value |
|---|---|
X-AgentsPodium-Event | the event type |
X-AgentsPodium-Delivery | the event id; retries reuse it, so it is your idempotency key |
X-AgentsPodium-Signature | sha256= + hex HMAC-SHA256 of the raw request body, keyed with your secret |
User-Agent | AgentsPodium-Webhooks/1.0 |
Body:
{ "id": "evt_9f3c2a1b7d4e6f80", "type": "agent.running", "createdAt": "2026-09-09T10:00:00.000Z",
"agent": { "id": "agt_…", "name": "My Agent", "engine": "hermes", "tier": "small", "status": "running", "endpointUrl": "https://…" },
"data": { "from": "provisioning", "to": "running" } }
| type | when | data |
|---|---|---|
agent.running | the pod came up: after create, resume, rebuild, or a recovery | from, to |
agent.stopped | paused by you, by an unpaid trial, or by a lapsed payment | from, to |
agent.failed | a build or rebuild did not come up (rebuild again; data is restored from backup) | from, to |
agent.deleted | the pod and its data are gone | from, to |
payment.confirmed | a card, crypto or Stars payment activated a subscription | subscriptionId, provider, tier, period, paidTill |
deletion.warning | 3, 2 and 1 days before an unpaid pod is stopped, and once when it stops | daysLeft (null once stopped), keptDays |
test | you asked for it | note |
Verify the signature before acting; reply with any 2xx within 10 seconds. Anything else is retried twice more, 10 seconds and then a minute later; after that the outcome is written to the agent record and the event is dropped. Order between events is not guaranteed; use createdAt.
Verification in Node:
import { createHmac, timingSafeEqual } from "node:crypto";
const expected = "sha256=" + createHmac("sha256", process.env.WEBHOOK_SECRET).update(rawBody).digest("hex");
const ok = timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers["x-agentspodium-signature"] ?? ""));