Server Verification

Always verify tokens on your backend - never trust the client alone. A valid token proves the visitor passed a recent challenge for your site.

Verify via REST API

Send the token from your form handler to the Squeaker API. This checks signature, expiry, and one-time use (replay protection).

POST /v1/verify
Content-Type: application/json

{
  "secret": "sq_secret_xxx",
  "token": "eyJ...",
  "remoteip": "203.0.113.1"
}
{ "success": true }
// or
{ "success": false, "error": "Token already used" }

Common failure reasons

ReasonMeaning
Invalid or expired tokenBad signature, wrong secret, or expired
Token already usedReplay detected
Invalid secret keySQUEAKER_SECRET does not match any site

Node.js

const apiUrl = process.env.SQUEAKER_API_URL ?? 'https://api.squeaker.cc/v1';

app.post('/contact', async (req, res) => {
  const token = req.body['squeaker-token'];
  if (!token) return res.status(400).json({ error: 'Missing token' });

  const verifyRes = await fetch(`${apiUrl}/verify`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      secret: process.env.SQUEAKER_SECRET,
      token,
      remoteip: req.ip,
    }),
  });
  const result = await verifyRes.json();
  if (!result.success) return res.status(403).json({ error: result.error });

  res.json({ ok: true });
});

PHP

$token = $_POST['squeaker-token'] ?? '';
if ($token === '') {
    http_response_code(400);
    exit('Missing token');
}

$apiUrl = getenv('SQUEAKER_API_URL') ?: 'https://api.squeaker.cc/v1';
$payload = json_encode([
    'secret' => getenv('SQUEAKER_SECRET'),
    'token' => $token,
    'remoteip' => $_SERVER['REMOTE_ADDR'] ?? null,
]);
$ctx = stream_context_create([
    'http' => [
        'method' => 'POST',
        'header' => "Content-Type: application/json\\r\\n",
        'content' => $payload,
    ],
]);
$response = file_get_contents("$apiUrl/verify", false, $ctx);
$result = json_decode($response, true);
if (!$result['success']) {
    http_response_code(403);
    exit($result['error'] ?? 'Invalid token');
}

Python

import json
import os
import urllib.request

@app.route('/contact', methods=['POST'])
def contact():
    token = request.form.get('squeaker-token')
    if not token:
        return {'error': 'Missing token'}, 400

    api_url = os.environ.get('SQUEAKER_API_URL', 'https://api.squeaker.cc/v1')
    payload = json.dumps({
        'secret': os.environ['SQUEAKER_SECRET'],
        'token': token,
        'remoteip': request.remote_addr,
    }).encode()
    req = urllib.request.Request(
        f'{api_url}/verify',
        data=payload,
        headers={'Content-Type': 'application/json'},
        method='POST',
    )
    with urllib.request.urlopen(req) as resp:
        result = json.loads(resp.read())
    if not result.get('success'):
        return {'error': result.get('error', 'Invalid token')}, 403

    return {'ok': True}

React / Next.js

Use the <squeaker-widget> web component directly — load squeaker.js once, no npm wrapper needed.

'use client';
import { useEffect } from 'react';

export function SignupForm() {
  useEffect(() => {
    const src = 'https://cdn.squeaker.cc/squeaker.js';
    if (document.querySelector(`script[src="${src}"]`)) return;
    const s = document.createElement('script');
    s.src = src;
    s.defer = true;
    document.head.appendChild(s);
  }, []);

  return (
    <form id="signup" action="/api/signup" method="POST">
      <squeaker-widget
        data-sitekey="sq_live_xxx"
        data-api="https://api.squeaker.cc/v1"
        data-mode="auto"
        data-form="#signup"
        data-theme="light"
      />
      <button type="submit">Sign up</button>
    </form>
  );
}

In TypeScript projects, add squeaker-widget to your JSX intrinsic elements if the compiler complains.

Security checklist