openstead
Developer tools & integrations

Webhooks

Receive signed deployment, service, and backup events at your HTTPS endpoint.

Suggest a change

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

  1. Select Create Webhook.
  2. Enter a name and a public HTTPS endpoint on port 443.
  3. Select the subscribed events.
  4. Save and securely store the displayed signing secret.
  5. 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

TypeMeaning
deploy.succeededA deployment became live.
deploy.failedA deployment failed.
service.unhealthyA service health failure was reported.
backup.succeededA backup completed successfully.
backup.failedA backup failed.
integration.testAn 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:

HeaderValue
X-Runivo-DeliveryDelivery UUID; stable across retries.
X-Runivo-EventEvent type.
X-Runivo-TimestampUnix timestamp for this attempt.
X-Runivo-Signaturev1= 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 event

Use 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.

Need a hand? Contact Openstead support.

On this page