Overview
Receive signed, retried HTTP callbacks for VM, batch and balance events in your organization, and verify each delivery's HMAC-SHA256 signature.
Webhooks
Webhooks let you react to events, such as a VM coming online, a batch finishing or your balance running low, without polling. OpenRelay POSTs a JSON payload to a URL you control, signs
each delivery, and retries on failure.
Create a webhook
Register an endpoint and the events you care about. The response includes the
signing secret (whsec_…), shown once:
curl -X POST https://api.openrelay.inc/v1/orgs/$ORG_ID/webhooks/create \
-H "Authorization: Bearer $OPENRELAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "deploys",
"url": "https://example.com/hooks/openrelay",
"events": ["vm.running", "vm.stopped"]
}'{
"id": "wh_…",
"url": "https://example.com/hooks/openrelay",
"events": ["vm.running", "vm.stopped"],
"enabled": true,
"secret": "whsec_…", // store this now: shown once
"secretPrefix": "whsec_abcd"
}Lost the secret?
You can't read a secret again, but you can roll it with
/webhooks/{id}/regenerate-secret.
An empty events array subscribes to the four VM events. Batch and balance
events reach only a webhook that lists them.
Event types
| Events | Reference |
|---|---|
vm.running, vm.stopped, vm.terminated, vm.interrupted | VM and pod events |
batch.completed, batch.failed, batch.expired, batch.cancelled | Batch events |
balance.low, balance.depleted | Balance events |
Each reference page says when its events are sent and describes every field
and header of a delivery: a JSON body of id, type, created_at and data,
signed in X-OpenRelay-Signature.
Listing a batch event needs the Batch: Read permission, and a balance event
Billing: Read, as well as Integrations: Write to manage webhooks (see
Permissions); a legacy key needs clusters:read for a
balance event. Changing a webhook that delivers them, its URL included, needs
the same.
Verify the signature
Always verify
Treat unsigned or badly-signed requests as forgeries. Verify the signature on the raw request body before parsing it.
The signature is computed as:
X-OpenRelay-Signature = "sha256=" + hex( HMAC_SHA256(key, rawBody) )where the HMAC key is the lowercase hex SHA-256 of your whsec_… secret, i.e. hex(sha256(secret)). Compute the key once and reuse it.
import { createHash, createHmac, timingSafeEqual } from 'node:crypto';
// Derive the signing key from your stored secret (do this once).
function signingKey(secret: string) {
return createHash('sha256').update(secret).digest('hex');
}
export function verify(rawBody: string, header: string, secret: string) {
const expected =
'sha256=' +
createHmac('sha256', signingKey(secret)).update(rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(header);
return a.length === b.length && timingSafeEqual(a, b);
}import hashlib, hmac
def signing_key(secret: str) -> bytes:
return hashlib.sha256(secret.encode()).hexdigest().encode()
def verify(raw_body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(signing_key(secret), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header)Use the raw bytes
Compute the HMAC over the exact bytes you received, before any JSON parsing or re-serialization: reformatting the body changes the signature.
Responding, retries & delivery
- Respond
2xxquickly. Acknowledge fast and do slow work asynchronously; receivers that take too long are treated as failures. - Retries. A failed delivery is tried up to 3 times, on the schedule
each event's reference page gives. Every retry carries the same
X-OpenRelay-Delivery, so deduplicate on it. - Inspect deliveries with
/webhooks/{id}/deliveriesto see recent attempts and response codes. A batch or balance event's deliveries are listed only for a caller with the read permission that event needs. - Pause a webhook with
/webhooks/{id}/toggleinstead of deleting it.
Delete a registry credential DELETE
Removes a registry credential from the org by id and returns 204 with no body. Deleting an id that does not exist also returns 204.
List an org's webhooks (no secrets) GET
Returns every webhook in the org with its URL, subscribed events, enabled flag and secret prefix. The full signing secret is shown only on create or regenerate.