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 (includestableSlugin 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:
{
"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
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.
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 })
})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:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| Attempt 1 | Delay | optional | Immediate | |
| Attempt 2 | Delay | optional | ~30 seconds | |
| Attempt 3 | Delay | optional | ~2 minutes | |
| Attempt 4 | Delay | optional | ~10 minutes | |
| Attempt 5 | Delay | optional | ~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:
/api/v1/webhooks/:id/deliveries/:deliveryId/replayReplay a specific delivery
Delivery Log
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
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
{
"trigger": {
"type": "event_bus",
"topic": "infra.alerts",
"eventType": "disk.threshold_exceeded",
"filter": {
"payload.usagePercent": { "gte": 90 }
}
}
}Consume events from the API (polling)
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