Platform

Webhooks & Events

Subscribe to real-time platform events and receive POST payloads to any URL. Every action in FlowOS emits an event.

Overview

FlowOS has two event mechanisms: outbound webhooks push event payloads to external URLs when platform events occur, and the event bus is an internal pub/sub channel for workflows and artifacts to communicate. Both share the same event schema.

Event Catalog

Every platform action emits a typed event. Events are namespaced by resource type:

Incidents

  • incident.created
  • incident.updated
  • incident.escalated — Severity raised or team reassigned
  • incident.assigned — Assigned to a specific user
  • incident.resolved
  • incident.closed
  • incident.reopened
  • incident.sla_breached — SLA deadline passed
  • incident.comment_added

Changes

  • change.created
  • change.submitted — Submitted for CAB review
  • change.approved
  • change.rejected
  • change.implementation_started
  • change.completed
  • change.rolled_back

Workflows

  • workflow.run_started
  • workflow.run_completed
  • workflow.run_failed
  • workflow.run_paused — Waiting for approval
  • workflow.activated
  • workflow.deactivated

DB Records

  • record.created — For any table (includes tableSlug in payload)
  • record.updated
  • record.deleted

Platform

  • user.invited, user.activated, user.deactivated
  • connector.status_changed — Connection went up or down
  • sla.breached — Any SLA breach across any ITSM entity
  • deploy.completed, deploy.failed

Event Payload Structure

All events use the same envelope regardless of type:

json
{
  "id": "evt_01HZ9MNPQRSTUV",
  "type": "incident.escalated",
  "workspaceId": "ws_01HZ...",
  "environment": "production",
  "createdAt": "2026-06-01T10:00:00.342Z",
  "actor": {
    "id": "usr_01HZ...",
    "name": "Alice Smith",
    "type": "user"          // "user" | "api_token" | "workflow" | "system"
  },
  "resource": {
    "type": "incident",
    "id": "inc_01HZ...",
    "number": "INC-1042",
    "displayName": "API gateway latency spike"
  },
  "data": {
    // Resource-type-specific fields
    "before": { "severity": "P2", "assignedTeam": "networking" },
    "after":  { "severity": "P1", "assignedTeam": "infrastructure" },
    "changes": ["severity", "assignedTeam"]
  }
}

Creating a Webhook

bash
POST /api/v1/webhooks
{
  "name": "PagerDuty bridge",
  "url": "https://events.pagerduty.com/v2/enqueue",
  "events": ["incident.created", "incident.escalated"],
  "secret": "wh_sec_mysecret",
  "headers": {
    "Authorization": "Token token={{secrets.PD_API_KEY}}"
  },
  "filter": {
    "severity": { "in": ["P1", "P2"] }
  },
  "retries": 5,
  "retryStrategy": "exponential",
  "retryMaxDelayMs": 300000,
  "timeoutMs": 10000
}

Verifying Signatures

FlowOS signs each webhook delivery with HMAC-SHA256. The signature is in theX-FlowOS-Signature-256 header as sha256=hexdigest.

typescript
import crypto from 'node:crypto'

function isValidSignature(
  rawBody: Buffer,
  signatureHeader: string,
  secret: string
): boolean {
  const digest = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex')

  if (signatureHeader.length !== digest.length) return false
  return crypto.timingSafeEqual(
    Buffer.from(signatureHeader),
    Buffer.from(digest)
  )
}

// Fastify example
fastify.post('/webhook/flowos', {
  config: { rawBody: true }    // ensure raw body is preserved
}, async (request, reply) => {
  const sig = request.headers['x-flowos-signature-256'] as string
  if (!isValidSignature(request.rawBody, sig, process.env.FLOWOS_WEBHOOK_SECRET!)) {
    return reply.code(401).send({ error: 'Invalid signature' })
  }
  const event = request.body as FlowOSEvent
  await handleEvent(event)
  return reply.code(200).send({ ok: true })
})
Always validate signatures before processing events. Reject requests with missing or invalid signatures with 401 Unauthorized. Never bypass signature validation in production.

Delivery & Retries

FlowOS considers a delivery successful when your endpoint returns any 2xx status within the timeout window. On failure (non-2xx, timeout, or network error), FlowOS retries with exponential backoff:

FieldTypeRequiredDefaultDescription
Attempt 1DelayoptionalImmediate
Attempt 2Delayoptional~30 seconds
Attempt 3Delayoptional~2 minutes
Attempt 4Delayoptional~10 minutes
Attempt 5Delayoptional~30 minutes

After all retries are exhausted, the delivery is marked failed. You can manually replay failed deliveries from the webhook detail page or via API:

POST
/api/v1/webhooks/:id/deliveries/:deliveryId/replay

Replay a specific delivery

Delivery Log

bash
GET /api/v1/webhooks/wh_01HZ.../deliveries?status=failed&limit=20

// Response includes:
{
  "data": [{
    "id": "del_01HZ...",
    "eventId": "evt_01HZ...",
    "eventType": "incident.created",
    "url": "https://hooks.example.com/flowos",
    "status": "failed",
    "attempts": 5,
    "lastAttemptAt": "2026-06-01T11:30:00Z",
    "lastResponseStatus": 503,
    "lastResponseBody": "Service Unavailable",
    "lastDurationMs": 10012
  }]
}

Event Bus (Internal)

The event bus enables internal pub/sub between workflows, artifacts, and connectors. Unlike webhooks (which push to external URLs), the event bus is consumed within the FlowOS platform.

Publish an event

bash
POST /api/v1/events/topics/infra.alerts/publish
{
  "type": "disk.threshold_exceeded",
  "payload": {
    "host": "prod-web-01",
    "usagePercent": 95
  },
  "deduplicationKey": "disk-prod-web-01"   // optional: suppress duplicates within 5 min
}

Consume events in a workflow trigger

json
{
  "trigger": {
    "type": "event_bus",
    "topic": "infra.alerts",
    "eventType": "disk.threshold_exceeded",
    "filter": {
      "payload.usagePercent": { "gte": 90 }
    }
  }
}

Consume events from the API (polling)

bash
GET /api/v1/events/topics/infra.alerts/events
  ?after=evt_01HZ...      // cursor — get events after this ID
  &limit=50
  &timeout=20000          // long-poll up to 20s if no new events