Overview
Truform protects forms with an "I'm human" checkbox. When clicked, it opens an interactive puzzle. After a successful solve, Truform adds a hidden response field to the form.
Keys
Each protected site uses two keys:
| Key | Where It Is Used | Safe To Expose? |
|---|---|---|
public site key |
Inside the HTML snippet. | Yes. |
private secret key |
Only on your server when verifying a response. | No. |
Never put the private secret key in HTML, JavaScript, client apps, or public repositories.
Basic Install
Add the Truform container inside your form and load the remote script.
<form method="post" action="/contact-submit.php">
<label>
Email
<input type="email" name="email" required>
</label>
<div
class="truform"
data-sitekey="your_public_site_key"
></div>
<button type="submit">Submit</button>
</form>
<script src="https://your-truform-domain.example/truform/api.js?v=2026-05-22" async defer></script>
The script automatically loads Truform's stylesheet, so the widget keeps the same design and dimensions on every site.
Server Verification
After the puzzle is solved, Truform adds a hidden field named truform-response to the form. Verify that value from your server.
PHP Example
<?php
$verify = curl_init('https://your-truform-domain.example/truform/api/siteverify.php');
curl_setopt_array($verify, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => [
'secret' => 'your_private_secret_key',
'response' => $_POST['truform-response'] ?? '',
],
]);
$result = json_decode((string) curl_exec($verify), true);
curl_close($verify);
if (empty($result['success'])) {
http_response_code(400);
exit('Truform verification failed.');
}
// Continue processing the trusted form submission.
Verification Response
{
"success": true,
"challenge_ts": "2026-05-18T20:30:00+00:00",
"hostname": "example.com",
"type": "slider"
}
Widget Options
data-theme="light", dark, or auto.
data-size="normal" for 304x78, or compact for 164x44.
route, sequence, slider, icon-select, odd-icon, or random.
data-callback="onTruformSolved" runs a browser function after a successful solve.
Full Snippet
<div
class="truform"
data-sitekey="your_public_site_key"
data-theme="auto"
data-size="normal"
data-puzzle-type="random"
data-callback="onTruformSolved"
></div>
<script>
function onTruformSolved(response) {
console.log('Truform solved:', response);
}
</script>
<script src="https://your-truform-domain.example/truform/api.js?v=2026-05-22" async defer></script>
Manual Rendering
Use manual rendering when your form is created dynamically or when you want to control exactly when Truform appears.
<div id="truform-box"></div>
<script src="https://your-truform-domain.example/truform/api.js?v=2026-05-22"></script>
<script>
truform.ready(async () => {
const widgetId = await truform.render('#truform-box', {
sitekey: 'your_public_site_key',
theme: 'dark',
size: 'compact',
puzzleType: 'sequence'
});
console.log('Widget id:', widgetId);
});
</script>
Client API
| Method | Purpose |
|---|---|
truform.ready(callback) |
Runs code after the Truform loader is ready. |
truform.render(container, options) |
Creates a widget manually. |
truform.getResponse(widgetId) |
Returns the current response token, if solved. |
truform.reset(widgetId) |
Resets a widget back to the checkbox state. |
Domain Rules
Truform keys are locked to the domains configured for that key. If the site is not allowed, the widget cannot load a challenge.
| Configured Domain | Allowed | Not Allowed |
|---|---|---|
example.com |
example.com, www.example.com |
app.example.com |
*.example.com |
example.com, app.example.com, www.example.com |
Unrelated domains |
* |
Any domain | None |
Examples
Contact Form
<form method="post" action="/send-message.php">
<input name="name" placeholder="Name" required>
<input type="email" name="email" placeholder="Email" required>
<textarea name="message" placeholder="Message" required></textarea>
<div class="truform" data-sitekey="your_public_site_key"></div>
<button type="submit">Send</button>
</form>
<script src="https://your-truform-domain.example/truform/api.js?v=2026-05-22" async defer></script>
Dark Compact Signup
<div
class="truform"
data-sitekey="your_public_site_key"
data-theme="dark"
data-size="compact"
data-puzzle-type="random"
></div>
Troubleshooting
| Issue | What To Check |
|---|---|
| Widget does not appear | Confirm api.js is loading and the container has class="truform" and data-sitekey. |
| Challenge says it cannot load | Confirm the public site key is active and the current domain is allowed. |
| Old widget behavior after an update | Change the ?v= value on api.js. The loader passes that version to its CSS and widget script. |
| Too many requests | Wait for the rate window to pass, or ask the Truform administrator to clear rate limits for testing. |
| Form submits without verification | Make sure the server checks truform-response with siteverify.php. |
| Verification fails on server | Confirm you are using the private secret key, not the public site key. |
| Compact widget is too small for the layout | Use data-size="normal" for desktop forms and reserve compact for narrow mobile layouts. |
Production Checklist
- Add the Truform HTML container to each protected form.
- Load
api.jsonce on the page. - Use the public site key in the browser snippet.
- Verify
truform-responseon the server with the private secret key. - Reject the form submission if server verification fails.
- Keep normal server-side validation, rate limiting, and abuse monitoring in place.