In the 2025 Insights Association and GDQ benchmark, 75.2% of supplier records used encrypted or server-to-server links. Research agencies were at 91.5%. Put the other way round, roughly one supplier record in four arrived on a link that nothing stopped a respondent from editing.
A survey complete link is worth money. If a respondent can see it, they can bookmark it, share it, or change terminate to complete and press enter. Signatures are the cheapest fix available.
What a plain endlink allows
https://researchdart.com/s/end/complete?rdid=01k8zq4r2mxv6c3e9ab7t5wq1n
Everything needed to record a complete is visible. Checking that the session ID exists stops made-up IDs. First-outcome-wins stops a session that already ended from being changed. Neither stops a respondent who screened out on the first question from editing their own still-open session into a complete.
What the signature adds
HMAC combines a message with a secret key into a short fingerprint. Anyone holding the key can produce the same fingerprint from the same message. Nobody else can, and changing one character of the message changes the whole fingerprint.
For redirects, the message is the URL and the key is shared between the survey owner and the router. The survey appends the fingerprint. The router recomputes it and refuses the redirect if they differ.
The ResearchDart format
The rules are the same for supplier entry links and buyer endlinks.
- Build the URL with every parameter except the signature.
- Take the path and query: everything from the first
/after the domain. - Compute HMAC-SHA256 of that string with your secret, as lowercase hex.
- Append it as the last parameter,
h.
The domain and scheme are left out on purpose, so http, https and www variants of the same link all verify.
PHP
$path = '/s/end/complete?rdid=' . $rdid;
$url = 'https://researchdart.com' . $path . '&h=' . hash_hmac('sha256', $path, $secret);
Python
import hashlib, hmac
path = f"/s/end/complete?rdid={rdid}"
signature = hmac.new(secret.encode(), path.encode(), hashlib.sha256).hexdigest()
url = f"https://researchdart.com{path}&h={signature}"
Node.js
const crypto = require('crypto');
const path = `/s/end/complete?rdid=${rdid}`;
const signature = crypto.createHmac('sha256', secret).update(path).digest('hex');
const url = `https://researchdart.com${path}&h=${signature}`;
The rule that matters
Never compute the signature in the respondent's browser. If the secret sits in JavaScript on the survey page, anyone can read it in developer tools and sign any URL they like. You have then added complexity and removed no risk.
The signature has to come from a server: your survey platform's server-side scripting, your own backend, or a small signing service the survey calls. If your platform has none of these, do not fake it. Turn signing off for that project and report outcomes with the status API from a system you control. That gives the same protection, because the respondent never sees the request. Postbacks versus redirects covers the trade-offs.
Rotating secrets
Treat signing secrets as passwords. Keep them in the platform's secret storage, not in survey text. Rotate them when someone with access leaves. On ResearchDart rotation takes effect immediately, so update the survey first, rotate second, and run a sandbox session straight after.
When signatures fail
Nearly every rejected signature comes down to one of these:
- Parameters after
h. The signature must be the last thing in the URL. - Encoding differences. Sign exactly the string that will appear in the URL. If a value is URL-encoded in the link, sign the encoded form.
- The domain was signed. Start at the path.
- A stale secret after rotation.
- Uppercase hex from a library with different defaults.
Is it worth it?
On a consumer study paying a dollar a complete, a determined respondent editing URLs is a nuisance. On a B2B or healthcare study paying many times that, the incentive to forge a complete is real and the fix is an afternoon. We would sign every project where the survey platform can do it server-side, and use the status API everywhere else.