Appearance
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
| Method | Path | Description |
|---|---|---|
GET | /org/:orgId/webhooks | List webhook endpoints |
GET | /org/:orgId/webhooks/:id | Get endpoint details |
POST | /org/:orgId/webhooks | Create endpoint |
PATCH | /org/:orgId/webhooks/:id | Update endpoint |
DELETE | /org/:orgId/webhooks/:id | Delete endpoint |
POST | /org/:orgId/webhooks/:id/test | Send test delivery |
POST | /org/:orgId/webhooks/:id/rotate-secret | Rotate signing secret |
GET | /org/:orgId/webhooks/:id/deliveries | List delivery logs |
Create Endpoint
POST /org/:orgId/webhooksRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name (max 100 characters) |
endpointUrl | string | Yes | HTTPS URL to receive events |
entityTypes | string[] | Yes | Entity types to subscribe to (see table below) |
eventTypes | string[] | Yes | Event 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 Type | Description |
|---|---|
employee | Full-time and part-time staff members |
team | Organizational teams and hierarchy |
project | Projects with timelines and cost tracking |
contractor | External contractors and consulting firms |
vacancy | Open positions and hiring pipeline |
user | Platform user accounts |
Allocations
| Entity Type | Description |
|---|---|
employee_team_allocation | Employee assignments to teams (with FTE) |
employee_project_allocation | Employee assignments to projects (with FTE) |
team_project_allocation | Team assignments to projects (with FTE) |
contractor_team_allocation | Contractor assignments to teams |
contractor_project_allocation | Contractor assignments to projects |
vacancy_team_allocation | Vacancy assignments to teams |
vacancy_project_allocation | Vacancy assignments to projects |
Compensation
| Entity Type | Description |
|---|---|
salary_adjustment | Employee salary changes (effective-dated) |
bonus | Employee bonus records |
contractor_rate | Contractor rate adjustments |
Reference Data
| Entity Type | Description |
|---|---|
cost_center | Budget tracking and financial allocation |
value_stream | Business capability groupings for projects |
driver | Project business driver types |
lifecycle_stage | Project progress stages |
exchange_rate | Currency conversion rates |
geography | Geographic locations |
AI
| Entity Type | Description |
|---|---|
ai_agent | AI tool cost configurations |
Event Types
| Event | Description |
|---|---|
create | A new entity was created. before is null. |
update | An existing entity was modified. Both before and after are present. |
delete | An 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
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Unique event identifier (for idempotency) |
timestamp | string | ISO 8601 timestamp of when the event occurred |
entity_type | string | Entity type (see tables above) |
entity_id | string (CUID) | ID of the affected entity |
change_type | string | create, update, or delete |
initiator.type | string | Who triggered the change (see below) |
initiator.id | string | ID of the actor (user ID or API key ID) |
initiator.email | string or null | Email of the actor (present for user type) |
organization_id | string (CUID) | Organization the event belongs to |
before | object or null | Entity state before the change (null for creates) |
after | object or null | Entity state after the change (null for deletes) |
Initiator Types
| Type | Description |
|---|---|
user | Change made by an authenticated user via the UI or GraphQL |
api_key | Change made via the REST API with an API key |
merge | Change applied by merging a scenario (covers MCP tools, AI chat) |
system | System-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
| Header | Description |
|---|---|
X-Flowstate-Signature | sha256=<hex-encoded HMAC> |
X-Flowstate-Event-Id | Unique event UUID (same as id in body) |
Content-Type | application/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-secretExample 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/testFlowstate 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/deliveriesExample 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
| Property | Value |
|---|---|
| Timeout | 5 seconds per delivery attempt |
| Method | POST with Content-Type: application/json |
| Deduplication | Use X-Flowstate-Event-Id or id in the payload for idempotency |
| Ordering | Events are delivered in approximate order but not guaranteed |
| Plan mode | Changes within a scenario do NOT fire webhooks |
| Merge | When 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
Always verify signatures. Reject requests with missing or invalid
X-Flowstate-Signatureheaders to prevent spoofing.Use the event ID for idempotency. In rare cases, the same event may be delivered more than once. Use the
idfield to deduplicate on your end.Respond quickly. Return
200 OKas soon as you receive the event. Process the payload asynchronously if needed.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.
Monitor delivery logs. Check the delivery log periodically for failures. Persistent failures may indicate endpoint issues or network problems.
Rotate secrets regularly. Treat the signing secret like a credential. Rotate it on a schedule that matches your security policy.