postMessage API
When you embed a HIPAAtizer form as an iframe, the form and the page around it are on different origins, so the page cannot reach into the form directly. The postMessage API opens a controlled channel between the two. The embedding page can prefill fields, read values back, validate the form, submit it programmatically, and subscribe to what the form is doing.
This is what you use when the form has to take part in a flow that the hosting page owns. Submitting the form when a customer clicks your own button, blocking a checkout until the form is valid, or prefilling fields from data the page already has are all typical cases.
Nothing is available by default. The API is off until you enable it for a form, and each origin, field, event and command is allowed individually.
Any field you expose through this API is sent to the embedding page, where it becomes readable by the scripts running on that page, including third-party scripts such as analytics or chat widgets.
Expose only the fields the integration genuinely needs, and never expose a field carrying PHI to a page you do not fully control. The field picker tags PHI fields so you can tell them apart.
What you can do with this feature
- Prefill one field or several fields from data the embedding page already holds.
- Read the current value of exposed fields.
- Validate the form, or specific fields, and get the validation errors back.
- Submit the form programmatically, with or without enforcing required fields.
- Be notified when a step is rendered, when an exposed field changes, when a step is submitted, and when the whole submission is complete.
- Receive a link to the finished submission.
Requirements
- A published form embedded on your page with the standard embed code, which creates the iframe this API talks to.
- Access to the code of the page that embeds the form, so you can add a script to it.
- The exact origin of that page, including scheme and host.
Where to find it
The API is configured per form.
- Go to My Forms.
- Click the Form Settings icon next to the form you want to configure.
- Open the Advanced Form Settings tab.
- Find the postMessage API section.
- Save the form settings when you are done.
Turning it on
Enable the API
Tick Enable postMessage API. Until this is on, the form ignores every incoming command and sends no events, whatever else is configured.
Allowed origins
Add the origin of each page that is allowed to talk to the form. You can add up to three.
Each entry is matched exactly on scheme, host and port, so these are all separate entries:
https://clinic.example.com
https://shop.example.com
https://example-shop.myshopify.com
With an empty list no exchange happens at all, in either direction. This is the most common reason an integration appears to do nothing: the form receives the command, sees an origin that is not on the list, and stops there without replying.
A staging or preview domain is a different origin from your production domain and needs its own entry.
Exposed fields
Choose which fields the embedding page may read and write. The picker lists every field of the form by its label together with its unique name, and splits them into two groups:
- SAFE TO EXPOSE for ordinary fields.
- CONTAINS PHI for fields marked as containing protected health information, each tagged
PHI.
There is also an action to add all safe fields at once, which skips the PHI ones.
Field values are filtered against this list every time they cross the channel, in both directions. A field that is not on the list cannot be read, cannot be written, and never appears in any event payload. If the list is empty, the page can still validate and submit the form, but it will receive no field values.
Events and commands
Tick the events you want the form to send and the commands you want it to accept. A command that is not ticked is ignored silently, exactly as if it came from a disallowed origin.
Code for your page
Rather than writing the plumbing by hand, open the Code for your page block in the same section. It generates a snippet from the settings you just made, with the form origin, the iframe id, the request and response handling and an example call for each command you allowed already filled in.
Paste it into the page that embeds the form, below the embed code.
Take the form origin from this generated snippet instead of typing a domain by hand. It depends on the form version and on whether your account uses a white-label domain, so it is not the same for every form.
Message format
The iframe the embed code creates has the id {WORKFLOW_ID}-iframe, where {WORKFLOW_ID} is the Workflow ID of the form. You can copy that id from the Integrations tab of the same Form Settings panel.
Sending a command
Post a message shaped like this to the iframe, targeting the form origin:
const requestId = String(Date.now()) + Math.random().toString(16).slice(2);
iframe.contentWindow.postMessage({
kind: 'request',
requestId,
command: {type: 'SetFieldValue', payload: {name: 'patient_email', value: 'a@example.com'}},
}, FORM_ORIGIN);
The requestId is yours to generate. It comes back on the response so you can match a reply to the request that caused it. Messages without a command field are ignored.
Receiving the reply
Every allowed command produces one response, delivered to the allowed origins:
{
kind: 'response',
requestId: 'the id you sent',
success: true,
data: {},
error: 'only present when success is false'
}
success reports whether the command itself could be carried out. For Validate and Submit, whether the form was actually valid is reported separately, in data.valid.
A command that is disallowed, or that arrives from a disallowed origin, produces no response at all. See Troubleshooting.
Receiving events
Events are not tied to a request and carry no requestId:
{
kind: 'event',
type: 'FormReady',
data: {}
}
Always verify the sender before trusting a message:
window.addEventListener('message', (event) => {
if (event.origin !== FORM_ORIGIN) return;
if (event.source !== iframe.contentWindow) return;
const message = event.data || {};
// handle message.kind === 'response' and message.kind === 'event'
});
Commands
| Command | Payload | data in the response |
|---|---|---|
GetFieldValue | {name} | The field value, or nothing if the field is not exposed |
GetFieldsValue | {names} optional | An object of the requested exposed fields |
SetFieldValue | {name, value} | None |
SetFieldsValue | {values} | None |
Validate | {names} optional | {valid, values, errors, message} |
Submit | {ignoreRequiredRules} optional | {valid, values, errors, message} |
Notes on individual commands:
GetFieldsValueandValidateaccept nonamesat all, in which case they act on the whole form. Requested names are always narrowed down to the exposed fields first.SetFieldValueandSetFieldsValuesilently skip anything that is not exposed, so a prefill that writes nothing usually means the field is missing from the exposed list.Submitvalidates the form and submits it. The response arrives after the submission has been attempted, sosuccess: truetogether withvalid: truemeans the submission went through.SubmitwithignoreRequiredRules: trueskips validation and submits whatever is currently filled in.- An unrecognised command type comes back with
success: falseand anUnknown commanderror. Command types are case-sensitive.
Events
| Event | data | Fires when |
|---|---|---|
FormReady | {formIndex} | A step is rendered and ready to accept commands |
FormsStateChange | {formsState} | An exposed field changes |
SubmitSuccess | {formIndex, formId, values} | A step has been submitted |
AllFormsSubmitted | {formsState, submissionURL} | Every step has been submitted |
formIndex is the zero-based index of the current step. formsState is an array with one entry per step, each holding that step's exposed field values.
AllFormsSubmitted always carries submissionURL, even when no fields are exposed. On a multi-step form it fires only after the last step, so a single Submit command does not complete a multi-step submission.
submissionURL opens the submission in the HIPAAtizer dashboard and requires an authenticated session. It is a link for your team, not a link you can hand to the person filling in the form.
How the form protects the exchange
Both ends of the channel check who they are talking to:
- The form accepts a command only from the page directly containing it, and only from an allowed origin. A page nested one level deeper, such as a storefront preview inside a theme editor, is not the form's parent and cannot reach it.
- The form sends responses and events only to the allowed origins, so a page that is not on the list never receives them even if it somehow sent a command.
- Field values are filtered against the exposed fields list on the way in and on the way out.
- Your own listener should check
event.originandevent.source, as in the example above, so another frame on your page cannot impersonate the form.
Troubleshooting
| What you see | Usual cause |
|---|---|
| Command sent, nothing happens, no response at all | The API is off, the command is not allowed, or the page origin is not on the allowed origins list. Each of these makes the form drop the message without replying |
| Nothing happens, and the origin is definitely allowed | The message is missing the kind, requestId and command envelope, or it is being sent from a frame that is not the form's immediate parent |
| Your code waits forever | Add a timeout. A dropped command produces no reply, so code that only waits for a response will hang |
Response arrives, but data is empty | The fields involved are not in the exposed fields list |
Validate reports the form as valid although fields are empty | The requested names were narrowed to the exposed fields and none of them are exposed, so nothing was validated |
success: false with an Unknown command error | The command type is misspelled or cased differently |
Submit succeeded, but no AllFormsSubmitted | The event is not allowed, or the form has more steps still to submit |
Values changed in the form, but no FormsStateChange | The changed fields are not exposed. The event carries nothing until at least one field is exposed |
Related
- Add a HIPAAtizer Form to a Shopify Product Page: a full integration built on this API.
- Advanced Form Settings: the rest of the settings on the same tab.
- Integrations: where the form's Workflow ID is shown.