Guide

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 for contact won't pass as signup.
  • Log result.ref for support requests.
  • See Errors for error descriptions.