Flow Studio
Inbound Webhook Trigger
The WebhookTriggerNode — how external systems POST to a workflow-specific URL and how the payload becomes available as $json.
Node Configuration
{
"nodeType": "WebhookTrigger",
"config": {
"credentialId": 1042,
"responseMode": "immediate",
"responseBody": { "status": "received" },
"allowedEventTypes": ["order.created", "order.updated"],
"payloadSizeLimitKb": 512
}
}
URL Structure
Each workflow thread gets a dedicated webhook URL automatically assigned when the process is published:
POST https://api.bizfirstai.com/webhook/{tenantId}/{threadId}
Example:
POST https://api.bizfirstai.com/webhook/tenant-acme/proc-orders-main
Payload to $json Mapping
The full request body (JSON) becomes $json. HTTP headers are accessible via $json._headers. Query parameters are in $json._query:
// Inbound payload: { "event": "order.created", "orderId": "ord-001" }
$json.event // → "order.created"
$json.orderId // → "ord-001"
// HTTP headers
$json._headers["X-Source-System"] // → "shopify"
// Query parameters (?ref=campaign1)
$json._query.ref // → "campaign1"
Response Modes
| Mode | Behavior | Use When |
|---|---|---|
immediate | Returns 200 with static body right away; workflow runs async | Most integrations — don't make the caller wait |
onComplete | HTTP connection held open until workflow finishes; returns workflow output | Synchronous request/response webhooks (short workflows only) |
Executor Pattern
// ExecutionNodes/Webhooks/WebhookTriggerExecutor.cs
[NodeCapability(CapabilityType.Webhook)]
public class WebhookTriggerExecutor : BaseNodeExecutor
{
public override async Task<NodeExecutionResult> ExecuteAsync(NodeExecutionContext ctx)
{
// $json is already populated by the trigger middleware before this node runs
// This executor just validates the payload and routes
var eventType = ctx.Json?.GetString("event");
var allowedTypes = ctx.GetConfig<string[]>("allowedEventTypes");
if (allowedTypes?.Length > 0 && !allowedTypes.Contains(eventType))
return NodeExecutionResult.Success(
new { skipped = true, reason = "event type not in allowlist" },
portKey: "filtered");
return NodeExecutionResult.Success(new
{
event = eventType,
receivedAt = DateTimeOffset.UtcNow,
payloadSize = ctx.Json?.ToString().Length ?? 0
});
}
}
Tip: Use the
allowedEventTypes filter to avoid starting workflow executions for event types the workflow doesn't handle. This keeps execution logs clean and prevents unnecessary resource consumption.