Skip to help content
RightMessage
DevelopersReference

Webhook payloads, signatures, delivery, and limits

Receive flow and form webhooks, verify signed payloads, and understand delivery logs, deduplication, and failure limits.

Available on the Pro plan.

Updated

View as Markdown

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

SettingFlow Webhook nodeForm webhook action
DestinationWebhook URL. Required HTTPS endpoint.Where to send it. Required HTTPS endpoint.
MethodHTTP Method. POST, PUT, or PATCH. Default POST.Method selector beside the URL. Same methods and default.
Extra payloadAdditional Context. Optional JSON and supported substitutions, included as meta.Extra data. Same meta behavior.
PreviewPayload preview. Illustrative data, not a delivery test.Payload preview. Illustrative submitted fields and visitor data.
DeduplicationOnly fire once per visitor is on by default.Off. Each submitted form can send a webhook.
Delivery historyLogs 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 fieldValidation and default
nodeIdRequired string. Flow node UUID, or form webhook action ID.
visitorIdRequired string.
flowId, offerId, actionIdOptional nullable strings. Missing values become null. actionId takes precedence over nodeId for form action logging and deduplication.
visitorEmailOptional nullable string, not email-format validation. Defaults to null.
visitorContextOptional nullable array or object. Defaults to []. Browser context contains email, segments, scores, variables, and available provider fields and tags.
formSubmissionOptional nullable array or object. Defaults to []. Native forms send email and fields.
pageUrlOptional nullable string. Defaults to an empty string.
configSourceOptional 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.

FieldType and meaning
eventwebhook_action for a flow node, or form_webhook_submission for a form action.
flowIdFlow ID, or null when there is no containing flow.
nodeIdWebhook node ID. For a form action, the resolved action ID.
visitor.idRightMessage browser visitor ID. Not proof of an authenticated account.
visitor.emailEmail from the connected visitor profile, or null. It is separate from the email just submitted in a form.
visitor.segmentsActive segments keyed by rendered segment-group name. Values are rendered segment names. Unknown segment IDs remain keys with true values.
visitor.scoresCurrent 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.variablesStored variable map. Included only when nonempty.
visitor.customFieldsConnected profile custom fields. Included only when available and nonempty.
visitor.tagsConnected profile tag names. Included only when available and nonempty.
pageUrlPage URL supplied by the visitor's browser.
timestampServer delivery timestamp in ISO 8601 format.
metaOptional 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.

HTTP
POST /rightmessage/webhook HTTP/1.1
Host: example.com
Content-Type: application/json
X-RightMessage-Team-Id: 123456789
X-RightMessage-Flow-Id: ofn_workforge
X-RightMessage-Signature: sha256=<HMAC_HEX>
User-Agent: RightMessage-Webhook/1.0
JSON
{
	"event": "webhook_action",
	"flowId": "ofn_workforge",
	"nodeId": "workforge-qualified",
	"visitor": {
		"id": "workforge-example-visitor",
		"email": "lead@example.com",
		"segments": {"Team size": "11-50"},
		"scores": {"Project readiness": 8, "recommended_plan": "Team"},
		"variables": {"recommended_plan": "Team"}
	},
	"pageUrl": "https://example.com/assessment",
	"timestamp": "2026-10-13T12:00:00+00:00",
	"meta": {"source": "workforge-assessment"}
}

<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.

FieldMeaning
offerIdThe submitted form's underlying offer ID.
actionIdStable ID of its webhook action.
form.emailEmail submitted through a native form email field, or null. A shared Email question remains in form.fields under its label instead.
form.fieldsSubmitted 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.

JSON
{
	"event": "form_webhook_submission",
	"flowId": "ofn_workforge",
	"nodeId": "workforge-form-action",
	"visitor": {
		"id": "workforge-example-visitor",
		"email": null,
		"segments": {"Team size": "11-50"}
	},
	"pageUrl": "https://example.com/demo",
	"timestamp": "2026-10-13T12:00:00+00:00",
	"offerId": "ofr_workforge",
	"actionId": "workforge-form-action",
	"form": {
		"email": "lead@example.com",
		"fields": {"Company": "WorkForge", "Team size": "11-50"}
	}
}

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.

PlaceholderValue
{{ 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

HeaderValue
Content-Typeapplication/json.
User-AgentRightMessage-Webhook/1.0.
X-RightMessage-Team-IdProject PID.
X-RightMessage-Flow-IdFlow ID, when supplied by the browser.
X-RightMessage-Offer-IdForm's underlying offer ID, when supplied.
X-RightMessage-Action-IdForm webhook action ID, when supplied.
X-RightMessage-SignatureHMAC 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.

Text
sha256=<lowercase hexadecimal HMAC-SHA256 of the raw request body>

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.

PHP
<?php
$secret = getenv('RIGHTMESSAGE_WEBHOOK_SECRET');
if ($secret === false || $secret === '') {
	http_response_code(503);
	exit;
}
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_RIGHTMESSAGE_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $signature)) {
	http_response_code(401);
	exit;
}
$data = json_decode($payload, true, 512, JSON_THROW_ON_ERROR);

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.

Webhook Signing Secret settings with the secret masked and Reveal, Copy, Rotate Secret, and Verification Example controlsOriginal
Webhook Signing Secret settings with the secret masked and Reveal, Copy, Rotate Secret, and Verification Example controls

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.

  1. Click Rotate Secret.
  2. Review the Rotate Webhook Secret dialog.
  3. Click Rotate Secret to replace the secret, or Cancel to keep it.
  4. Update your receiver's secret storage with the new value.

Keep the secret out of client-side JavaScript and screenshots.

Delivery behavior and limits

BehaviorContract
DestinationHTTPS 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 methodsPOST, PUT, and PATCH.
Destination timeoutDefault 10,000 milliseconds. The relay rounds configured milliseconds up to whole seconds, with a minimum of one second.
Browser relay timeout15 seconds. This is separate from the destination timeout.
Rate limitThe 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.
SuccessDestination returns a 2xx response.
RetryThe relay makes one outbound attempt. There is no automatic retry schedule or delivery guarantee.
Flow fire-onceBrowser 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 deliveryFire-once is disabled. Repeated submissions can produce repeated requests.
Receiver responseLogged 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.

StatusMeaning
SuccessThe destination returned 2xx.
FailedURL rejection, network failure, timeout, or non-success destination response.
SkippedFire-once history already records a successful send for this visitor and action.
Rate LimitedThe visitor exceeded the relay limit.
validation_errorRequired relay data or destination configuration is invalid.
plan_errorWebhooks are unavailable for the project.
config_errorThe 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 outcomeHTTP response
Invalid request422, status: error, and errors.
Webhook entitlement missing403, reason: feature_not_available.
Visitor rate limited429, status: rate_limited.
Configuration absent404, reason: no_config.
Action absent404, reason: node_not_found.
URL not configured422, reason: no_webhook_url.
Already sent under fire-once202, status: skipped, reason: already_fired.
Outbound attempt completed202, 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.

Still need a hand?

Tell us what you’re trying to do and where you’re getting stuck.

support@rightmessage.com