Skip to main content

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 — the executionId from the data-action-proposal chunk delivered via chat streaming. Must be a UUID.

Headers

HeaderRequiredValue
Content-Typeyesapplication/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
}
}
FieldTypeRequiredDescription
result"success" | "failure" | "cancelled"yesTerminal outcome of the in-browser execution.
errorstringnoHuman-readable error description when result is "failure". Stored verbatim on the execution row.
contactMetadataobjectnoDebugging and analytics metadata (see below). Persisted on the execution row for the dashboard's Contact Submissions tab.

contactMetadata

FieldTypeDescription
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.
signalSourcestringFree-form tag identifying how the widget detected success (network_200, dom_mutation, navigation, timeout, etc.).
networkStatusnumberHTTP status of the underlying form submission, when observable.
submitToSignalMsnumberMilliseconds 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 (for contact_form / newsletter kinds) PII-masks params before 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 failed ExecutionResultRequestSchema
  • 403 FORBIDDEN_TARGET — the action's target URL is not on the bot's allowlist; the server refuses to record the success
  • 404 NOT_FOUND — the executionId is not a UUID, or no execution row with that id exists
  • 409 ALREADY_COMPLETED — the execution row has already been finalized; posts to a terminal execution are rejected so result events stay idempotent
  • 429 RATE_LIMITED — the per-visitor cap tripped; see Rate limits

Public endpoint errors return { "error": { "code": "..." } }. See Errors.