Webhooks
Receive signed deployment, service, and backup events at your HTTPS endpoint.
Openstead webhooks notify your systems when events occur in a workspace. Configure them as an owner or admin in the dashboard's Webhooks page.
Create a destination
- Select Create Webhook.
- Enter a name and a public HTTPS endpoint on port 443.
- Select the subscribed events.
- Save and securely store the displayed signing secret.
- Open delivery history and send a test.
The receiver must have a valid TLS certificate and resolve to public addresses. Redirects are not followed. Saving an enabled destination subscribes it to future events.
Event types
| Type | Meaning |
|---|---|
deploy.succeeded | A deployment became live. |
deploy.failed | A deployment failed. |
service.unhealthy | A service health failure was reported. |
backup.succeeded | A backup completed successfully. |
backup.failed | A backup failed. |
integration.test | An explicitly requested delivery test. |
Payload
{
"id": "00000000-0000-4000-8000-000000000001",
"type": "deploy.succeeded",
"version": 1,
"createdAt": "2026-09-28T12:00:00Z",
"workspaceId": "00000000-0000-4000-8000-000000000002",
"data": {
"serviceId": "00000000-0000-4000-8000-000000000003",
"serviceName": "example-api",
"title": "Deployment succeeded",
"description": "The service deployment is live."
}
}IDs and descriptions above are illustrative. Treat descriptive text as display content, not a fixed schema for extracting state. Use the event type and resource identifiers for processing.
Verify the signature
Deliveries include these headers:
| Header | Value |
|---|---|
X-Runivo-Delivery | Delivery UUID; stable across retries. |
X-Runivo-Event | Event type. |
X-Runivo-Timestamp | Unix timestamp for this attempt. |
X-Runivo-Signature | v1= followed by the HMAC-SHA256 hex digest. |
The signed input is the timestamp, a period, and the exact request-body bytes. Do not parse and reserialize JSON before verifying. The following standalone Python function verifies the body and returns its JSON object:
import hashlib
import hmac
import json
import time
def verify_openstead_webhook(body: bytes, headers, secret: str) -> dict:
stamp = headers.get("X-Runivo-Timestamp", "")
signature = headers.get("X-Runivo-Signature", "")
if not stamp.isascii() or not stamp.isdigit() or len(stamp) > 12:
raise ValueError("Invalid webhook timestamp")
if abs(time.time() - int(stamp)) > 300:
raise ValueError("Webhook timestamp is outside the allowed window")
if (len(signature) != 67 or not signature.startswith("v1=")
or any(char not in "0123456789abcdef" for char in signature[3:])):
raise ValueError("Invalid webhook signature format")
expected = "v1=" + hmac.new(
secret.encode("utf-8"),
stamp.encode("ascii") + b"." + body,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(expected, signature):
raise ValueError("Invalid webhook signature")
event = json.loads(body)
if not isinstance(event, dict):
raise ValueError("Expected a webhook object")
if event.get("id") != headers.get("X-Runivo-Delivery"):
raise ValueError("Delivery ID does not match the signed event")
return eventUse a case-insensitive header mapping supplied by your HTTP framework. Load secret from a secret store, validate the expected workspace and supported event type, then durably record or enqueue the event. Keep a uniqueness constraint on its delivery ID so repeated deliveries cannot apply the same business action twice.
Return a 2xx response after accepting the event durably. Process expensive work asynchronously. A configured integration token is sent separately as an Authorization Bearer header.
Retries and delivery history
Delivery is at least once. A 2xx response succeeds. Other status codes and transport failures retry with exponential backoff, up to eight attempts. The delivery ID and body stay the same across attempts; the timestamp and signature are refreshed.
Use delivery history to inspect status, HTTP response, attempt count, and safe error details. Retry failed deliveries after fixing the endpoint. Editing or disabling a connection cancels pending deliveries, although a request already in flight may still arrive.
Delivery history is retained for 30 days. Saving a new connection does not replay events from before its activation.