Qualtrics setup guide
Run an authorized voice module in one Qualtrics text-entry question. The native answer contains the completed recorded result; a separate browser journal supports interruption recovery.
These instructions load the survey from voice over the internet. Saving a ZIP on a tablet does not make this integration offline. If the connection drops, collection pauses; reconnect and resume the same interview.
A host is the outer survey that opens voice and receives its recorded results. For this setup you need permission to edit that host survey, a published voice survey, and its integration code.
How answers reach the host
One voice module saves one JSON text value in one host question, even when the module contains several questions. JSON is structured text: its reports list holds the individual recorded answers. The integration does not automatically create a separate host column for each voice question.
- Create the multiline Text Entry question with export tag strudl_result and paste the configured question.js into its JavaScript editor. That question’s answer box receives the result; the Embedded Data fields supply inputs to voice.
- During collection, voice accepts each recorded answer online. The adapter saves progress in this browser’s localStorage. On completion it writes the full result JSON into the hidden native answer box and enables Next.
- Click Next and finish the Qualtrics survey. Writing the answer box or seeing voice’s completion screen alone does not submit it to Qualtrics. Export the Text Entry answer column strudl_result, then read JSON.parse(value).reports. The adapter does not write an Embedded Data output field.
Once installed and configured, the supplied adapter performs these writes automatically. The enumerator does not copy answers between surveys. Native storage and export still need the device/account acceptance test described below.
The recorded result contains garbled reports for protected questions and ordinary recorded values for other questions, plus identifiers and completion status. Intended protected answers and garbling randomness are never returned. Host-provided enumerator/respondent IDs are not echoed in the result: keep those native host fields beside the module answer in the export.
See the saved result and extraction example1. Prepare the voice survey
Do this on the voice website. Start with the downloadable example, then substitute your own questionnaire once the integration works.
Download example questionnaireIn the editor, open Upload or download Markdown → Upload questionnaire Markdown and select the .md file. After publishing, keep the management page open: the Embed panel provides the dedicated iframe URL and integration code.
- Create one question or a complete questionnaire. For a first test, use the example below and synthetic IDs only.
- Publish a survey with one or more questions and a closing date that covers your fieldwork.
- On its management page, open Embed in another survey. Choose a label and consent mode, then generate an integration code. Save the code when it is shown; you can revoke it in the same panel.
Save the example as example-questionnaire.md and import it using the questionnaire tools in the editor. The same file is included in every download.
<!-- strudl:questionnaire v1 -->
# Online host example
```variables
respondent_id = host("respondent_id", "")
```
## first. Participant $respondent_id: have you ever missed an appointment?
[choose one]
- Yes*
- No
## last. Would you like more information?
[choose one]
- Yes
- No
Choose one consent mode
- For the first test, keep respondent consent in voice and leave scriptVersion empty. voice displays its own consent screen; leave any host consent field empty or 0.
- If the enumerator collects consent, choose external consent in Embed, enter the actual study script and version, then set scriptVersion to that exact version. The host must separately confirm consent for each interview. Setting the version alone does not confirm consent.
// Respondent consent:
scriptVersion: ""
// External consent (also requires this interview's host consent field = "1"):
scriptVersion: "study-v1"
Codes allow participation in this survey, not management. Keep codes and interview credentials out of URLs. Use HTTPS, except for local tests. Third-party cookies are not required.
2. Configure the download
Extract the ZIP and edit the configuration at the top of question.js. Set the iframe URL, integration code and consent script version. The file already contains the bridge and adapter; it does not load JavaScript from an external CDN.
In question.js, replace only the configuration block at the top with the block below. Keep all the bridge and adapter code underneath it. Later you will paste the entire question.js file into Qualtrics, not just this configuration block.
window.STRUDL_QUALTRICS = {
url: "https://strudl.io/embed/s/REPLACE-SURVEY-UUID",
code: "REPLACE-INTEGRATION-CODE",
scriptVersion: "",
interviewField: "strudl_interview_id",
consentField: "strudl_consent",
hostFields: { enumerator_id: "enumerator_id", respondent_id: "respondent_id" },
embeddedDataApi: "classic"
};
What the download contains
strudl-qualtrics-example/
├── question.js
├── config.js
├── adapter.js
├── embed-bridge.js
├── embed-host.js
├── simulator.html
├── simulator.js
├── simulator.css
├── example-questionnaire.md
└── README.md
Edit and copy question.js only. config.js, adapter.js and the two embed files are source copies for reference; editing them does not rebuild question.js. You do not upload this ZIP to Qualtrics or import it as a QSF. The simulator files stay on your computer.
3. Install and map host fields
- Before the module block in Survey Flow, declare Embedded Data fields
strudl_interview_id,enumerator_id,respondent_idandstrudl_consent. Populate them from your contact list or earlier host questions. For a synthetic test usesynthetic-001,E007,R0042and1. Production interview IDs must be unique and stable across reopening; never use one constant for all respondents. - Create one Text Entry question with a multiline answer box. Put it alone on a page using page breaks, enable Force Response, and give it the export tag
strudl_result. In the question’s JavaScript editor, replace the default code with the entire configuredquestion.js. Do not paste it into question text. - The default uses
getEmbeddedData. If your layout exposesgetJSEmbeddedData, setembeddedDataApi: "js"and declare the fields with the__js_prefix in Survey Flow, keeping configuration names unprefixed. The adapter stops when the selected API is unavailable. Verify the mapping in your actual theme before publishing.
For a test interview, set strudl_interview_id to synthetic-001. For another test interview, use synthetic-002. In production, populate this field from a persistent, unique host interview identifier before the module; a respondent ID alone is unsuitable if that person can be interviewed more than once.
Host values are read at runtime, not interpolated into JavaScript source. Preserve ID values as strings. The adapter hides the native answer box and disables Next until voice confirms completion and the recorded result is saved locally.
4. Save, reopen and export
Preview, then publish and test a real distribution link. Qualtrics receives the native answer when the page is submitted: continue past the module and finish the host survey. Export strudl_result, parse its JSON and use reports; it contains all recorded answers, never intended protected answers or resume credentials.
Recovery requires the same browser profile, host origin, question and interview ID. Do not clear browser storage, use private browsing or switch devices during an unfinished interview. Preview and live links may use different origins. Browser storage is not encrypted by voice. A native result without its journal is blocked; for cross-device recovery use a host integration with durable server storage.
Use an HTTPS browser with Web Locks support. Only one tab can open a given interview at a time; close that tab before reopening elsewhere in the same browser.
What the recorded result looks like
The example below is the complete type of JSON stored in strudl_result after completion. Resume credentials are stored separately and are not in this answer field.
{
"version": 1,
"survey": "example-survey-id",
"run_id": "example-run-id",
"completed": true,
"reports": [
{"question": "first", "reported": 1, "event_id": "example-run-id:first"},
{"question": "last", "reported": 0, "event_id": "example-run-id:last"}
]
}
These are illustrative recorded values for the downloadable two-question example, not predictions of what a respondent will report. Both questions list Yes first and No second: reported: 0 means recorded Yes; reported: 1 means recorded No. For the protected first question, this does not reveal the intended answer.
| Report field | How to read it |
|---|---|
reported | One recorded choice index, starting at 0. Match it to the answer order in the exact questionnaire used. |
text / number | The ordinary recorded text or number. |
multiple | A list of recorded choice indices, each starting at 0. |
skipped: true | An explicit skip. A question absent from reports has no recorded answer; do not convert it to No or zero. |
event_id | A stable answer identifier. Use it to remove duplicates if you combine repeated callbacks or exports. |
Turn the host answer into analysis columns
Run this JavaScript after exporting the host data, in your export-processing code. exportedRow means one parsed CSV row. Replace the column names if your host export uses different names. This is not code to paste into the voice questionnaire or the host question editor.
const result = JSON.parse(exportedRow.strudl_result);
if (result?.completed !== true) throw new Error("Incomplete module");
const byQuestion = Object.fromEntries(
result.reports.map(report => [report.question, report])
);
const analysisRow = {
interview_id: exportedRow.strudl_interview_id,
enumerator_id: exportedRow.enumerator_id,
respondent_id: exportedRow.respondent_id,
first_recorded: byQuestion.first?.reported ?? null,
last_recorded: byQuestion.last?.reported ?? null
};
For the example result above, first_recorded is 1 and last_recorded is 0. The IDs come from the host’s existing columns, not from the result. Keep first and last for the supplied example; replace them with your questionnaire’s question IDs for your study. The code preserves zero and leaves missing/skipped choices as null.
This creates an analysisRow object; it does not modify the host survey or write a CSV file by itself. Save those objects using your own export workflow. To use a recorded answer in a later host question during the interview, add platform-specific parsing/routing after module completion; the supplied adapters do not create those extra host fields.
5. Test before fieldwork
Extract the download, run python3 -m http.server 8081 in that directory, and open http://localhost:8081/simulator.html. Enter the dedicated voice URL, integration code, synthetic interview ID and consent details. The simulator shows the recorded answer and enables Next only after completion. It does not prove native platform compatibility.
Run these commands in a terminal on your computer, from the extracted package folder. Replace the example folder path with your own. On Windows, use py -3 instead of python3 if needed. Keep the terminal open while testing.
cd "/path/to/extracted/package"
python3 -m http.server 8081 --bind 127.0.0.1
This local server is only for testing on the same computer. A localhost or 127.0.0.1 address on a tablet points to the tablet itself. Fieldwork uses the installed host package or the published host survey, not this local test address.
The simulator takes its connection details from the form you fill in. Passing this test does not prove that the settings you edited in the production file are correct. Test the installed package or pasted question code too.
- Verify the entire handoff: finish a synthetic module, save/submit the host interview, export the receiving field, parse its JSON and locate both first and last by question ID. Check recorded values against the module result, not against the protected choices clicked by the respondent.
- Repeat the test in the actual host platform: complete two different interviews, return to the module, close and reopen the app or browser, and confirm distinct run IDs with unchanged answers.
- Interrupt connectivity before sending and after server acceptance. Reconnect and verify that reports are recovered without new random draws or duplicate answer rows.
- Confirm that onward routing waits for completion, failed host saves block progress, and missing or stale external consent is rejected.
- Test blocked third-party cookies, tablet layout, a revoked code, an expired survey and the private withdrawal link. Export a completed host interview and compare every recorded answer with voice results; check that long JSON values are not truncated.
Keep the resume journal confidential and separate from analyst results. Server withdrawal does not delete host answers, browser journals or earlier exports. Set a retention and deletion procedure before fieldwork. Provide a host exit route for participants who decline or leave the module.