Skip to content

Server Integration

PoW verification process

The verification process relies on computational work (SHA-hashing) and involves the following steps:

  1. Retrieve the challenge on the client side.
  2. Determine the solution to the challenge.
  3. Submit both the solution and user data to the server.
  4. Validate the solution and signature on the server.

The client-side aspect is managed by the ALTCHA widget, while the implementation of server-side verification is required. The server’s submission handler (e.g., POST /form_submit) needs to authenticate the ALTCHA payload upon form submission.

Refer to the proof-of-work documentation to read more about the mechanism behind ALTCHA.


Generating a new challenge involves three passes of SHA computation (once for the challenge and twice for the HMAC signature). Similarly, verifying a solution also requires three passes.

The range of the random number adjusts the difficulty of the computational task required from the client.

For more details, refer to adjusting complexity.


The server must generate a new challenge for each ALTCHA verification. There are two methods to provide the challenge to the widget:

  1. Using challengeurl: If configured, the widget fetches the challenge from the specified URL. The server must return a new challenge as described below.

  2. Using challengejson: Directly provide the challenge as a JSON-encoded string. This method is useful for server-rendered pages.

Read more about the proof-of-work mechanism.

Creating a challenge

Here is a pseudo-code example for creating a challenge on the server:

// Generate a random salt (recommended length: at least 10 characters)
salt = random_string();
// Generate a random secret number (positive integer)
// Range depends on the chosen complexity (refer to documentation)
// Ensure NOT to expose this number to the client
secret_number = random_int();
// Compute the SHA256 hash of the concatenated salt + secret_number (result encoded as HEX string)
challenge = sha2_hex(concat(salt, secret_number));
// Create a server signature using an HMAC key (result encoded as HEX string)
signature = hmac_sha2_hex(challenge, hmac_key);
// Return JSON-encoded data
response = {
algorithm = 'SHA-256',

Salt Parameters

Starting from Widget version v0.4 (May 2024), it is recommended to include the expires flag and additional parameters in the salt as URL-encoded query strings. This allows you to pass custom data, which will be part of the signature and verifiable on the server.

The widget automatically detects the expires parameter when provided in the salt as a Unix timestamp in seconds:

salt = '<random_salt>?expires=1715526540'

To ensure compatibility with future versions of the widget, it’s recommended to prefix any custom parameters with an underscore _.

The altcha-lib library already supports salt parameters and automatically verifies the expiration of the challenge from version v0.3.

Form submission

Upon submission within a <form>, the server will receive data encoded as application/x-www-form-urlencoded or multipart/form-data, depending on the form structure. The ALTCHA payload will be embedded as the altcha field (customizable via the name attribute in the widget).

Use the value of the altcha field as the payload in the examples below. The payload is a Base64-JSON-encoded string.

Solution validation

Here is a pseudo-code example for validating a solution on the server:

// The payload is a BASE64-JSON-encoded string
// The decoded data is an object containing { algorithm, challenge, number, salt, signature }
data = json_decode(base64_decode(payload));
// Validate algorithm
alg_ok = equals(data.algorithm, 'SHA-256');
// Validate challenge
challenge_ok = equals(data.challenge, sha2_hex(concat(data.salt, data.number)))
// Validate signature
signature_ok = equals(data.signature, hmac_sha2_hex(data.challenge, hmac_key))
// Consider the request verified if all checks are true
verified = alg_ok and challenge_ok and signature_ok


The official JS library works with Node.js, Bun and Deno.

import { createChallenge, verifySolution } from 'altcha-lib';
const hmacKey = '$ecret.key'; // Change the secret HMAC key
// Create a new challenge and send it to the client:
const challenge = await createChallenge({
// When submitted, verify the payload:
const ok = await verifySolution(payload, hmacKey);

For more examples and integrations, refer to the Community Integrations page.

Security recommendations

  • Replay attacks

    To prevent the vulnerability of “replay attacks,” where a client resubmits the same solution multiple times, the server should implement measures that invalidate previously solved challenges.

    The server should maintain a registry of solved challenges and reject any submissions that attempt to reuse a challenge that has already been successfully solved.

  • Challenge expiration

    Limiting the validity period of challenges can bolster security measures, ensuring challenges cannot be exploited indefinitely. Implementing challenge expiration involves setting a time limit within which a challenge must be solved and submitted.

    One effective method to achieve challenge expiration involves incorporating a server timestamp into the challenge’s salt during its generation.

    Starting from version 0.4 of the widget, you can utilize salt parameters and include the ?expires=<unix_ts> parameter in the salt. The widget will automatically detect the expires parameter. Your server should then verify the expiration during the verification process.