Portal Community

When to Use

URL auto-generation: Each workflow instance receives a unique webhook URL in the format https://app.bizfirstai.com/webhooks/wf/{workflowId}/{instanceKey}. The URL is displayed in FlowStudio after the workflow is published. No manual endpoint registration is required on the BizFirstAI side.

Configuration

SettingRequiredDescription
WebhookSecret Optional A shared secret string used for HMAC-SHA256 signature verification. When set, every inbound request must include an X-Hub-Signature-256 header containing sha256=<hex>. Requests with a missing or invalid signature are rejected with HTTP 401 and routed to the error port. The secret is never exposed in FlowStudio logs.
RequireBody Optional Boolean, default false. When true, HTTP requests with an empty or missing body are rejected with HTTP 400 and routed to the error port. Prevents downstream nodes from receiving null input data.
RequiredFields Optional Comma-separated list of top-level field names that must be present in the request body (e.g. orderId,customerId,amount). If any listed field is absent or null, the request is routed to the error port with a descriptive validation message identifying the missing field. Supports top-level fields only — use a downstream DataMapping node for nested field validation.
Always set WebhookSecret in production: An unprotected webhook endpoint can be triggered by any caller on the internet. Configure WebhookSecret and require your sending system to sign payloads with HMAC-SHA256. Stripe, GitHub, Shopify, and most modern SaaS providers support this natively with zero additional effort.

Output Ports

PortWhen It Fires
main The inbound request passes all validation checks: signature is valid (or not required), body is present (if RequireBody is true), and all RequiredFields are present. The system returns HTTP 200 to the caller immediately and the workflow continues on this port.
error Any validation failure: invalid HMAC signature, empty body when required, or one or more required fields are missing from the body. The error port receives a structured payload including error_code, error_message, and the raw request headers.

Output Fields

On the main port, all InputData (the HTTP request body parsed as JSON) is passed through unchanged. The trigger node appends these system fields:

FieldTypeDescription
__webhook_received_atstringISO 8601 UTC timestamp of when the request was received by the platform.
__webhook_instance_keystringThe instance key component of the webhook URL — useful for correlation and idempotency checks.
__webhook_methodstringHTTP method of the inbound request (POST, PUT, etc.).
__webhook_headersobjectAll HTTP request headers as key-value pairs, normalized to lowercase keys.

Sample Configuration

{
  "nodeType": "webhook-trigger",
  "settings": {
    "WebhookSecret": "whsec_a7b3c9d2e5f1234567890abcdef",
    "RequireBody": true,
    "RequiredFields": "event,data.orderId,data.customerId"
  }
}
Idempotency tip: Extract the webhook provider's event ID (Stripe's id field, GitHub's X-GitHub-Delivery header) immediately after the trigger and store it with a VariableAssignment node. Use this ID as an idempotency key in your database write nodes to safely handle webhook retries without duplicate processing.

Sample Output

Success Port

{
  "headers": {
    "content-type": "application/json",
    "x-webhook-source": "shopify",
    "x-shopify-hmac-sha256": "abc123...",
    "user-agent": "Shopify-Webhooks/1.0"
  },
  "body": {
    "id": 820982911946154508,
    "email": "jon@example.com",
    "total_price": "409.94",
    "currency": "USD",
    "fulfillment_status": null,
    "line_items": [
      { "id": 866550311766439020, "title": "IPod Nano - 8gb", "quantity": 1, "price": "199.00" }
    ]
  },
  "queryParams": {
    "source": "shopify",
    "event": "orders/create"
  },
  "method": "POST",
  "path": "/webhooks/trigger/wf_order_created",
  "receivedAt": "2025-03-15T12:30:00.000Z",
  "requestId": "req_abc123xyz"
}

Expression Reference

After the WebhookTrigger fires, downstream nodes reference the payload using the node's output name as set in FlowStudio (shown here as webhookTrigger).

ExpressionValue
{{ $output.webhookTrigger.event }}The event field from the posted JSON body.
{{ $output.webhookTrigger.data.orderId }}Nested field access using dot notation.
{{ $output.webhookTrigger.__webhook_received_at }}ISO timestamp of when the webhook arrived at the platform.
{{ $output.webhookTrigger.__webhook_headers['x-stripe-signature'] }}Access a specific request header by name (lowercase).
{{ $output.webhookTrigger.__webhook_instance_key }}Instance key for deduplication or correlation audit logs.

Node Policies & GuardRails

PolicyRationale
Always configure WebhookSecret in productionUnsigned webhooks can be spoofed by any caller on the internet. HMAC-SHA256 verification ensures only your trusted sender can trigger the workflow.
Validate at the trigger boundary with RequiredFieldsCatching missing fields at the entry point prevents downstream nodes from receiving incomplete data and producing silent failures deep in the workflow.
HTTP 200 is returned immediately on acceptanceThe platform returns 200 as soon as the request passes validation. This prevents retry storms from senders that interpret slow responses as failures. All workflow steps run asynchronously afterward.
Log the raw payload during integration testingConnect a CodeExecute or VariableAssignment node immediately after the trigger to capture __webhook_headers and the body to a debug log during initial integration. Remove or gate behind a feature flag before production.
Always handle the error portConnect the error port to a Slack or Email notification node so your team is alerted when unauthorized or malformed requests arrive. This often signals a misconfiguration on the sending system.
Use __webhook_instance_key as an idempotency keyStore processed instance keys in a cache or database table on arrival and check before processing. This safely handles duplicate webhook deliveries from retry-happy senders.

Pattern Examples

Pattern 1 — Stripe Payment Webhook with Signature Verification

Stripe posts payment_intent.succeeded events signed with HMAC-SHA256. The workflow verifies the signature, normalizes the payload to an internal schema, and routes to fulfilment.

// WebhookTrigger settings
{
  "WebhookSecret": "whsec_your_stripe_webhook_signing_secret",
  "RequireBody": true,
  "RequiredFields": "type,data.object.id,data.object.amount,data.object.currency"
}

// Downstream VariableAssignment — capture idempotency key
{
  "VariableName": "stripeEventId",
  "Value": "{{ $output.webhookTrigger.id }}"
}

// Downstream DataMapping — normalize to internal order payment schema
{
  "Mappings": [
    { "SourceField": "data.object.id",             "TargetField": "paymentIntentId" },
    { "SourceField": "data.object.amount",         "TargetField": "amountCents",    "Transform": "toInt" },
    { "SourceField": "data.object.currency",       "TargetField": "currency",       "Transform": "uppercase" },
    { "SourceField": "data.object.metadata.orderId", "TargetField": "internalOrderId" }
  ]
}

Pattern 2 — GitHub Push Hook for Deployment Trigger

GitHub delivers a push event on every commit. The workflow filters for main-branch pushes, captures the commit SHA, and initiates a deployment pipeline.

// WebhookTrigger settings
{
  "WebhookSecret": "github_webhook_secret_abc123",
  "RequireBody": true,
  "RequiredFields": "ref,repository.full_name,head_commit.id"
}

// Downstream IfCondition — only process commits to main branch
// Condition expression:
"{{ $output.webhookTrigger.ref }}" === "refs/heads/main"

// Downstream VariableAssignment — store commit SHA for deployment
{
  "VariableName": "deployCommitSha",
  "Value": "{{ $output.webhookTrigger.head_commit.id }}"
}

Pattern 3 — IoT Sensor Alert with Field Validation

Edge devices POST temperature and humidity readings. Validation ensures required fields are present. A downstream CodeExecute node checks thresholds and routes an alert if readings are out of range.

// WebhookTrigger settings (no secret — internal network, IP-restricted)
{
  "RequireBody": true,
  "RequiredFields": "deviceId,temperature,humidity,timestamp"
}

// Downstream CodeExecute — threshold check
var data = input;
var alert = false;
var reason = "";
if (data.temperature > 85) {
  alert = true;
  reason = "Temperature exceeded 85C on device " + data.deviceId;
}
if (data.humidity > 95) {
  alert = true;
  reason = reason + " Humidity exceeded 95% threshold.";
}
result = { alert: alert, reason: reason.trim(), deviceId: data.deviceId };