Skip to main content

Webhook Payloads

When an alert is delivered to a webhook channel, FORJ sends the full alert envelope — a JSON object containing everything about the moment the alert fired: what triggered it, the strategy's state, the event payload, indicator values, and trade parameters.

You can control the shape of the webhook payload using three transform modes: passthrough (send everything), mapping (pick specific fields), or template (build a custom JSON structure with expressions).

Alert envelope

Every alert produces an envelope with these top-level fields:

FieldTypeDescription
alertDefinitionIdstringUnique ID of the alert definition that fired
alertDefinitionNamestringName you gave the alert definition
alertDefinitionDescriptionstring?Optional description
alertKindstringGUARD, TRANSITION, ACTION, or EVENT
severitystringINFO, WARNING, or CRITICAL
workspaceIdstringWorkspace ID
stateMachineConfigIdstringStrategy ID
triggerTsnumberTimestamp when the alert fired (epoch seconds)
contextSymbolstring?Symbol from the strategy context
streamSymbolstring?Symbol from the data stream that produced the event
causeobjectWhat triggered the alert — shape varies by alertKind (see below)
eventobjectThe incoming event that was being processed when the alert fired
machineEventobjectStrategy execution metadata for this event

Cause object

The cause object shape depends on the alert kind:

Action cause (alertKind: ACTION)

FieldTypeDescription
cause.kindstringACTION
cause.actionIdstringID of the action that executed
cause.actionNamestringName of the action
cause.actionTypestringAction type (e.g., predefined)
cause.fromStatestringState the strategy was in before the transition
cause.toStatestringState the strategy transitioned to
cause.predefinedActionKindstring?EXECUTE_TRADE when the action contains an Execute Trade step
cause.predefinedConfigobject?Resolved trade parameters (see below)

When an Execute Trade action fires, cause.predefinedConfig contains:

FieldTypeDescription
cause.predefinedConfig.sl_pricenumber?Resolved stop loss price
cause.predefinedConfig.tp_pricenumber?Resolved take profit price
cause.predefinedConfig.symbolstring?Trading symbol
cause.predefinedConfig.timestampnumber?Execution timestamp (ms)
cause.predefinedConfig.entryPricenumber?Entry price
cause.predefinedConfig.directionstring?long or short
cause.predefinedConfig.orderTypestring?market, limit, stop, or stop_limit
cause.predefinedConfig.tradePlanIdstring?Trade plan ID
cause.predefinedConfig.tradePlanNamestring?Trade plan name
cause.predefinedConfig.lifecycleIdstring?Links entry and exit events

Transition cause (alertKind: TRANSITION)

FieldTypeDescription
cause.kindstringTRANSITION
cause.fromStatestringPrevious state
cause.toStatestringNew state

Guard cause (alertKind: GUARD)

FieldTypeDescription
cause.kindstringGUARD
cause.guardIdstringID of the guard that was evaluated
cause.guardNamestringName of the guard
cause.resultbooleanWhether the guard passed or failed

Event cause (alertKind: EVENT)

FieldTypeDescription
cause.kindstringEVENT
cause.eventNamestringName of the system event (e.g., ACK_TRADE_EXECUTED)
cause.triggerKindstringEvent category

Event payload

The event object contains the incoming market data:

FieldTypeDescription
event.typestringEvent type name
event.payload.opennumber?Candle open price
event.payload.highnumber?Candle high price
event.payload.lownumber?Candle low price
event.payload.closenumber?Candle close price
event.payload.volumenumber?Candle volume
event.payload.timestampnumber?Event timestamp
event.payload.indicators.triggerStreamIdstring?Physical stream ID that triggered the event
event.payload.indicators.byStreamobject?Indicator snapshots keyed by physical stream ID
event.payload.indicators.byStream.<streamId>.byIndicatorobject?Indicator values keyed by workspace indicator config ID

The live runtime indicator contract is stream-qualified. A typical payload shape looks like this:

{
"triggerStreamId": "stream_eurusd_1m",
"byStream": {
"stream_eurusd_1m": {
"ts": 1703548800,
"byIndicator": {
"wic_rsi_14": {
"name": "RSI_14_EMA_close",
"value": 65.4,
"params": {
"length": 14,
"smoothing": "EMA",
"priceSource": "close"
},
"outputs": ["value"]
}
}
}
}
}

Machine event metadata

FieldTypeDescription
machineEvent.triggerKindstringHow the event was triggered
machineEvent.triggerNamestring?Name of the trigger
machineEvent.hasRealChangebooleanWhether the strategy's state actually changed
machineEvent.inferredCandleTsSecnumber?Inferred candle timestamp (epoch seconds)

Transform modes

Passthrough

The default mode. Sends the entire alert envelope as the webhook payload with no transformation. Useful when your receiving system can parse the full JSON and extract what it needs.

Mapping

Select specific fields from the envelope, optionally rename them, and apply a filter. The webhook payload contains only the fields you specify.

Each field mapping has:

SettingDescription
SourceDot-path to a field in the alert envelope (e.g., cause.toState)
TargetName the field should have in the output payload
FilterOptional filter to apply to the value
DefaultFallback value if the source field is missing

Example mapping:

SourceTargetFilterDefault
alertDefinitionNamealertName
cause.toStatenewStateupper
event.payload.closepricenumber
cause.predefinedConfig.sl_pricestopLoss0

Produces:

{
"alertName": "My Trade Alert",
"newState": "IN_POSITION",
"price": 1.0842,
"stopLoss": 1.0822
}

Template

Build a custom JSON structure using {{expression}} placeholders. Any string value in the template is interpolated; non-string values (numbers, booleans) pass through unchanged.

Example template:

{
"content": "Trade signal: {{cause.predefinedConfig.direction | upper}} {{cause.predefinedConfig.symbol}}",
"embeds": [
{
"title": "{{alertDefinitionName}}",
"fields": [
{ "name": "Entry", "value": "{{cause.predefinedConfig.entryPrice}}" },
{ "name": "SL", "value": "{{cause.predefinedConfig.sl_price}}" },
{ "name": "TP", "value": "{{cause.predefinedConfig.tp_price}}" },
{ "name": "State", "value": "{{cause.fromState}} → {{cause.toState}}" }
]
}
]
}

This format works directly as a Discord webhook payload, for example.

Expression syntax

Expressions use {{path}} to reference a value from the alert envelope. The path is a dot-separated key sequence that walks into the envelope object.

Type preservation

When a template value is a single expression with no surrounding text, the resolved value keeps its original type:

Template valueResultType
"{{event.payload.close}}"1.0842number
"{{machineEvent.hasRealChange}}"trueboolean
"{{cause}}"{ ... }object

When a template value mixes text with expressions, the result is always a string:

Template valueResultType
"Price: {{event.payload.close}}""Price: 1.0842"string
"{{cause.fromState}} → {{cause.toState}}""Scanning → In Position"string

Missing values

If a path points to a field that doesn't exist:

  • Single expression: resolves to undefined
  • Mixed text: the placeholder is replaced with an empty string

Use the default filter to provide a fallback: {{cause.guardName | default:unknown}}

Array access

Use numeric indices in the dot-path to access array elements. For nested indicator values, use the stream-qualified path such as {{event.payload.indicators.byStream.stream_eurusd_1m.byIndicator.wic_rsi_14.value}}. For arrays, use {{some.array.0}} for the first element.

Filters

Filters transform a resolved value before it's included in the output. Chain them with the pipe character (|):

{{path | filter1 | filter2:arg}}

Filters are applied left-to-right. Each filter receives the output of the previous one.

FilterWhat it doesExample inputExample output
upperUppercases a string"in_position""IN_POSITION"
lowerLowercases a string"CRITICAL""critical"
numberConverts to a number (strings are parsed, non-numeric values become 0)"3.14"3.14
booleanConverts to boolean0false
stringConverts to string42"42"
jsonSerializes to a JSON string{ a: 1 }"{\"a\":1}"
dateConverts a timestamp to an ISO 8601 date string1704067200"2024-01-01T00:00:00.000Z"
default:valUses val if the value is null or undefinedundefined"val"
truncate:NTruncates a string to N characters (adds ...)"Hello World" (N=5)"Hello..."

Filter details

upper / lower — Only affect strings. Non-string values pass through unchanged.

number — Strings are parsed with parseFloat. If the result is NaN, returns 0. Non-string, non-number values return 0.

date — Accepts epoch seconds or milliseconds. Timestamps below 1,000,000,000,000 are treated as seconds and multiplied by 1000; above that threshold they're treated as milliseconds. Produces an ISO 8601 string like 2024-01-01T00:00:00.000Z.

default:val — Only triggers on null or undefined. Values like 0, "", and false are kept as-is. The fallback value is always a string.

truncate:N — Defaults to 100 characters if N is omitted. Only affects strings. The truncated output is the first N characters followed by ....

Filter chaining example

{{alertDefinitionName | upper | truncate:20}}
  1. Resolve alertDefinitionName"My Long Strategy Alert Name"
  2. Apply upper"MY LONG STRATEGY ALERT NAME"
  3. Apply truncate:20"MY LONG STRATEGY ALE..."