Skip to content
OpenRelay is in early access, and the /v1 API is stable. New capabilities ship in the changelog.
API ReferenceComputeWebhooks

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

EventsReference
vm.running, vm.stopped, vm.terminated, vm.interruptedVM and pod events
batch.completed, batch.failed, batch.expired, batch.cancelledBatch events
balance.low, balance.depletedBalance 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.

Node.js / TypeScript
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);
}
Python
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 2xx quickly. 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}/deliveries to 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}/toggle instead of deleting it.

On this page