TRUSTIKS API

Age verification through one API.

A focused API for software teams that need a simple approved or rejected result without owning the verification pipeline.

Built for real operations

01

Credentials kept on your backend

Keep permanent credentials out of browser code and rotate secrets when required.

02

Session-scoped browser tokens

Use a short-lived token for each browser verification and confirm the final result from your own server.

03

Versioned, auditable policy decisions

Trace every decision to the exact policy and model versions used.

EXPLICIT BY DEFAULT

One request.
One trusted outcome.

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

Install once. Verify at the right moment.

Add the TRUSTIKS widget to your storefront, open it before a restricted action, and confirm every result from your own server.

  1. 1

    Add the script once

    Paste it immediately before </body> in the site-wide theme or layout.

  2. 2

    Choose when it opens

    Call Trustiks.verify before an age-restricted cart action, checkout or protected access.

  3. 3

    Confirm on your server

    Send sessionToken to your backend. Continue only after TRUSTIKS returns approved.

  4. 4

    Test both sides

    Test the browser journey and server confirmation before enabling it for customers.

Three identifiers, three trust levels

Only the public widget ID belongs in browser code. Keep permanent credentials on your server.

IdentifierIssued fromStore itPublic?
pub_… · Public widget IDWhen a website device is createdHTML and Trustiks.verifyYes
dk_… + ds_… · Device credentialsWidget → 02 · API Credentials; secret shown onceServer environment variablesNo
sessionToken · Verification UUIDCreated per check; result.sessionTokenBind to your order/session and record as usedYes, but never trust it

Implement the trusted server path

The browser result is for UX only. Your backend must confirm the decision with TRUSTIKS.

  1. 1
    Get credentials

    Copy the device key and one-time secret from Widget → 02 · API Credentials.

  2. 2
    Set server env

    Store TRUSTIKS_DEVICE_KEY and TRUSTIKS_DEVICE_SECRET only on your backend.

  3. 3
    Add your endpoint

    Receive sessionToken and your order/session id, then call TRUSTIKS with both headers.

  4. 4
    Poll in_progress

    Repeat after 1–2 seconds. A check unfinished after 120 seconds is reported as rejected.

  5. 5
    Prevent replay

    Bind an accepted token to one order/session in durable storage and never accept it twice.

  6. 6
    Rotate the secret

    Prepare the deploy, regenerate, then update env immediately. The old secret stops working at once.

RequestGET {API_BASE}/v1/verifications/{sessionToken}X-Device-Key: dk_…
X-Device-Secret: ds_…
Exact response{"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.

Copy-ready examples

Replace the public widget id in HTML. Keep the device key and secret only in server environment variables.

HTML + JavaScript
<!-- 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>
Node.js (fetch)
// 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 (cURL)
<?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]);

Handle every response

TRUSTIKS deliberately exposes only three verification statuses. Keep the restricted action blocked unless your server receives approved.

approved

Proceed only after a fresh server confirmation. Record the sessionToken as used so it cannot approve another order.

rejected

Do not continue. This covers refusal, customer cancellation, timeout, and internal error. If allowed, start a new verification.

in_progress

Do not continue yet. Ask TRUSTIKS again from your server after a short delay and stop retrying after your own timeout.

Trusted verification flow

The browser callback is for interface updates. The server-to-server response makes the access decision.

Customer browserStarts verification
TRUSTIKS widgetReturns sessionToken
Your serverKeeps credentials secret
TRUSTIKS APIReturns trusted status
Your decisionAllow only approved

Integration roadmap

01
Shopify

A ready-made Shopify integration is on the roadmap for mid-October 2026.

02
WooCommerce / WordPress

A ready-made WooCommerce and WordPress integration is planned for late November 2026.

TRUSTIKS

Ready to make age checks easier?

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.

Get started