Skip to content

Outbound Webhooks

Receive real-time notifications when data changes in Flowstate. Configure webhook endpoints to push events to your systems whenever employees, teams, projects, allocations, and other entities are created, updated, or deleted.

Overview

Flowstate webhooks deliver events from all entry points:

  • GraphQL mutations — direct changes by users in the UI
  • REST API — changes made via API keys
  • Scenario merges — when a plan/scenario is merged to live data (including changes made via MCP tools and AI chat)

Events are delivered asynchronously. Mutations are never blocked by slow or failing endpoints.

Endpoints

MethodPathDescription
GET/org/:orgId/webhooksList webhook endpoints
GET/org/:orgId/webhooks/:idGet endpoint details
POST/org/:orgId/webhooksCreate endpoint
PATCH/org/:orgId/webhooks/:idUpdate endpoint
DELETE/org/:orgId/webhooks/:idDelete endpoint
POST/org/:orgId/webhooks/:id/testSend test delivery
POST/org/:orgId/webhooks/:id/rotate-secretRotate signing secret
GET/org/:orgId/webhooks/:id/deliveriesList delivery logs

Create Endpoint

POST /org/:orgId/webhooks

Request Body

FieldTypeRequiredDescription
namestringYesDisplay name (max 100 characters)
endpointUrlstringYesHTTPS URL to receive events
entityTypesstring[]YesEntity types to subscribe to (see table below)
eventTypesstring[]YesEvent types: create, update, delete

Example Request

bash
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/webhooks" \
  -H "Authorization: Bearer private_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HRIS Sync",
    "endpointUrl": "https://hris.example.com/flowstate/webhook",
    "entityTypes": ["employee", "team", "vacancy"],
    "eventTypes": ["create", "update", "delete"]
  }'

Example Response

json
{
  "data": {
    "id": "clx9w8h7k6e5",
    "name": "HRIS Sync",
    "endpointUrl": "https://hris.example.com/flowstate/webhook",
    "entityTypes": ["employee", "team", "vacancy"],
    "eventTypes": ["create", "update", "delete"],
    "enabled": true,
    "signingSecret": "whsec_a1b2c3d4e5f6...",
    "lastDeliveredAt": null,
    "lastDeliveryResult": null,
    "createdAt": "2026-03-18T15:00:00Z",
    "updatedAt": "2026-03-18T15:00:00Z"
  }
}

WARNING

The signingSecret is only returned when the endpoint is created and when you rotate the secret. Store it securely — it will not be shown again.


Entity Types

Subscribe to the specific entity types you need. Events are only delivered for entity types your endpoint is subscribed to.

Core Entities

Entity TypeDescription
employeeFull-time and part-time staff members
teamOrganizational teams and hierarchy
projectProjects with timelines and cost tracking
contractorExternal contractors and consulting firms
vacancyOpen positions and hiring pipeline
userPlatform user accounts

Allocations

Entity TypeDescription
employee_team_allocationEmployee assignments to teams (with FTE)
employee_project_allocationEmployee assignments to projects (with FTE)
team_project_allocationTeam assignments to projects (with FTE)
contractor_team_allocationContractor assignments to teams
contractor_project_allocationContractor assignments to projects
vacancy_team_allocationVacancy assignments to teams
vacancy_project_allocationVacancy assignments to projects

Compensation

Entity TypeDescription
salary_adjustmentEmployee salary changes (effective-dated)
bonusEmployee bonus records
contractor_rateContractor rate adjustments

Reference Data

Entity TypeDescription
cost_centerBudget tracking and financial allocation
value_streamBusiness capability groupings for projects
driverProject business driver types
lifecycle_stageProject progress stages
exchange_rateCurrency conversion rates
geographyGeographic locations

AI

Entity TypeDescription
ai_agentAI tool cost configurations

Event Types

EventDescription
createA new entity was created. before is null.
updateAn existing entity was modified. Both before and after are present.
deleteAn entity was deleted. after is null.

Payload Format

Every webhook delivery is an HTTP POST with a JSON body:

json
{
  "id": "d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8g",
  "timestamp": "2026-03-18T15:30:00.000Z",
  "entity_type": "employee",
  "entity_id": "clx1a2b3c4d5e6f7g8h9",
  "change_type": "create",
  "initiator": {
    "type": "user",
    "id": "clx9u8s7e6r5",
    "email": "jane.chen@acme.com"
  },
  "organization_id": "clx9o8r7g6i5d4",
  "before": null,
  "after": {
    "id": "clx1a2b3c4d5e6f7g8h9",
    "firstName": "Alex",
    "lastName": "Rivera",
    "email": "alex.rivera@acme.com",
    "startDate": "2026-04-01T00:00:00.000Z",
    "jobRoleId": "clx9r8q7w6e5",
    "geographyId": "clx3g2h1j0k9",
    "defaultCurrencyCode": "GBP"
  }
}

Payload Fields

FieldTypeDescription
idstring (UUID)Unique event identifier (for idempotency)
timestampstringISO 8601 timestamp of when the event occurred
entity_typestringEntity type (see tables above)
entity_idstring (CUID)ID of the affected entity
change_typestringcreate, update, or delete
initiator.typestringWho triggered the change (see below)
initiator.idstringID of the actor (user ID or API key ID)
initiator.emailstring or nullEmail of the actor (present for user type)
organization_idstring (CUID)Organization the event belongs to
beforeobject or nullEntity state before the change (null for creates)
afterobject or nullEntity state after the change (null for deletes)

Initiator Types

TypeDescription
userChange made by an authenticated user via the UI or GraphQL
api_keyChange made via the REST API with an API key
mergeChange applied by merging a scenario (covers MCP tools, AI chat)
systemSystem-triggered change (reserved for future use)

Signature Verification

Every delivery includes an HMAC-SHA256 signature in the X-Flowstate-Signature header. Verify this signature to ensure the payload was sent by Flowstate and has not been tampered with.

Headers

HeaderDescription
X-Flowstate-Signaturesha256=<hex-encoded HMAC>
X-Flowstate-Event-IdUnique event UUID (same as id in body)
Content-Typeapplication/json

Verification Example (Node.js)

javascript
import crypto from 'crypto';

function verifyWebhookSignature(body, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(body, 'utf8')
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your webhook handler:
app.post('/flowstate/webhook', (req, res) => {
  const signature = req.headers['x-flowstate-signature'];
  const rawBody = req.body; // Must be the raw string, not parsed JSON

  if (!verifyWebhookSignature(rawBody, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(rawBody);
  console.log(`Received ${event.change_type} on ${event.entity_type}: ${event.entity_id}`);

  res.status(200).send('OK');
});

Verification Example (Python)

python
import hmac
import hashlib

def verify_webhook_signature(body: bytes, signature: str, secret: str) -> bool:
    expected = 'sha256=' + hmac.new(
        secret.encode('utf-8'),
        body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

Secret Rotation

Rotate the signing secret without downtime:

POST /org/:orgId/webhooks/:id/rotate-secret

Example Request

bash
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/webhooks/clx9w8h7k6e5/rotate-secret" \
  -H "Authorization: Bearer private_..."

Example Response

json
{
  "data": {
    "id": "clx9w8h7k6e5",
    "signingSecret": "whsec_n3w5e6c7r8e9t..."
  }
}

TIP

After rotating, update the secret in your webhook handler. Both the old and new secrets are not valid simultaneously — update your handler immediately after rotation.


Test Delivery

Send a test event to verify your endpoint is reachable and your signature verification works:

POST /org/:orgId/webhooks/:id/test

Flowstate delivers a test event with entity_type: "employee", change_type: "create", and sample data. Check your endpoint's delivery log for the result.

Example Request

bash
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/webhooks/clx9w8h7k6e5/test" \
  -H "Authorization: Bearer private_..."

Delivery Logs

View delivery history for an endpoint:

GET /org/:orgId/webhooks/:id/deliveries

Example Response

json
{
  "data": [
    {
      "id": "clx9d8l7v6y5",
      "eventId": "d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8g",
      "entityType": "employee",
      "entityId": "clx1a2b3c4d5e6f7g8h9",
      "changeType": "create",
      "httpStatus": 200,
      "success": true,
      "errorMessage": null,
      "createdAt": "2026-03-18T15:30:01Z"
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 47,
    "hasNextPage": true
  }
}

Delivery Behaviour

PropertyValue
Timeout5 seconds per delivery attempt
MethodPOST with Content-Type: application/json
DeduplicationUse X-Flowstate-Event-Id or id in the payload for idempotency
OrderingEvents are delivered in approximate order but not guaranteed
Plan modeChanges within a scenario do NOT fire webhooks
MergeWhen a scenario is merged, one event fires per entity changed

INFO

Your endpoint should return a 2xx status code to acknowledge receipt. Non-2xx responses are logged as failures. The delivery timeout is 5 seconds — if your processing takes longer, acknowledge the event immediately and process it asynchronously.


Best Practices

  1. Always verify signatures. Reject requests with missing or invalid X-Flowstate-Signature headers to prevent spoofing.

  2. Use the event ID for idempotency. In rare cases, the same event may be delivered more than once. Use the id field to deduplicate on your end.

  3. Respond quickly. Return 200 OK as soon as you receive the event. Process the payload asynchronously if needed.

  4. Subscribe only to what you need. Each endpoint can filter by entity type and event type. Subscribing to fewer types reduces noise and delivery volume.

  5. Monitor delivery logs. Check the delivery log periodically for failures. Persistent failures may indicate endpoint issues or network problems.

  6. Rotate secrets regularly. Treat the signing secret like a credential. Rotate it on a schedule that matches your security policy.

Flowstate Documentation