Node.js / Express
Verify the token in your Express route before creating anything.
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', {
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>Express route
// server.mjs (Node 18+)
import express from 'express';
const app = express();
app.use(express.json());
// If you run behind a proxy you control, tell Express so req.ip is the visitor:
// app.set('trust proxy', 'loopback');
async function verify({ token, remoteIp, action, account }) {
const response = await fetch('https://nolgard.com/v1/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ secret: process.env.NOLGARD_SECRET, token, remoteIp, action, account }),
signal: AbortSignal.timeout(3000),
});
if (response.status >= 500) throw new Error('nolgard unavailable');
return response.json();
}
app.post('/signup', async (req, res) => {
const { token, email } = req.body;
let result;
try {
result = await verify({ token, remoteIp: req.ip, action: 'signup', account: { email } });
} catch {
result = { success: true, decision: 'allow' }; // timeout or 5xx: fail open
}
if (!result.success) {
if (result.error === 'invalid-secret') {
return res.status(503).json({ error: 'try-again' }); // config error: fail closed
}
if (result.error !== 'over-quota') return res.status(403).json({ error: 'rejected' });
result = { success: true, decision: 'allow' }; // over quota: fail open
}
switch (result.decision) {
case 'allow':
await createAccount(email);
return res.json({ ok: true });
case 'limit':
await createAccount(email, { trialDays: 3 });
return res.json({ ok: true });
case 'shadow':
return res.json({ ok: true }); // same response, nothing created
case 'challenge':
return res.status(403).json({ error: 'confirm-email' });
default:
return res.status(403).json({ error: 'rejected' });
}
});
app.listen(3000);Use Node 18 or later for the global fetch. Supply your own createAccount function. For a runnable example, see examples/verify-node.mjs in the repository.
Notes
- Pass the expected
action. A token made forcontactwon't pass assignup. - Log
result.reffor support requests. - See Errors for error descriptions.