Event payloads
Every node in the pipeline receives a specific context object depending on the scenario. This page is the exhaustive reference for every payload shape, every template variable, and how data flows between nodes.
How data flows
When an external service sends a webhook to HostWebhook, the data goes through a pipeline of nodes. Each node receives the data in a different shape depending on the scenario:
Original webhook payload
When a webhook arrives at a webhook's ingress URL, the raw JSON body is the original payload. This is the unmodified data from the external service (Stripe, GitHub, your app, etc.).
{
"id": "evt_1abc",
"type": "payment_intent.succeeded",
"data": {
"object": {
"id": "pi_xyz",
"amount": 5000,
"currency": "usd",
"customer": "cus_123"
}
}
}The primary webhook delivery sends this payload as-is to the target URL (unless a payload transform is configured on the webhook). All downstream nodes reference this as the payload object.
Delivery context (payload transforms)
When a payload transform is configured on a webhook or an additional target, the transform template has access to a richer context object — not just the payload.
{
"payload": {
"type": "payment_intent.succeeded",
"data": { "object": { "amount": 5000 } }
},
"headers": {
"content-type": "application/json",
"stripe-signature": "t=123,v1=abc..."
},
"meta": {
"eventId": "6650a1b2c3d4e5f6a7b8c9d0",
"webhookId": "6650a1b2c3d4e5f6a7b8c9d1",
"attempt": "1",
"receivedAt": "2024-06-01T12:00:00.000Z"
}
}| Object | Description | Template example |
|---|---|---|
| payload | The parsed JSON body of the incoming webhook | {{payload.type}} |
| headers | Inbound request headers (lowercase keys) | {{headers.content-type}} |
| meta.eventId | The HostWebhook event ID | {{meta.eventId}} |
| meta.webhookId | The webhook that received the event | {{meta.webhookId}} |
| meta.attempt | Current delivery attempt number (as string) | {{meta.attempt}} |
| meta.receivedAt | ISO 8601 timestamp when the event arrived | {{meta.receivedAt}} |
Additional target with forwardResponse
When an additional target has forwardResponse: true, the primary delivery's response is available as an additional response object in the transform context.
{
"payload": {
"type": "order.created",
"orderId": "ord_123"
},
"headers": { "content-type": "application/json" },
"meta": {
"eventId": "...",
"webhookId": "...",
"attempt": "1",
"receivedAt": "2024-06-01T12:00:00.000Z"
},
"response": {
"status": 200,
"body": { "processed": true, "internalId": "int_456" },
"raw": "{"processed":true,"internalId":"int_456"}"
}
}| Field | Description |
|---|---|
| response.status | HTTP status code from the primary delivery |
| response.body | Parsed JSON body (or empty object if not JSON) |
| response.raw | Raw response string |
{{response.body.internalId}} in a transform to include the primary server's ID in a Discord notification.Chain payload
When a webhook has chained webhooks, the next webhook in the chain receives a wrapped payload — not the original webhook body. This lets you access both the original data and the previous step's response.
Successful delivery (step 2+)
{
"original": {
"type": "order.created",
"orderId": "ord_123"
},
"previous": {
"processed": true,
"internalId": "int_456"
},
"response": {
"status": 200,
"body": { "processed": true, "internalId": "int_456" },
"raw": "{"processed":true,"internalId":"int_456"}",
"truncated": false,
"originalSize": 52
}
}| Field | Description |
|---|---|
| original | The very first payload in the chain (root webhook body) |
| previous | Parsed response body from the previous step (JSON or string) |
| response.status | HTTP status code from the previous step |
| response.body | Parsed JSON response (or empty object) |
| response.raw | Raw response string |
| response.truncated | true if the response was truncated due to plan limits |
| response.originalSize | Original byte count before truncation (only if truncated) |
Chain with forwardResponse
When a chain connection has forwardResponse: true, the HTTP body delivered to the chained webhook's target URL is the previous step's response.body — not the full wrapped object. Your server receives the previous server's response directly as the request body.
original, previous, response) is still accessible via the chain headers described below.Chain headers
In addition to the wrapped payload, HostWebhook adds special headers to every chain delivery:
| Header | Value |
|---|---|
| X-HostWebhook-Chain-Step | Current step number (e.g., "1", "2", "3") |
| X-HostWebhook-Chain-Original | Base64-encoded JSON of the original root payload |
| X-HostWebhook-Chain-Previous | Base64-encoded JSON of the previous step's response |
Failed delivery (continueOnFailure)
When a chain connection has continueOnFailure: true and the previous step's delivery failed, the next webhook still receives the chain payload — but previous contains an error object instead of the response:
{
"original": {
"type": "order.created",
"orderId": "ord_123"
},
"previous": {
"error": true,
"message": "Connection refused"
}
}response object — only the error in previous. Check for previous.error === true in your server to handle failure scenarios.HTTP Action context
HTTP Actions receive a context object that varies depending on the trigger scenario. The context is used to interpolate template variables in the URL, headers, and body of the HTTP request.
Scenario 1 — Direct webhook (no chain)
The most common scenario: a webhook arrives at a webhook, the primary delivery succeeds, and the connected HTTP Action fires.
{
"payload": {
"type": "order.created",
"orderId": "ord_123",
"customer": { "email": "[email protected]" }
},
"response": {
"status": 200,
"body": { "processed": true },
"raw": "{"processed":true}"
}
}| Field | Description |
|---|---|
| payload | The original webhook payload (raw body from external service) |
| response.status | HTTP status code from the webhook's primary delivery |
| response.body | Parsed JSON response from the primary target URL |
| response.raw | Raw response string from the primary target URL |
// HTTP Action URL:
https://api.crm.com/customers/{{payload.customer.email}}/orders
// HTTP Action body:
{
"orderId": "{{payload.orderId}}",
"wasProcessed": "{{response.body.processed}}",
"serverStatus": "{{response.status}}"
}Scenario 2 — Delivery failed (triggerOn: always)
If the HTTP Action is configured with triggerOn: always, it fires even when the primary delivery fails. In this case, there is no response object:
{
"payload": {
"type": "order.created",
"orderId": "ord_123"
}
}response fields in templates shared between success/failure scenarios: {{response.status ?? 0}}Scenario 3 — Chain event (step 2+)
When an HTTP Action is connected to a webhook that is part of a chain, it receives the chain context as an additional chain object:
{
"payload": {
"type": "order.created",
"orderId": "ord_123"
},
"response": {
"status": 200,
"body": { "enriched": true, "trackingNumber": "TRK-789" },
"raw": "{"enriched":true,"trackingNumber":"TRK-789"}"
},
"chain": {
"step": 2,
"original": {
"type": "order.created",
"orderId": "ord_123"
},
"previous": {
"enriched": true,
"trackingNumber": "TRK-789"
},
"priorResponse": {
"status": 200,
"body": { "enriched": true, "trackingNumber": "TRK-789" },
"raw": "..."
}
}
}| Field | Description |
|---|---|
| chain.step | Current chain step number |
| chain.original | The root webhook payload (step 0) |
| chain.previous | Parsed response from the previous step |
| chain.priorResponse | Full response object from the previous step (status, body, raw) |
payload always refers to the original webhook payload — not the chain wrapper. Use {{chain.previous.field}} to access the prior step's response.Scenario 4 — Test execution (dashboard)
When you use the Run Test button on the HTTP Action detail page, the context contains only what you provide:
{
"payload": {
"whatever": "you type in the textarea"
}
}No response or chain objects — only the payload you provide. Useful for testing template interpolation.
HTTP Action output (downstream webhooks)
When an HTTP Action has output webhooks configured, the response from the HTTP request is forwarded to those webhooks as a new event. The downstream webhook receives a structured payload:
{
"original": {
"type": "order.created",
"orderId": "ord_123"
},
"httpAction": {
"name": "Fetch Order Details",
"method": "GET",
"url": "https://api.legacy.com/orders/ord_123"
},
"httpResponse": {
"statusCode": 200,
"body": {
"orderTotal": 99.99,
"items": [
{ "sku": "ITEM-1", "qty": 2 }
]
},
"raw": "{"orderTotal":99.99,"items":[{"sku":"ITEM-1","qty":2}]}",
"truncated": false,
"originalSize": 68
}
}| Field | Description |
|---|---|
| original | The payload from the event that triggered the HTTP Action |
| httpAction.name | Name of the HTTP Action that made the request |
| httpAction.method | HTTP method used (GET, POST, PUT, etc.) |
| httpAction.url | The URL that was called (after template interpolation) |
| httpResponse.statusCode | HTTP status code from the external service |
| httpResponse.body | Parsed JSON response body (or empty object) |
| httpResponse.raw | Raw response string |
| httpResponse.truncated | true if the response was truncated due to plan limits |
| httpResponse.originalSize | Original byte count (only present if truncated) |
Example flow: bridging a service without webhooks
// 1. Webhook arrives at Webhook A with:
{ "orderId": "ord_123", "action": "check_status" }
// 2. HTTP Action is configured as:
// GET https://api.legacy.com/orders/{{payload.orderId}}
// 3. External API responds with:
{ "orderTotal": 99.99, "status": "shipped", "trackingUrl": "https://..." }
// 4. Webhook B receives:
{
"original": { "orderId": "ord_123", "action": "check_status" },
"httpAction": {
"name": "Check Order Status",
"method": "GET",
"url": "https://api.legacy.com/orders/ord_123"
},
"httpResponse": {
"statusCode": 200,
"body": {
"orderTotal": 99.99,
"status": "shipped",
"trackingUrl": "https://..."
},
"raw": "..."
}
}Scheduled Workflow payload
Scheduled webhooks fire on a cron schedule. The payload is whatever you defined when creating the scheduled webhook — there is no fixed structure.
{
"type": "daily_report",
"reportDate": "2024-06-01"
}The connected webhooks receive this payload as a regular event. All downstream processing (chains, additional targets, HTTP actions) works the same as for any other event.
Email Action context
Email Actions receive the same context object as HTTP Actions. Template variables work the same way in the email subject, recipients, and body.
{
"payload": { "type": "order.created", "orderId": "ord_123" },
"response": {
"status": 200,
"body": { "processed": true },
"raw": "..."
},
"chain": { // only if triggered from a chain event
"step": 2,
"original": { ... },
"previous": { ... },
"priorResponse": { ... }
}
}Order {{payload.orderId}} processed, or in the recipient field: {{payload.customer.email}}.Transform node context
Transform nodes receive the same base context as payload transforms: the event payload, inbound headers, and event metadata.
{
"payload": {
"type": "order.created",
"data": { "object": { "amount": 5000 } }
},
"headers": {
"content-type": "application/json"
},
"meta": {
"eventId": "6650a1b2c3d4e5f6a7b8c9d0",
"webhookId": "6650a1b2c3d4e5f6a7b8c9d1",
"attempt": "1",
"receivedAt": "2024-06-01T12:00:00.000Z"
}
}Transform nodes have two modes:
- replace—The transform output completely replaces the payload
- merge—The transform output is merged into the original payload (shallow merge)
Router payload
Routers perform transparent forwarding. The destination webhook receives the exact same payload, headers, and source IP as the original event — no wrapping or modification.
type: "payment" events to Webhook A and type: "subscription" events to Webhook B). The destination webhook sees the event as if it arrived directly.Summary table
Which context fields are available in each scenario:
| Scenario | payload | response | chain | headers | meta | httpResponse |
|---|---|---|---|---|---|---|
| Payload transform | ✓ | — | — | ✓ | ✓ | — |
| Additional target (forwardResponse) | ✓ | ✓ | — | ✓ | ✓ | — |
| Chain (success) | ✓ | ✓ | — | — | — | — |
| Chain (failure) | ✓ | — | — | — | — | — |
| HTTP Action (direct) | ✓ | ✓ | — | — | — | — |
| HTTP Action (failed delivery) | ✓ | — | — | — | — | — |
| HTTP Action (chain) | ✓ | ✓ | ✓ | — | — | — |
| HTTP Action (test) | ✓ | — | — | — | — | — |
| HA output webhook | ✓ | — | — | — | — | ✓ |
| Transform node | ✓ | — | — | ✓ | ✓ | — |
| Router | ✓ | — | — | ✓ | — | — |
| Email Action | ✓ | ✓ | if chain | — | — | — |
payload refers to original (the root event payload) and httpResponse contains the HTTP Action's response. These use different field names than the template context — they are the raw event payload delivered to the webhook.Template variables quick reference
All template variables available inside {{...}} expressions (in payload transforms, HTTP Action URL/headers/body, and email templates):
| Variable | Description | Available in |
|---|---|---|
| {{payload.field}} | Value from the webhook payload (dot-notation) | All |
| {{payload.nested.deep.field}} | Deeply nested payload values | All |
| {{headers.content-type}} | Inbound request header (lowercase) | Transforms |
| {{meta.eventId}} | HostWebhook event ID | Transforms |
| {{meta.webhookId}} | Webhook ID | Transforms |
| {{meta.attempt}} | Delivery attempt number | Transforms |
| {{meta.receivedAt}} | ISO 8601 timestamp | Transforms |
| {{response.status}} | HTTP status code from primary delivery | Additional targets, HTTP Action |
| {{response.body.field}} | Parsed response field | Additional targets, HTTP Action |
| {{response.raw}} | Raw response string | Additional targets, HTTP Action |
| {{chain.step}} | Current chain step number | HTTP Action (chain) |
| {{chain.original.field}} | Root payload field | HTTP Action (chain) |
| {{chain.previous.field}} | Previous step's response field | HTTP Action (chain) |
| {{chain.priorResponse.status}} | Previous step's HTTP status | HTTP Action (chain) |
Expression syntax
| Syntax | Example | Description |
|---|---|---|
| Null coalescing | {{payload.name ?? 'Guest'}} | Fallback value if field is null/undefined |
| Ternary | {{payload.active ? 'yes' : 'no'}} | Conditional value based on truthiness |
| Comparison | {{payload.amount > 1000 ? 'large' : 'small'}} | Compare with ===, !==, >, <, >=, <= |
| Path branches | {{payload.x ? payload.x : payload.y}} | Resolve field paths in branches |
| Pipe: json | {{payload.data | json}} | Serialize as JSON string |
| Pipe: upper | {{payload.name | upper}} | Uppercase string |
| Pipe: lower | {{payload.name | lower}} | Lowercase string |
| Pipe: trim | {{payload.name | trim}} | Trim whitespace |
Response truncation limits
Chain responses and HTTP Action output responses are subject to size limits based on your plan tier. If a response exceeds the limit, it is truncated and the truncated flag is set to true.
| Plan | Max response size | Applies to |
|---|---|---|
| Free | 8 KB | Chain responses, HTTP Action outputs |
| Pro | 64 KB | Chain responses, HTTP Action outputs |
| Enterprise | 1 MB | Chain responses, HTTP Action outputs |
response.body will fall back to the raw truncated string instead of a parsed object. Always check truncated if you depend on the response structure.Propagation depth
To prevent infinite loops (e.g., Webhook A chains to B, B chains to A), HostWebhook enforces a maximum propagation depth of 10. This applies to both chain steps and HTTP Action output webhooks.
- Chains — each chain step increments
chainStepby 1. After 10 steps, the chain stops. - HTTP Action outputs — each output delivery increments
propagationDepthby 1. After 10 levels, no further downstream events are created.