Webhooks
Receive signed events when runs progress, and verify them with public-key JWTs.
Instead of polling a run, point CloudRaker at an endpoint and get told what happened. Every delivery is a JWT signed with a private key that you verify 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, and 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 rather than running 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.
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, so route on processingId and read GET /v1/runs/:id when you need your own keys back.
Delivery and retries
- At-least-once. The same event can arrive more than once — dedupe on
eventIdbefore acting. - 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, and re-read
GET /v1/runs/:idwhen you need the authoritative state.
Debug a delivery
GET /v1/webhooks/:id/deliveries is the “did it arrive?” surface — one row per attempt, newest first:
responseStatus is what your endpoint answered. Two rows with the same eventId and rising attempt is a retry — the row above shows an endpoint rejecting the POST, the most common integration mistake.
Signature
Each delivery carries the header:
It’s 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 won’t match — capture the raw body before it’s parsed.