Webhook integration
The webhook URL on an agent points to an external workflow engine. Any system that can receive HTTP POST requests and return JSON responses will work. These examples use n8n but the same principles apply to any workflow engine or custom API.
What the webhook receives
Propeller sends a POST request to the configured webhookUrl with the following structure.
Headers
Content-Type: application/json(always included)Authorization: Basic ...(included when basic auth credentials are configured on the agent)X-Propeller-Signature: sha256=<hex-digest>(included when a signature salt is configured on the agent)
Body
{
"type": "SALES_HUB_ORDER_EDITOR",
"adminUserId": 205,
"requestMessage": "can you give me advice how to increase the order value?",
"requestMetadata": {
"payload": {
"tenderId": "019d026b-99ce-7b94-b5cd-f5d436d23f78",
"orderId": 3043,
"status": "VALIDATED",
"channelId": 1,
"source": "Sales Portal",
"contact": { "contactId": 14780, "firstName": "Wouter", "lastName": "Smrkovsky" },
"company": { "companyId": 308, "name": "M&G Group" },
"items": [ { "name": "La Marzocco Linea Mini – Yellow", "quantity": 1, "price": "4796.55", "costPrice": 3650, "childItems": [] } ],
"total": { "subTotalGross": 6246.94, "totalGross": 6291.94, "totalTax": 1321.31 },
"carriers": [ { "name": "DHL", "id": 8, "amount": 2.36 } ],
"payMethods": [ { "code": "IDEAL", "description": "Ideal | Wero", "allowed": true } ],
"paymentData": { "method": "", "overruled": "N", "price": 35 },
"postageData": { "carrier": null, "overruled": "N", "price": 10 },
"revisions": { "itemsFound": 4, "items": [] }
}
}
}
| Field | Description |
|---|---|
type | The agent type that triggered the request (for example SALES_HUB_ORDER_EDITOR) |
adminUserId | The backoffice user who triggered the agent |
requestMessage | The user's message (present for chat agents, absent for button agents) |
conversationId | Absent on the first request. Present on follow-up messages in multi-turn conversations, carrying the value your webhook returned earlier |
requestMetadata.payload | The full page context. Structure varies by agent type. See Agent payloads for the complete field reference |
What the webhook must return
The webhook responds with a JSON object. responseMessage is always required. For multi-turn agents conversationId is required too, and on the first response your webhook generates it: Propeller does not create conversation identifiers. The first request contains no conversationId, so your webhook mints one (any unique, non-blank string), returns it, and echoes the incoming value back on every follow-up. The structure of responseMetadata is entirely up to you and depends on what your workflow does.
{
"conversationId": "4308",
"responseMessage": "The result of your workflow, displayed to the user.",
"responseMetadata": {
"anyKey": "anyValue"
}
}
| Field | Required | Description |
|---|---|---|
conversationId | For multi-turn | Generated by your webhook on the first response, echoed back on follow-ups. Without it, follow-up messages cannot be linked to the conversation. Can be omitted for single-turn agents |
responseMessage | Yes | Text displayed to the user in the UI |
responseMetadata | No | Freeform object. Use it to pass structured data back to the UI or downstream systems. The contents are entirely defined by your workflow |
Example: Margin Validator n8n workflow
This n8n workflow powers the Margin Validator agent. It receives quote data from Propeller, calculates the overall margin and returns an approval or warning.
Workflow nodes
Webhook → Code (Process Quote Data) → Respond to Webhook
1. Webhook node listens for POST requests on the agent's webhook path. Configured with basic auth credentials matching the agent's webhookBasicAuthUsername and webhookBasicAuthPassword. Uses "response node" mode so the response is sent by a later node.
2. Code node contains the business logic. It extracts the quote payload from body.requestMetadata.payload and:
- Sums
costPrice × quantityfor all main items and their child items to get total cost - Calculates revenue as
subTotalGross - discountGrossfrom the totals - Computes margin percentage as
(revenue - totalCost) / revenue × 100 - Compares against a 20% threshold
- Returns an approval message if the margin is at or above threshold, or a warning if below
Core logic:
const quoteData = inputData.body?.requestMetadata?.payload || {};
const quoteItems = quoteData.items || [];
// Sum cost prices across all items including children
let totalCost = 0;
quoteItems.forEach(item => {
totalCost += (item.costPrice || 0) * (item.quantity || 1);
if (item.childItems) {
item.childItems.forEach(child => {
totalCost += (child.costPrice || 0) * (child.quantity || 1);
});
}
});
// Calculate margin
const totalRevenue = (quoteData.total?.subTotalGross || 0)
- (quoteData.total?.discountGross || 0);
const marginAmount = totalRevenue - totalCost;
const marginPercentage = totalRevenue > 0
? (marginAmount / totalRevenue) * 100
: 0;
const MARGIN_THRESHOLD = 20;
The response follows the required format. The conversationId is reused from the request when present and generated on the first request (only needed for multi-turn agents):
const conversationId = inputData.body?.conversationId
|| `conv-${Date.now()}`;
return [{
json: {
conversationId: conversationId,
responseMessage: responseText,
responseMetadata: {
processed: true,
marginPercentage: marginPercentage.toFixed(2),
marginAmount: marginAmount.toFixed(2),
totalRevenue: totalRevenue.toFixed(2),
totalCost: totalCost.toFixed(2),
threshold: MARGIN_THRESHOLD,
timestamp: new Date().toISOString()
}
}
}];
3. Respond to Webhook node sends the code node's output back to Propeller as a JSON response.
Try it yourself
Download the Margin Validator n8n workflowTo get the example running:
- In n8n, go to Settings → Import from File and select the downloaded JSON
- Open the Webhook node and create a new Basic Auth credential with a username and password of your choice
- Activate the workflow and copy the webhook URL that n8n generates
- In the Propeller Backoffice, create an agent with:
- Webhook URL set to the n8n webhook URL
- Basic auth credentials matching what you configured in step 2
- Type set to
SALES_HUB_QUOTE_EDITOR - Trigger set to
BUTTON
- Open a quote in the Sales Hub and click the agent button to validate the margin
Building your own workflow
Any workflow engine or custom API can serve as the webhook backend. The requirements are:
- Accept a POST request with a JSON body
- Optionally validate the
Authorizationheader (Basic Auth) and/orX-Propeller-Signatureheader (HMAC-SHA256) - Read the context from
body.requestMetadata.payload - Return a JSON response with
responseMessageand optionallyresponseMetadata
Since the webhook is fully external, tenants and partners can build any logic they need: call LLMs, query databases, integrate with CRMs, run business rules or chain multiple steps together. The workflow can also call the Propeller API with its own API key, both to fetch data beyond the page context and to make changes, for example updating the quote the user is viewing or creating sales tickets for follow-up.
Two practical guidelines:
- Respond quickly. The agent panel waits for your response. For long-running work such as a full catalog scan, return a short acknowledgement and deliver the outcome asynchronously, for example as a sales ticket.
- Keep credentials out of exports. Workflow exports should never contain API keys. Reconnect credentials after importing and sanitize any workflow you share. Never paste an API key into an AI assistant.
For working examples you can download and adapt, browse the Workflow Hub library.
See also
- Agent configuration for setting up webhook credentials
- Agent payloads for the complete field reference per agent type
- Conversations for the conversation lifecycle