Action result posting
When the widget executes an action on behalf of a visitor (form submission, newsletter signup, booking prefill) it posts the outcome to this endpoint. The server updates the corresponding action_executions row, enforces the bot's URL allowlist as a defense-in-depth check, and appends a follow-up assistant message to the conversation so the bot can acknowledge the completion on the next turn.
POST /v1/public/actions/{executionId}/result
Path parameters
executionId— theexecutionIdfrom thedata-action-proposalchunk delivered via chat streaming. Must be a UUID.
Headers
| Header | Required | Value |
|---|---|---|
Content-Type | yes | application/json |
No Authorization header — the endpoint is CORS-unrestricted and scoped by executionId alone.
Request body
{
"result": "success",
"contactMetadata": {
"deliveryStatus": "delivered",
"deliveryMethod": "hidden_iframe",
"signalSource": "network_200",
"networkStatus": 200,
"submitToSignalMs": 1830
}
}
| Field | Type | Required | Description |
|---|---|---|---|
result | "success" | "failure" | "cancelled" | yes | Terminal outcome of the in-browser execution. |
error | string | no | Human-readable error description when result is "failure". Stored verbatim on the execution row. |
contactMetadata | object | no | Debugging and analytics metadata (see below). Persisted on the execution row for the dashboard's Contact Submissions tab. |
contactMetadata
| Field | Type | Description |
|---|---|---|
deliveryStatus | "delivered" | "deeplink_opened" | "optimistic" | "failed" | High-level classification of what the widget observed. |
deliveryMethod | "same_page" | "hidden_iframe" | "deep_link" | Which form-replay layer actually fired. See Form replay for the three-layer architecture. |
signalSource | string | Free-form tag identifying how the widget detected success (network_200, dom_mutation, navigation, timeout, etc.). |
networkStatus | number | HTTP status of the underlying form submission, when observable. |
submitToSignalMs | number | Milliseconds between posting the form and observing the success signal. Used for p95 latency analytics. |
Response 200
{ "ok": true, "result": "success" }
The server also:
- Updates
action_executions.result,completedAt,contactMetadata, and (forcontact_form/newsletterkinds) PII-masksparamsbefore persisting. - Appends an assistant-role follow-up message to the source conversation with an acknowledgement like
"Subscribe to newsletter — done."so the bot can reference the outcome on the next turn.
Rate limits
Result posts are limited to 5 per hour per (botId, visitorId) to prevent replay spam. A 429 response includes Retry-After in seconds. See Rate limits.
Errors
400 INVALID_REQUEST— body failedExecutionResultRequestSchema403 FORBIDDEN_TARGET— the action's target URL is not on the bot's allowlist; the server refuses to record the success404 NOT_FOUND— theexecutionIdis not a UUID, or no execution row with that id exists409 ALREADY_COMPLETED— the execution row has already been finalized; posts to a terminal execution are rejected so result events stay idempotent429 RATE_LIMITED— the per-visitor cap tripped; see Rate limits
Public endpoint errors return { "error": { "code": "..." } }. See Errors.