Webhook payloads, signatures, delivery, and limits
Receive flow and form webhooks, verify signed payloads, and understand delivery logs, deduplication, and failure limits.
Webhooks send a flow's visitor context or a form submission to your HTTPS endpoint. Use them to receive quiz results in your own application or an automation service such as Zapier or Make.
You need an HTTPS receiver and a project with webhook access. A native email-platform integration is not required for a webhook form. Your receiving application handles any later contact creation, email subscription, or automation. Webhook delivery alone doesn't prove those actions succeeded.
Flow nodes and form actions
| Setting | Flow Webhook node | Form webhook action |
|---|---|---|
| Destination | Webhook URL. Required HTTPS endpoint. | Where to send it. Required HTTPS endpoint. |
| Method | HTTP Method. POST, PUT, or PATCH. Default POST. | Method selector beside the URL. Same methods and default. |
| Extra payload | Additional Context. Optional JSON and supported substitutions, included as meta. | Extra data. Same meta behavior. |
| Preview | Payload preview. Illustrative data, not a delivery test. | Payload preview. Illustrative submitted fields and visitor data. |
| Deduplication | Only fire once per visitor is on by default. | Off. Each submitted form can send a webhook. |
| Delivery history | Logs tab for the node. | The form action is identified by its actionId in relay records. |
Flow traversal does not wait for a webhook's delivery result. A later redirect waits for pending webhook and integration work for at most two seconds, then continues. Don't design a branch that assumes the receiving application has already finished processing.
Configuration changes need publication for the normal live visitor path. Preview skips delivery, and the on-site debugger records the proposed write without sending it.
Browser-to-relay request
The snippet posts JSON to /{teamPid}/webhooks/relay on its configured relay host. This visitor endpoint is separate from the authenticated REST API. It does not require an API token or dashboard session. The project must have webhooks enabled, and the destination comes from the project's saved configuration, not a URL supplied in the request.
| Request field | Validation and default |
|---|---|
nodeId | Required string. Flow node UUID, or form webhook action ID. |
visitorId | Required string. |
flowId, offerId, actionId | Optional nullable strings. Missing values become null. actionId takes precedence over nodeId for form action logging and deduplication. |
visitorEmail | Optional nullable string, not email-format validation. Defaults to null. |
visitorContext | Optional nullable array or object. Defaults to []. Browser context contains email, segments, scores, variables, and available provider fields and tags. |
formSubmission | Optional nullable array or object. Defaults to []. Native forms send email and fields. |
pageUrl | Optional nullable string. Defaults to an empty string. |
configSource | Optional nullable draft or published. Defaults to published. The normal live snippet uses published configuration. |
This relay accepts visitor-supplied context. A signed outbound request proves that RightMessage relayed the body, not that the visitor's identity or answers were independently verified.
Shared flow and form payload fields
The endpoint receives a JSON body, not form-encoded fields. These fields are common to both event types.
| Field | Type and meaning |
|---|---|
event | webhook_action for a flow node, or form_webhook_submission for a form action. |
flowId | Flow ID, or null when there is no containing flow. |
nodeId | Webhook node ID. For a form action, the resolved action ID. |
visitor.id | RightMessage browser visitor ID. Not proof of an authenticated account. |
visitor.email | Email from the connected visitor profile, or null. It is separate from the email just submitted in a form. |
visitor.segments | Active segments keyed by rendered segment-group name. Values are rendered segment names. Unknown segment IDs remain keys with true values. |
visitor.scores | Current score-source map, including stored variables. Variables with the same name overwrite score entries here, and numeric-looking variable values become numbers. Included only when nonempty. |
visitor.variables | Stored variable map. Included only when nonempty. |
visitor.customFields | Connected profile custom fields. Included only when available and nonempty. |
visitor.tags | Connected profile tag names. Included only when available and nonempty. |
pageUrl | Page URL supplied by the visitor's browser. |
timestamp | Server delivery timestamp in ISO 8601 format. |
meta | Optional rendered additional context. Non-null parsed JSON when valid, otherwise the rendered string. |
An empty collection can serialize as []. Don't require every optional visitor property to be present.
Flow example
This illustrative outbound request uses a fictional WorkForge receiver.
<HMAC_HEX> is a placeholder, not a usable signature. A successful receiver can return 204 No Content or another successful 2xx response. RightMessage doesn't parse a success response body into flow variables.
Form additions
Form webhooks add offerId, actionId, and form.
| Field | Meaning |
|---|---|
offerId | The submitted form's underlying offer ID. |
actionId | Stable ID of its webhook action. |
form.email | Email submitted through a native form email field, or null. A shared Email question remains in form.fields under its label instead. |
form.fields | Submitted values keyed by rendered field labels. Display-only HTML, rich text, spacers, and submit controls are omitted. The native email field is not repeated here. |
Use form.email for a newly submitted address. Without a connected profile, visitor.email can still be null in the same payload.
This illustrative payload shows that distinction.
The payload includes IDs, not an automatic flow name. Add a stable label such as {"flow":"WorkForge assessment"} to additional context if your automation needs one.
Segment and field limitations
visitor.segments is not a lossless list of every selected answer. When several active segments share a group, the current payload stores one value under that group's name. Later entries overwrite earlier ones. Groups with identical rendered names can also collide.
Form fields use labels as keys. Give fields distinct labels, and update your receiving automation when you rename them. Don't assume email-platform field IDs are the webhook keys.
Additional context substitutions
The extra-data field uses these literal {{ ... }} substitutions. It is not the general flow Liquid engine.
| Placeholder | Value |
|---|---|
{{ visitor.email }} | Connected visitor email, or an empty string. |
{{ visitor.id }} | Browser visitor ID. |
{{ flow.id }} | Flow ID. |
{{ flow.node_id }} | Resolved webhook node or action ID. |
{{ page.url }} | Current page URL. |
{{ segments.seg_example }} | Raw segment value for that segment ID, not its display name. |
{{ variables.example }} | Stored variable value. Non-string values are JSON encoded. |
Unsupported placeholders are not evaluated. Values are substituted before JSON parsing. Quotes or other characters in substituted text can make the result invalid JSON, in which case meta is sent as a string. For reliable automation, prefer the structured visitor and form fields for visitor-provided values.
Outbound headers
| Header | Value |
|---|---|
Content-Type | application/json. |
User-Agent | RightMessage-Webhook/1.0. |
X-RightMessage-Team-Id | Project PID. |
X-RightMessage-Flow-Id | Flow ID, when supplied by the browser. |
X-RightMessage-Offer-Id | Form's underlying offer ID, when supplied. |
X-RightMessage-Action-Id | Form webhook action ID, when supplied. |
X-RightMessage-Signature | HMAC signature described below. |
There is no editor option for arbitrary authorization headers. Configure your receiver to verify the RightMessage signature.
Verify webhook signatures
Every outbound request includes X-RightMessage-Signature in this format.
The key is the project's webhook signing secret. Compute the HMAC over the exact received bytes before parsing JSON. Parsing and reserializing can change escaping or whitespace and invalidate an otherwise correct signature.
This PHP receiver example verifies the raw body with a constant-time comparison. RIGHTMESSAGE_WEBHOOK_SECRET is server-side secret storage.
The signature authenticates the bytes sent by the relay. It does not prove that the visitor-provided email, page URL, or answers are true. The browser supplies visitor context. Don't use a webhook payload alone to grant account access or authorize a purchase.
The signature has no separate signed timestamp header or unique delivery ID header. The JSON includes timestamp, but your receiver must enforce any freshness and duplicate-processing policy it needs.
Find and rotate the signing secret
The Webhook Signing Secret card is under project Settings > Integration > Other integrations. Reveal shows the secret, Hide masks it, and Copy copies it. Verification Example expands PHP, Node.js, Ruby, and Python examples. RightMessage creates the secret when first requested if the project has none.
Rotation replaces the secret immediately. RightMessage does not sign with both keys during a transition. Coordinate the receiver update before rotating. The new value isn't available until rotation completes.
- Click Rotate Secret.
- Review the Rotate Webhook Secret dialog.
- Click Rotate Secret to replace the secret, or Cancel to keep it.
- Update your receiver's secret storage with the new value.
Keep the secret out of client-side JavaScript and screenshots.
Delivery behavior and limits
| Behavior | Contract |
|---|---|
| Destination | HTTPS hostname resolving to a public address. HTTP, private or reserved addresses, and unresolved hosts fail. Use a hostname rather than a literal IP address. |
| Outbound methods | POST, PUT, and PATCH. |
| Destination timeout | Default 10,000 milliseconds. The relay rounds configured milliseconds up to whole seconds, with a minimum of one second. |
| Browser relay timeout | 15 seconds. This is separate from the destination timeout. |
| Rate limit | The relay rejects attempts after 10 requests for the same visitor ID and project within its one-minute counter window. If the rate-limit store fails, this check allows the request. |
| Success | Destination returns a 2xx response. |
| Retry | The relay makes one outbound attempt. There is no automatic retry schedule or delivery guarantee. |
| Flow fire-once | Browser storage, an in-flight guard, and successful-delivery history suppress repeated sends for the same node and visitor ID. This is not exactly-once delivery across all devices. |
| Form delivery | Fire-once is disabled. Repeated submissions can produce repeated requests. |
| Receiver response | Logged status and failure details. A response body is not returned to the flow as data. |
Clearing a browser's data does not undo external processing. A changed visitor ID or repeated form submission can send another request. Make receiving operations safe to repeat when duplicate processing would matter.
Delivery logs
The flow node's Logs tab shows the ten most recent recorded deliveries, newest first. Its refresh control reloads them. Columns are Time, Status, Code, Duration, and Visitor.
Expand a row to inspect Request Payload or Response. The response view shows Status Code, Error, Duration, and Target URL. It doesn't provide the full successful response body. HTTP failure messages contain a shortened response-body excerpt.
| Status | Meaning |
|---|---|
| Success | The destination returned 2xx. |
| Failed | URL rejection, network failure, timeout, or non-success destination response. |
| Skipped | Fire-once history already records a successful send for this visitor and action. |
| Rate Limited | The visitor exceeded the relay limit. |
validation_error | Required relay data or destination configuration is invalid. |
plan_error | Webhooks are unavailable for the project. |
config_error | The configuration or webhook action could not be found. |
A blank log is not proof that a request never happened. The current log reader also returns an empty list if its history query fails. Check the receiver's logs when investigating a missing delivery.
Browser relay HTTP responses
The browser-to-relay request returns one of these responses. The relay resolves the URL, method, timeout, and extra-data template from project configuration before it signs and sends the outbound request. It is not a general-purpose proxy.
| Relay outcome | HTTP response |
|---|---|
| Invalid request | 422, status: error, and errors. |
| Webhook entitlement missing | 403, reason: feature_not_available. |
| Visitor rate limited | 429, status: rate_limited. |
| Configuration absent | 404, reason: no_config. |
| Action absent | 404, reason: node_not_found. |
| URL not configured | 422, reason: no_webhook_url. |
| Already sent under fire-once | 202, status: skipped, reason: already_fired. |
| Outbound attempt completed | 202, status: accepted. Inspect delivery logs for the destination result. |
For the configuration steps, see Webhook flow node and Form actions. For a complete integration test, see Verify an integration change.