PHP
Plain PHP with the curl extension, without a framework or SDK.
Browser
<script src="https://nolgard.com/v1/client.js" data-site-key="SITE_KEY" defer></script>
<script>
document.querySelector('#signup').addEventListener('submit', async (event) => {
event.preventDefault();
await Nolgard.ready;
const token = await Nolgard.execute({ action: 'signup' });
const form = event.target;
const response = await fetch('/signup.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, email: form.elements.email.value })
});
// Show your own success or error message here.
});
</script>Server
<?php
// signup.php
function nolgard_verify(string $token, string $action, array $account = []): ?array
{
$payload = ['secret' => getenv('NOLGARD_SECRET'), 'token' => $token, 'action' => $action];
if (!empty($_SERVER['REMOTE_ADDR'])) {
$payload['remoteIp'] = $_SERVER['REMOTE_ADDR'];
}
if ($account) {
$payload['account'] = $account;
}
$ch = curl_init('https://nolgard.com/v1/verify');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_TIMEOUT => 3,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($body === false || $status >= 500) {
return null; // timeout, network or 5xx
}
$result = json_decode($body, true);
return is_array($result) ? $result : null;
}
$input = json_decode(file_get_contents('php://input'), true) ?? [];
$email = (string) ($input['email'] ?? '');
$result = nolgard_verify((string) ($input['token'] ?? ''), 'signup', ['email' => $email]);
header('Content-Type: application/json');
if (($result['error'] ?? '') === 'invalid-secret') {
http_response_code(503); // config error: fail closed
exit(json_encode(['error' => 'try-again']));
}
if ($result === null || ($result['error'] ?? '') === 'over-quota') {
$result = ['success' => true, 'decision' => 'allow']; // timeout, 5xx or over quota: fail open
}
if (empty($result['success'])) {
http_response_code(403);
exit(json_encode(['error' => 'rejected']));
}
switch ($result['decision']) {
case 'allow':
create_account($email);
break;
case 'limit':
create_account($email, ['trial_days' => 3]);
break;
case 'shadow':
break; // same response, nothing created
case 'challenge':
http_response_code(403);
exit(json_encode(['error' => 'confirm-email']));
default:
http_response_code(403);
exit(json_encode(['error' => 'rejected']));
}
echo json_encode(['ok' => true]);Supply your own create_account function. Behind a proxy, REMOTE_ADDR contains the proxy's address. Use the address reported by your proxy only if you control it. On WordPress, use the plugin instead.