OrkStageOrkStage

Webhooks

Receive signed OrkStage events over HTTPS. Event types, payload, signature checks in Node.js, Python and Go, retries, testing and redelivery.

OrkStage sends events to your HTTPS endpoints as JSON POST requests signed with HMAC-SHA256. Use them to post to chat, open tickets, start pipelines or feed your incident tools.

Events

EventWhen
deployment.startedThe worker picked up a deployment.
deployment.succeededA deployment (apply or destroy) finished.
deployment.failedA deployment ended in failure.
deployment.awaiting_approvalA high risk production deployment waits for approval.
instance.degradedAn instance's health dropped.
instance.drift_detectedThe drift scan found changes made outside OrkStage.
incident.opened, incident.updated, incident.resolvedAn incident was opened, updated or resolved.
cloud_target.unhealthyA cloud target failed validation, either when someone validates it or when a deployment checks it before it starts. Sent once, when the target becomes unhealthy.
service.unhealthyA critical resource of an instance turned unhealthy.
maintenance.scheduled, maintenance.started, maintenance.completedPlanned maintenance on a status page.
domain.verified, domain.verification_failedA custom domain's TXT record was found, or did not appear within 7 days.
domain.certificate_issued, domain.certificate_expiringA custom domain's certificate was issued, or expires in 30, 14, 7 or 1 days.
quota.exceededSomeone tried to create an instance beyond the tenant's instance limit.
pingSent by the Test button in the console and by POST /webhooks/:id/test. You cannot subscribe to it.

Creating an endpoint

In the console, open Integrations, then Webhooks, then New webhook. Through the API, use a session or a key with webhooks:manage:

POST /api/v1/webhooks
Content-Type: application/json

{
  "url": "https://hooks.example.com/orkstage",
  "description": "deployments to #platform",
  "events": ["deployment.failed", "deployment.awaiting_approval", "incident.opened"],
  "tenantId": "8a1c…"
}

format is ORKSTAGE by default. SLACK and TEAMS send a chat message to an incoming webhook of that service instead of the signed JSON event, see notifications.

The response contains { webhook, secret }. You see the signing secret (whsec_…) once. The server stores it encrypted with ENCRYPTION_KEY. To rotate it, call POST /webhooks/:id/rotate-secret, which returns the new secret.

OrkStage checks the URL when you create the endpoint and again before every delivery, because DNS can change:

  • The URL must use https:// and must not contain credentials.
  • The host must not resolve to a private, loopback, link local or multicast address.
  • Redirects are not followed, and each request times out after 10 seconds.

For local development, WEBHOOK_ALLOW_PRIVATE=true also accepts http:// URLs and private addresses. The API refuses to start with this setting in production.

The request

POST /orkstage HTTP/1.1
Content-Type: application/json
User-Agent: OrkStage-Webhooks/1.0
OrkStage-Event: deployment.failed
OrkStage-Delivery: 0f8b6c1e-…
OrkStage-Signature: t=1791234567,v1=5f2b…c9

{
  "id": "0f8b6c1e-…",
  "type": "deployment.failed",
  "createdAt": "2026-10-05T14:21:07.000Z",
  "tenantId": "8a1c…",
  "data": {
    "title": "Deployment failed",
    "body": "The deployment did not complete (AUTHORIZATION_ERROR). Open the deployment for details and safe retry options.",
    "link": "/deployments/9b07…",
    "deploymentId": "9b07…",
    "instanceId": "41de…",
    "kind": "APPLY"
  }
}

OrkStage-Delivery is unique per delivery and stays the same across retries. A manual redelivery is a new delivery with a new OrkStage-Delivery value, but its body keeps the original id. To drop duplicates, use the body id, which covers both retries and redeliveries.

Verifying the signature

OrkStage-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>
  1. Read the raw request body, before any JSON parsing.
  2. Split the header on , and = to get t and v1.
  3. Reject the request if t is more than 300 seconds away from your clock. This protects against replays.
  4. Compute HMAC-SHA256(secret, t + "." + rawBody) as lowercase hex and compare it with v1 in constant time.
  5. Answer with a 2xx status quickly, and do the work in the background.
verify.ts
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyOrkStage(secret: string, header: string, rawBody: string, toleranceS = 300): boolean {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=', 2) as [string, string]));
  const t = Number(parts.t);
  if (!Number.isFinite(t) || Math.abs(Math.floor(Date.now() / 1000) - t) > toleranceS || !parts.v1) return false;
  const expected = Buffer.from(createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex'));
  const given = Buffer.from(parts.v1);
  return expected.length === given.length && timingSafeEqual(expected, given);
}

// Express: keep the raw body for this route.
// app.post('/orkstage', express.raw({ type: 'application/json' }), (req, res) => {
//   const ok = verifyOrkStage(process.env.ORKSTAGE_WEBHOOK_SECRET!, req.get('OrkStage-Signature') ?? '', req.body.toString('utf8'));
//   if (!ok) return res.sendStatus(400);
//   res.sendStatus(204);
//   handle(JSON.parse(req.body.toString('utf8')));
// });

The platform uses the same algorithm in verifyWebhook (packages/runtime/src/webhooks.ts).

Retries and failures

SuccessAny 2xx response within 10 seconds. OrkStage ignores the response body.
Retry scheduleAfter a network error or a status outside 2xx, OrkStage retries after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. Then the delivery is FAILED.
Automatic disablingAfter 20 failed deliveries in a row, OrkStage disables the endpoint and records a reason. Turn it back on with PATCH /webhooks/:id { "enabled": true }, which also resets the failure count.

Testing and redelivery

EndpointWhat it does
POST /webhooks/:id/testSends a ping at once and returns the delivery result.
GET /webhooks/:id/deliveriesLists the last 50 deliveries with status, attempts, response status and error.
POST /webhooks/:id/deliveries/:deliveryId/redeliverCreates a new delivery with the same payload and answers 202. The worker sends it on its next pass.
PATCH /webhooks/:idChanges url, description, events or enabled.
DELETE /webhooks/:idRemoves the endpoint.

In the console, open Integrations, then Webhooks. The Deliveries button of an endpoint lists its recent deliveries, each with a Redeliver button.

On this page