Credentials kept on your backend
Keep permanent credentials out of browser code and rotate secrets when required.
TRUSTIKS API
A focused API for software teams that need a simple approved or rejected result without owning the verification pipeline.
Built for real operations
Keep permanent credentials out of browser code and rotate secrets when required.
Use a short-lived token for each browser verification and confirm the final result from your own server.
Trace every decision to the exact policy and model versions used.
EXPLICIT BY DEFAULT
Integrate against a small surface area while TRUSTIKS manages quality checks, processing and the final policy decision.
POST /v1/verifications
{
"id": "vrf_123456",
"status": "approved"
}STOREFRONT INTEGRATION
Add the TRUSTIKS widget to your storefront, open it before a restricted action, and confirm every result from your own server.
Paste it immediately before </body> in the site-wide theme or layout.
Call Trustiks.verify before an age-restricted cart action, checkout or protected access.
Send sessionToken to your backend. Continue only after TRUSTIKS returns approved.
Test the browser journey and server confirmation before enabling it for customers.
Only the public widget ID belongs in browser code. Keep permanent credentials on your server.
| Identifier | Issued from | Store it | Public? |
|---|---|---|---|
pub_… · Public widget ID | When a website device is created | HTML and Trustiks.verify | Yes |
dk_… + ds_… · Device credentials | Widget → 02 · API Credentials; secret shown once | Server environment variables | No |
sessionToken · Verification UUID | Created per check; result.sessionToken | Bind to your order/session and record as used | Yes, but never trust it |
The browser result is for UX only. Your backend must confirm the decision with TRUSTIKS.
Copy the device key and one-time secret from Widget → 02 · API Credentials.
Store TRUSTIKS_DEVICE_KEY and TRUSTIKS_DEVICE_SECRET only on your backend.
Receive sessionToken and your order/session id, then call TRUSTIKS with both headers.
Repeat after 1–2 seconds. A check unfinished after 120 seconds is reported as rejected.
Bind an accepted token to one order/session in durable storage and never accept it twice.
Prepare the deploy, regenerate, then update env immediately. The old secret stops working at once.
GET {API_BASE}/v1/verifications/{sessionToken}X-Device-Key: dk_…{"id":"<uuid>","status":"approved|rejected|in_progress"}No timestamps are returned. Completed results do not expire.Verification webhooks are not available: use polling. The dashboard server-confirmation test completes only after your server makes this authenticated GET for its test verification.
Replace the public widget id in HTML. Keep the device key and secret only in server environment variables.
<!-- Add once before </body> in your site-wide layout. -->
<script src="https://www.trustiks.com/widget.js"
data-client-id="YOUR_PUBLIC_WIDGET_ID"></script>
<button id="age-checkout" type="button">Continue to checkout</button>
<script>
document.getElementById("age-checkout").addEventListener("click", function () {
Trustiks.verify({
onComplete: async function (result) {
const response = await fetch("/api/verify-age", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ sessionToken: result.sessionToken, orderId: "YOUR_ORDER_ID" })
});
const decision = await response.json();
if (response.status === 200 && decision.approved === true) {
window.location.href = "/checkout";
} else if (decision.status === "in_progress") {
alert("Verification is still processing. Please try again shortly.");
} else {
alert("Age verification was not approved.");
}
}
});
});
</script>// Express server. Implement the two storage helpers with durable storage.
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
app.post("/api/verify-age", express.json(), async (req, res) => {
const { sessionToken, orderId } = req.body;
if (typeof sessionToken !== "string" || typeof orderId !== "string")
return res.status(400).json({ approved: false });
// session_token must have a UNIQUE constraint.
if (await sessionTokenWasUsed(sessionToken))
return res.status(409).json({ approved: false, status: "already_used" });
// ~15 s at most: stay inside your server's request timeout. The widget has
// already waited for the result, so in_progress here is rare; on 202 the
// browser can simply ask again.
for (let attempt = 0; attempt < 10; attempt += 1) {
const response = await fetch(
process.env.TRUSTIKS_API_URL + "/v1/verifications/" + encodeURIComponent(sessionToken),
{ headers: {
"X-Device-Key": process.env.TRUSTIKS_DEVICE_KEY,
"X-Device-Secret": process.env.TRUSTIKS_DEVICE_SECRET
}}
);
if (!response.ok) return res.status(502).json({ approved: false });
const result = await response.json();
if (result.status === "in_progress") {
await wait(1500); // Poll again after 1–2 seconds.
continue;
}
if (result.status === "rejected")
return res.status(403).json({ approved: false, status: "rejected" });
if (result.status === "approved") {
// Atomically bind the token to this order; false means it was reused.
const saved = await useSessionTokenOnce(sessionToken, orderId);
if (!saved) return res.status(409).json({ approved: false, status: "already_used" });
return res.status(200).json({ approved: true, status: "approved" });
}
return res.status(502).json({ approved: false });
}
return res.status(202).json({ approved: false, status: "in_progress" });
});<?php
// PHP server. Implement both storage helpers with durable storage.
$payload = json_decode(file_get_contents('php://input'), true);
$sessionToken = $payload['sessionToken'] ?? '';
$orderId = $payload['orderId'] ?? '';
header('Content-Type: application/json');
if (!is_string($sessionToken) || $sessionToken === '' || !is_string($orderId) || $orderId === '') {
http_response_code(400);
echo json_encode(['approved' => false]);
exit;
}
// session_token must have a UNIQUE constraint.
if (session_token_was_used($sessionToken)) {
http_response_code(409);
echo json_encode(['approved' => false, 'status' => 'already_used']);
exit;
}
$lastStatus = '';
// ~15 s at most: stay inside max_execution_time. The widget has already
// waited for the result, so in_progress here is rare; on 202 ask again.
for ($attempt = 0; $attempt < 10; $attempt++) {
$url = getenv('TRUSTIKS_API_URL') . '/v1/verifications/' . rawurlencode($sessionToken);
$curl = curl_init($url);
curl_setopt_array($curl, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [
'X-Device-Key: ' . getenv('TRUSTIKS_DEVICE_KEY'),
'X-Device-Secret: ' . getenv('TRUSTIKS_DEVICE_SECRET'),
]]);
$body = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($httpCode !== 200) break;
$status = (json_decode($body ?: '{}', true)['status'] ?? '');
$lastStatus = $status;
if ($status === 'in_progress') { usleep(1500000); continue; }
if ($status === 'rejected') {
http_response_code(403);
echo json_encode(['approved' => false, 'status' => 'rejected']);
exit;
}
if ($status === 'approved') {
// Atomically bind the token to this order; false means it was reused.
$saved = use_session_token_once($sessionToken, $orderId);
http_response_code($saved ? 200 : 409);
echo json_encode(['approved' => $saved, 'status' => $saved ? 'approved' : 'already_used']);
exit;
}
break;
}
http_response_code($lastStatus === 'in_progress' ? 202 : 502);
echo json_encode(['approved' => false, 'status' => $lastStatus]);TRUSTIKS deliberately exposes only three verification statuses. Keep the restricted action blocked unless your server receives approved.
approvedProceed only after a fresh server confirmation. Record the sessionToken as used so it cannot approve another order.
rejectedDo not continue. This covers refusal, customer cancellation, timeout, and internal error. If allowed, start a new verification.
in_progressDo not continue yet. Ask TRUSTIKS again from your server after a short delay and stop retrying after your own timeout.
The browser callback is for interface updates. The server-to-server response makes the access decision.
A ready-made Shopify integration is on the roadmap for mid-October 2026.
A ready-made WooCommerce and WordPress integration is planned for late November 2026.
TRUSTIKS
Tell us about your vending fleet, payment terminal, website or store. Start an account or email integration[at]trustiks.com to discuss the right integration.