Server snippets
Copy-paste verification helpers for curl, Node.js, Python, PHP, Go, Ruby, Java and C#, each with a timeout and fail-closed handling.
On this page
Every helper sends the token with a fixed hostname and action, uses a 3-second timeout, does not follow redirects, and returns "allowed" only when the answer is success: true. Replace example.com with your hostname and keep the secret in the BLOCKALT_SECRET environment variable.
curl #
curl -sS --max-time 3 https://blockalt.click/v1/siteverify \
-H "Authorization: Bearer $BLOCKALT_SECRET" \
-H "Content-Type: application/json" \
-d '{"token":"<token>","hostname":"example.com","action":"signup"}'Node.js with fetch #
No dependency, Node 18 or newer (also Bun and Deno):
export async function verifyBlockAlt({ token, action, remoteip, idempotencyKey }) {
try {
const response = await fetch("https://blockalt.click/v1/siteverify", {
method: "POST",
redirect: "error",
headers: {
Authorization: `Bearer ${process.env.BLOCKALT_SECRET}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
token: typeof token === "string" ? token : "",
hostname: "example.com", // fixed per deployment
action, // fixed per route
...(remoteip ? { remoteip } : {}),
...(idempotencyKey ? { idempotencyKey } : {}),
}),
signal: AbortSignal.timeout(3000),
});
const result = await response.json();
return { success: response.ok && result.success === true, reason: result.reason, id: result.id };
} catch {
return { success: false, reason: "service_unavailable" };
}
}Node.js with @blockalt/server #
ESM with TypeScript types and no runtime dependencies, for Node.js 20+ and Bun. Install version 0.3.0 straight from BlockAlt:
npm install https://blockalt.click/sdk/blockalt-server-0.3.0.tgzThe tarball's npm integrity is sha512-nMWLvnV6txpNPo8agA3SxDOFlAWiFPoYd3jgQqNlVU4kXKCZSzdlhj/lyz+NOfbZw0N4vQQ+nv4Ui8Xj8U2y4Q==; your lockfile records it, so later installs get exactly the same file. All versions are listed at /sdk/.
import { createVerifier } from "@blockalt/server";
// One verifier per protected action: hostname and action are fixed here, never taken from the request.
const verifySignup = createVerifier({
apiUrl: "https://blockalt.click",
secret: process.env.BLOCKALT_SECRET,
hostname: "example.com",
action: "signup",
});
const result = await verifySignup(req.body["blockalt-token"] ?? "", {
remoteip: req.ip, // optional, see remoteip
idempotencyKey: requestId, // optional, makes one retry safe
});
if (!result.success) return res.status(403).send("Please try again.");successistrueonly foraccepted,unpaidandpaused. Unknown reasons from newer API versions are refusals, never exceptions.- HTTP failures have their own reasons:
invalid_secret(401),invalid_request(400, 413, 415 and other 4xx),rate_limited(429, withretryAfterin seconds) andservice_unavailable(5xx, network errors and timeouts, 3 seconds by default). Calls are never retried automatically. - Some refusals are answered locally, marked
local: true, without calling BlockAlt: a token this verifier has already seen accepted (a replay), tokens whose readable claims are expired or bound to another hostname or action, and the test keys' dummy token with a real secret. Tokens are authenticated with a key only BlockAlt holds, so the SDK cannot check their tag: a well-formed forged token goes to siteverify, which checks the tag first and refuses it without spending your site's verification budget, and refusals are never billed. Only legacy version 1 tokens have a signature the SDK checks locally, against the keys published at/v1/keys. siteverify always makes the final decision on every token sent to it.precheck: falseandtokenKeys: falseturn the local checks off. - An empty token (the browser was told the site is
unpaidorpaused) is sent at most once every 15 seconds per hostname and action: its answer, whatever it is, is reused locally for empty tokens (local: true) during that time. For 15 seconds after anunpaidorpausedanswer, other tokens skip the local checks and go to siteverify, which decides each one, so a lockdown or a top-up applies to the next token. - With the test secrets, every call goes to siteverify and nothing is cached.
- The object form
verify({ token, hostname, action })also works with a verifier created without a fixed hostname or action.
Python #
Standard library only:
import json
import os
import urllib.error
import urllib.request
class _NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *args, **kwargs):
return None # a redirect raises HTTPError instead of resending the secret
_opener = urllib.request.build_opener(_NoRedirect)
def verify_blockalt(token: str, action: str, remoteip: str | None = None) -> bool:
payload = {"token": token or "", "hostname": "example.com", "action": action}
if remoteip:
payload["remoteip"] = remoteip
request = urllib.request.Request(
"https://blockalt.click/v1/siteverify",
data=json.dumps(payload).encode(),
headers={
"Authorization": f"Bearer {os.environ['BLOCKALT_SECRET']}",
"Content-Type": "application/json",
},
method="POST",
)
try:
with _opener.open(request, timeout=3) as response:
result = json.load(response)
except (urllib.error.URLError, TimeoutError, ValueError):
return False # 4xx, 5xx, timeouts and bad JSON all refuse
return result.get("success") is TruePHP #
<?php
function blockalt_verify(string $token, string $action, ?string $remoteip = null): bool
{
$payload = ['token' => $token, 'hostname' => 'example.com', 'action' => $action];
if ($remoteip !== null) {
$payload['remoteip'] = $remoteip;
}
$ch = curl_init('https://blockalt.click/v1/siteverify');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLOCKALT_SECRET'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 3,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($body === false || $status !== 200) {
return false;
}
$result = json_decode($body, true);
return is_array($result) && ($result['success'] ?? false) === true;
}
// $ok = blockalt_verify($_POST['blockalt-token'] ?? '', 'signup');Go #
package blockalt
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
type Result struct {
Success bool `json:"success"`
Reason string `json:"reason"`
ID string `json:"id"`
}
var client = &http.Client{
Timeout: 3 * time.Second,
CheckRedirect: func(*http.Request, []*http.Request) error {
return http.ErrUseLastResponse
},
}
// Verify returns an error for every non-decision; allow the action only when
// err == nil && result.Success.
func Verify(ctx context.Context, token, action, remoteIP string) (Result, error) {
payload := map[string]string{"token": token, "hostname": "example.com", "action": action}
if remoteIP != "" {
payload["remoteip"] = remoteIP
}
body, err := json.Marshal(payload)
if err != nil {
return Result{}, err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
"https://blockalt.click/v1/siteverify", bytes.NewReader(body))
if err != nil {
return Result{}, err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("BLOCKALT_SECRET"))
req.Header.Set("Content-Type", "application/json")
res, err := client.Do(req)
if err != nil {
return Result{}, err
}
defer res.Body.Close()
var result Result
if err := json.NewDecoder(io.LimitReader(res.Body, 8192)).Decode(&result); err != nil {
return Result{}, err
}
if res.StatusCode != http.StatusOK {
return result, fmt.Errorf("blockalt: HTTP %d (%s)", res.StatusCode, result.Reason)
}
return result, nil
}Ruby #
require "json"
require "net/http"
def blockalt_verify(token, action, remoteip: nil)
uri = URI("https://blockalt.click/v1/siteverify")
payload = { token: token.to_s, hostname: "example.com", action: action }
payload[:remoteip] = remoteip if remoteip
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true, open_timeout: 2, read_timeout: 3) do |http|
request = Net::HTTP::Post.new(uri, "Content-Type" => "application/json",
"Authorization" => "Bearer #{ENV.fetch("BLOCKALT_SECRET")}")
request.body = JSON.generate(payload)
http.request(request)
end
response.is_a?(Net::HTTPOK) && JSON.parse(response.body)["success"] == true
rescue StandardError
false
endJava #
Java 11 or newer, with Jackson for JSON:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
public final class BlockAlt {
private static final HttpClient HTTP = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(2))
.followRedirects(HttpClient.Redirect.NEVER)
.build();
private static final ObjectMapper JSON = new ObjectMapper();
public static boolean verify(String token, String action, String remoteIp) {
Map<String, String> payload = new HashMap<>();
payload.put("token", token == null ? "" : token);
payload.put("hostname", "example.com");
payload.put("action", action);
if (remoteIp != null) payload.put("remoteip", remoteIp);
try {
HttpRequest request = HttpRequest.newBuilder(URI.create("https://blockalt.click/v1/siteverify"))
.timeout(Duration.ofSeconds(3))
.header("Authorization", "Bearer " + System.getenv("BLOCKALT_SECRET"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(JSON.writeValueAsString(payload)))
.build();
HttpResponse<String> response = HTTP.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) return false;
JsonNode success = JSON.readTree(response.body()).path("success");
return success.isBoolean() && success.booleanValue();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} catch (Exception e) {
return false;
}
}
}C# / .NET #
.NET 6 or newer:
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
public static class BlockAlt
{
private static readonly HttpClient Http = new(new HttpClientHandler { AllowAutoRedirect = false })
{
Timeout = TimeSpan.FromSeconds(3),
};
private sealed record Result(bool Success, string? Reason, string? Id);
public static async Task<bool> VerifyAsync(string? token, string action, string? remoteIp = null,
CancellationToken cancellationToken = default)
{
var payload = new Dictionary<string, string>
{
["token"] = token ?? "",
["hostname"] = "example.com",
["action"] = action,
};
if (remoteIp is not null) payload["remoteip"] = remoteIp;
using var request = new HttpRequestMessage(HttpMethod.Post, "https://blockalt.click/v1/siteverify")
{
Content = JsonContent.Create(payload),
};
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer",
Environment.GetEnvironmentVariable("BLOCKALT_SECRET"));
try
{
using var response = await Http.SendAsync(request, cancellationToken);
if (response.StatusCode != HttpStatusCode.OK) return false;
var result = await response.Content.ReadFromJsonAsync<Result>(
new JsonSerializerOptions(JsonSerializerDefaults.Web), cancellationToken);
return result?.Success == true;
}
catch (Exception e) when (e is HttpRequestException or TaskCanceledException or JsonException)
{
return false;
}
}
}Something unclear or missing on this page? Write to support@blockalt.click.