Failures (In-Survey Quality Checks)
Failures are the in-survey quality checks you run inside your Decipher survey โ things like honeypots, trap questions, or low-quality open-ends. When you send them to DQC, they become the signals behind the In-Survey Quality (ISQ) dispositions, giving you a fuller picture of respondent quality alongside the automatic device and trust checks.
For the full, always-up-to-date list of the quality checks DQC accepts, see the Quality Checks and Failures page. To understand how failures roll up into a final response category, see Dispositions.
๐ Overviewโ
Failures fall into two groups, and you only have to worry about one of them:
| Group | Examples | Who computes it | What you do |
|---|---|---|---|
| Automatic | dqcFraud, dqcDuplicate | DQC โ from the device score, data trust score, persona and duplicate signals the Toolbox already sends | Nothing (optional: set thresholds โ see below) |
| Custom | honeyPot, openEndQuestion, trapQuestion, other | You โ your own in-survey logic | Send them in the failures field |
Every survey detects honeypots or trap questions a little differently, so there is no generator for custom failures. This page gives you working examples to adapt โ copy the check you need and wire it to your own questions.
How failure values are interpretedโ
For every failure you send, provide your raw value. DQC normalizes it on ingestion:
| You send | DQC records |
|---|---|
a number (e.g. 2) | that many failures |
True or any non-empty text | 1 failure |
0, False, null, "" (empty) | no failure |
This rule applies to every failure. When a check can trip more than once โ several honeypots, multiple trap questions, or more than one low-quality open-end โ send the count as a number. When it's a single pass/fail, send 1 (fail) or 0 (pass).
Automatic failures โ dqcFraud & dqcDuplicateโ
You do not send these. DQC already receives your device score, data trust score, persona and duplicate flag through the Toolbox, so it computes fraud and duplicate failures on your behalf โ they are informative only.
If you want DQC to act on them (end the survey early for the respondent), add the Termination Scripts โ for example, terminate when the device score โค 10. You don't send anything extra: when a DQC termination fires, the send script automatically reports why in a terminationReason field โ deviceScore, dataTrustScore, persona, or duplicate (empty when there was no termination). The interactive Decipher XML Generator can produce those termination blocks for you.
Custom failures โ the failures fieldโ
Send your custom checks in a single failures object, keyed by these standardized names:
| Failure | Name (key) | Typical value | Feeds disposition |
|---|---|---|---|
| Honeypot | honeyPot | count of hidden fields filled | Automated ISQ |
| Open-end quality | openEndQuestion | count of low-quality open-ends | Manual / Automated ISQ |
| Trap question | trapQuestion | count of traps failed (or 1 / 0) | Automated ISQ |
| Anything else | other | count, or 1 / 0 | ISQ |
DQC maps each key to a quality-check type on our end. If a key is misspelled (e.g. honeypot instead of honeyPot), the value can't be mapped. Copy the names exactly as shown above.
Below are working examples for each โ include only the checks you actually run. Each one has two parts: the DQC script (the detection you add, where you plug in your question labels) and, where useful, an example question showing one way to build it in Decipher.
The labels you give your questions (hp1, trap1, Q5, OE1) are exactly how you reference them in the detection scripts โ and, for a honeypot, in the CSS (#question_hp1). Whatever you name a question is what goes in these lists.
Always store the label itself (hp1), never hp1.val. The scripts read each answer's value for you (with getattr(q, "val", "")). A stray .val in a list is a mistake Decipher won't flag when you save the survey โ keeping the lists label-only avoids that silent error.
๐ฏ honeyPotโ
A honeypot is a question a real person never sees (so they leave it blank), but a bot auto-fills. If it has any content, the respondent is almost certainly a bot โ and you can use several, so the value is a count of how many were filled.
DQC script. List your honeypot labels and count how many were filled:
def dqcCountHoneypots():
honeypots = [hp1] # your honeypot labels โ add hp2, hp3, ... for more
n = 0
for q in honeypots:
try:
if str(getattr(q, "val", "") or "").strip():
n += 1 # a hidden field was filled -> one failure
except:
pass
return n
# -> 0 (clean), 1 (one bot-fill), 2+ (multiple honeypots tripped)
Then add this line to dqcBuildFailures (see Adding failures to your survey):
f["honeyPot"] = dqcCountHoneypots()
Example question. Unlike the other checks (which read questions you already have), a honeypot needs a dedicated question you create and hide. Add a text question whose label matches the script (hp1) and a title bots like to auto-fill:
<text label="hp1" optional="1" size="40" translateable="0">
<title>Website</title>
</text>
Then hide it from humans with CSS โ Decipher's documented XML Style System. Add the rule inside your existing <style name="respview.client.meta"> block (the preconnect links from General Setup), after the links:
<style name="respview.client.meta"><![CDATA[
<link rel="preconnect" href="https://api.dqco-op.com" crossorigin="anonymous">
<link rel="preconnect" href="https://fpmetrics.dqco-op.com" crossorigin="anonymous">
<style type="text/css">
/* Hide the honeypot question from humans (bots still fill it) */
#question_hp1 { display: none; }
</style>
]]></style>
Decipher does not render a question under its raw label. For a question labeled hp1, the container's id is question_hp1 and its class is label_hp1. Target #question_hp1 (id) or .label_hp1 (class) โ swap in your own label.
Smart bots skip fields hidden with display:none. To catch more of them, push the field off-screen instead:
#question_hp1 { position: absolute; left: -9999px; height: 0; overflow: hidden; }
where="execute"Decipher's Hidden Questions feature (where="execute") removes the question from the page entirely โ a bot can't fill it, so it's useless as a honeypot. Hiding with CSS keeps the question present but invisible.
โ๏ธ openEndQuestionโ
Open-end quality is a number: how many of the respondent's open-text answers you judged to be low quality.
Unlike a honeypot or trap, an open-end has no built-in "right answer" โ quality is a judgement. Normally a person makes that call by reading answers after the survey (the manual review described on the Quality Checks and Failures page). But this script runs the moment the respondent finishes, so there's no reviewer in the loop โ you supply a rule that judges each answer automatically, in real time. (That makes this an Automated in-survey check, not the manual one.)
Keep that rule in its own isBadOpenEnd(answer) function and let the counter apply it to every open-end. Start simple (e.g. blank or too short):
def isBadOpenEnd(answer):
text = str(answer or "").strip()
if not text: # unanswered / not reached -> not a failure
return False
return len(text) < 3 # answered but too short -> your "bad" rule
def dqcCountBadOpenEnds():
questions = [OE1, OE2] # your open-end questions (labels, no .val)
return sum(1 for q in questions if isBadOpenEnd(getattr(q, "val", "")))
You can edit isBadOpenEnd directly or replace it with your own function โ just make sure dqcCountBadOpenEnds calls whatever name you use. Your rule can be as simple or as smart as you like โ length/emptiness, gibberish or repetition checks, keyword lists, and so on.
An empty answer โ e.g. the respondent was terminated before reaching the open-end โ returns False, so it isn't counted as a failure. Only answered-but-low-quality responses count. (Without this guard, an unreached open-end would wrongly show up as a failure on terminated respondents.)
Then add this line to dqcBuildFailures:
f["openEndQuestion"] = dqcCountBadOpenEnds()
Decipher <exec> blocks are real Python and can call external APIs. So isBadOpenEnd could send the answer to your own AI/scoring service and return its verdict:
def isBadOpenEnd(answer):
text = str(answer or "").strip()
if not text:
return False # unanswered / not reached -> not a failure
try:
return callYourScoringService(text) # returns True if low quality
except Exception:
return False # if the service errors, don't fail the respondent
Trade-offs: an external call adds latency to every submission and depends on that service staying up โ always wrap it in try/except, default to not flagging on error, and reserve it for cases a simple rule can't handle.
Example question (optional). Open-ends are ordinary text questions you already have โ just reference them by label:
<textarea label="OE1" optional="1">
<title>What did you like most, and why?</title>
</textarea>
๐ชค trapQuestionโ
A trap question has an obvious correct answer stated in the text (e.g. "Select 'Somewhat agree' to show you're paying attention"). If they answer it wrong, they failed it. Like honeypots, a long survey can have several traps โ count how many were answered incorrectly.
def dqcCountTrapFails():
# (question, expected row) for each trap.
traps = [
(trap1, "r3"),
(trap2, "r1"),
]
n = 0
for q, expected in traps:
try:
ans = getattr(q, "val", "")
if ans and ans != expected: # answered, but wrong
n += 1
except:
pass
return n
# -> 0 (all correct or unanswered), 1, 2, ... (number of traps failed)
Only answered-and-wrong traps count. A trap the respondent never reached (empty answer) is not counted as a failure, so abandons aren't penalized.
Then add this line to dqcBuildFailures:
f["trapQuestion"] = dqcCountTrapFails()
Example question (optional). A radio question with an obvious correct answer โ an attention check, or a consistency/knowledge check about the brand or topic:
<radio label="trap1">
<title>To show you're paying attention, please select "Somewhat agree".</title>
<row label="r1">Strongly disagree</row>
<row label="r2">Disagree</row>
<row label="r3">Somewhat agree</row>
<row label="r4">Agree</row>
</radio>
The expected answer here is r3 โ the value paired with trap1 in the traps list above.
otherโ
A catch-all for any quality issue that doesn't fit the names above โ red flags unique to your survey or context. Like the others, it can be a single flag or a count of how many such issues you found.
def dqcOther():
return 0 # your logic here: return a count, or 1 / 0 (default 0 = nothing found)
Then add this line to dqcBuildFailures:
f["other"] = dqcOther()
other is a single catch-all โ a count tells DQC how many miscellaneous issues a respondent had, but not what they were. If you run a specific, recurring check that deserves its own label, contact the DQC team (see below) so we can give it a dedicated name instead of grouping it under other.
Adding failures to your surveyโ
Every check is optional and independent. Create this builder once โ it starts empty โ and register it. Then, for each check you run, add its function (from its section above) and add its one line here. Each check's section tells you exactly which line to add.
def dqcBuildFailures():
f = {}
# Add one line here for each check you run.
# Each check's section above shows the exact line, e.g.:
# f["honeyPot"] = dqcCountHoneypots()
return f
addDQCField("failures", lambda: dqcBuildFailures())
For example: a survey that only runs a honeypot has a builder with the single line f["honeyPot"] = dqcCountHoneypots(); a survey that runs everything has one line per check. Checks you don't run are simply never added โ you never define their functions.
dqcBuildFailures() runs inside an <exec> block, which Decipher executes on the server when the respondent submits โ not in the browser. It reads each question's stored answer, so it's fine to reference questions from earlier pages that are no longer on screen. A question that hasn't been answered yet simply reads as empty (the guards in the examples handle that).
The failures value is sent to DQC. If you also want it visible in your own Decipher RESPONSES โ VIEW/EDIT RESPONSES view, store repr(dqcBuildFailures()) in a text field โ but the source of truth is the value sent to DQC.
Custom failures are highly specific to each survey and client. If you're unsure how to detect a check or which name to use, contact the DQC team โ we'll help you set it up.
โ Summaryโ
- Failures are the in-survey quality checks you run โ they power the ISQ dispositions.
dqcFraudanddqcDuplicateare computed by DQC (informative only); you don't send them โ optionally set thresholds in Termination Scripts.- Send custom checks in the
failuresfield using the exact keys:honeyPot,openEndQuestion,trapQuestion,other. - Value rule: number = that many ยท
True/text = 1 ยท0/empty = none. - Include only the checks you run, and contact DQC if you need help.