Webhooks
Receive signed events when runs progress. Verify them with public-key JWTs.
Instead of polling a run, point CloudRaker at an endpoint and receive events. Every delivery is a JWT signed with a private key. You verify it against a public JWKS. There is no shared secret to store or rotate.
Two ways to subscribe
Per run — every capability call takes a webhook union:
{url} is ad-hoc. The URL is used for this run only. {id} references a saved endpoint. The run stores the reference, not a URL snapshot. Re-pointing or pausing that endpoint applies to runs that are already in flight.
The legacy /process API’s callbackUrl behaves like the {url} form and is unchanged.
Saved endpoints
Omit events to receive every type. The URL must be https.
A run that references a disabled endpoint fails at create with 422 webhook_endpoint_disabled. It does not run silently undelivered. Re-enable the endpoint, or send webhook: {url} for that call.
Event types
A single-capability run emits both run.completed (the step) and processing.completed (the run). A pipeline emits one run.* per step and one processing.completed at the end.
space.expired
Space expiry is lazy. The first call that addresses a space past its expiresAt closes it, deletes its files, and answers 404. That moment is when this event fires — once per space, not at the deadline itself, and never on an explicit DELETE. Advance warnings before the deadline come later, with the sweeper.
processingId carries the space id, so every published verifier keeps working:
Envelope
Each request body is JSON:
processingId is the run id you got at create. Those five fields are the whole envelope. Request metadata is not carried on deliveries. It lives on the run body. Route on processingId and read GET /v1/runs/:id when you need your own keys back.
Agent runs
Agent runs deliver on the same wire contract: same envelope, same x-rk1-signature JWT, same JWKS. processingId carries the agr_ id, so every verifier below keeps working unchanged. Only data differs. Unlike a capability run, an agent run’s metadata is carried on data.metadata when you set one.
Every agent_run.* payload starts with the run itself:
What each type adds to data:
approval_requested is deliberately minimal. The proposed params are auth-gated on the run itself. Re-read GET /v1/agent-runs/:id and take approvals[] from there before you decide.
task_ready is the one event that is a to-do list. Look up the step in the run’s tasks[], do the work, then POST /v1/agent-runs/:id/tasks/:taskId/complete.
Delivery rides the request that observed the change, with a retry ladder compressed into it (about 0.5, 1, 2, and 4 seconds). An endpoint that is down longer than that gets the event the next time the run is observed, not on a slow background ladder. An integration that also long-polls GET /v1/agent-runs/:id therefore never misses a transition. Re-read the run whenever you need the authoritative state.
Delivery and retries
- At-least-once. The same event can arrive more than once. Dedupe on
eventIdbefore you act. - Up to 5 attempts per event, with exponential backoff (about 10 s, 20 s, 40 s, 80 s, capped at 5 minutes).
- Any non-2xx response counts as a failure and is retried. After the last attempt the event is dropped.
- Ordering is not guaranteed. Treat each event as a fact about a run. Re-read
GET /v1/runs/:idwhen you need the authoritative state.
Debug a delivery
GET /v1/webhooks/:id/deliveries shows whether an event arrived. It returns one row per attempt, newest first:
responseStatus is what your endpoint answered. Two rows with the same eventId and rising attempt show a retry. The row above shows an endpoint that rejects the POST. This is the most common integration mistake.
Signature
Each delivery carries the header:
It is a JWT signed per attempt with a private P-256 key. Its claims:
The JWT header carries a kid (for example rk1-wh-prod-2026-07) and alg: ES256. Retries get a fresh iat/exp, so the replay window is always 5 minutes from the latest attempt.
Verify a delivery
Fetch the public JWKS
GET /v1/webhooks/jwks.json — unauthenticated, cacheable for an hour. It returns the public P-256 keys:
GET /process/jwks.json serves the same keys and keeps working. /v1/webhooks/jwks.json is the stable address.
Verify the JWT
Verify the x-rk1-signature JWT against those keys with any standard JWT library. Match on kid, algorithm ES256.
Sample verifier
Verify against the raw request bytes. If your web framework parses and re-serializes the JSON body, the digest will not match. Capture the raw body before it is parsed.