Server verification
Reference for POST /v1/siteverify: request, response, every reason with what to do, HTTP statuses, retries and failure policy.
On this page
Request #
POST /v1/siteverify HTTP/1.1
Host: blockalt.click
Authorization: Bearer ba_sk_…
Content-Type: application/json
{
"token": "<the token from the browser>",
"hostname": "example.com",
"action": "signup",
"remoteip": "203.0.113.7",
"idempotencyKey": "signup-7d1f0c2a"
}| Field | Required | Rules |
|---|---|---|
token | Yes | String up to 4,096 characters. Send it as received, including an empty string: an unpaid or paused site answers success: true for it |
hostname | Yes | The hostname of the page that got the token: lower case, letters, digits, dots and hyphens, no scheme or port. Fix it on the server |
action | Yes | The action you are about to perform, as configured for the site: 1–64 of A–Z a–z 0–9 _ -. Fix it per route on the server |
remoteip | No | The visitor's IP address as your server sees it. Enables the wrong_client check |
idempotencyKey | No | 1–64 of A–Z a–z 0–9 _ -. Makes a retry of the same call safe |
The body is JSON of at most 8 KiB. Unknown fields are refused with invalid_request, so typos do not go unnoticed. The secret key goes only in the Authorization header.
Response #
{ "success": true, "reason": "accepted", "id": "5b0b6f0e-5d0c-4bd2-9f7e-0d3c2a1e9b77" }| Field | Meaning |
|---|---|
success | true only for accepted, unpaid and paused. Perform the action only when it is true |
reason | Why; see reasons below. New reasons may be added: treat an unknown reason with success: false as a refusal |
id | A unique id for this decision. Log it, and quote it when you contact support |
An accepted token also returns its action, hostname and issuedAt (ISO 8601), and answers for test keys carry test: true. More fields may appear; ignore those you do not use.
Reasons #
success: true — perform the action
accepted- The token is valid, fresh and unused for this site, hostname and action. It is now consumed.
- Perform the action.
unpaid- The workspace is priced, has used its free allowance for the month and has no credit left, so the action was not checked. Not billed.
- Perform the action. Top up to restore protection.
paused- Protection is paused for the site, so the action was not checked. Not billed.
- Perform the action. Resume protection in the console when ready.
success: false — refuse the action
invalid_token- The token is missing, malformed, forged or signed by an unknown key.
- Refuse. Check that your form or request actually carries the token field.
expired_token- The token is older than 120 seconds.
- Refuse and ask for a new attempt. Request tokens at submit time, not at page load.
already_used- The token was already accepted once: a replay or a double submission.
- Refuse. Use a new token per attempt; use idempotencyKey for safe retries.
wrong_site- The token belongs to another site.
- Refuse. Check that the secret key and the page's site key belong to the same site.
wrong_hostname- The token was issued on another hostname, or the hostname is no longer an origin of the site.
- Refuse. Send the page's hostname, in lower case, without port.
wrong_action- The token was issued for another action, or the action is no longer configured.
- Refuse. Use the same action name in the browser and on the server.
wrong_client- You sent remoteip and the token was earned from a different network (IPv4 address or IPv6 /64).
- Refuse. If legitimate users hit this, stop sending remoteip for that action.
stale_config- The site's published configuration changed after the token was issued.
- Refuse and let the visitor retry; it happens briefly after publishing or rolling back.
rule_blocked- Your rules block this action, or block all issuance (lockdown).
- Refuse. Expected while the rule is active.
suspended- BlockAlt support suspended the site or its workspace for abuse. Every verification is refused, whatever the token, and nothing is charged.
- Refuse. The console shows the note from support; reply to support's email to resolve it.
HTTP errors — nothing was verified
invalid_secret- HTTP 401: the secret key is wrong or was revoked.
- Fix the key on your server. Every action is refused until you do.
invalid_request- HTTP 400, 413 or 415: the request is malformed, too large or not sent as JSON.
- Fix the request; see the field rules below.
rate_limited- HTTP 429: too many verification requests. Tokens BlockAlt did not issue for the site (forged, malformed, expired, replayed, and empty ones unless the site is paused or unpaid) have a small budget of their own, so a flood of them is refused without touching your site's budget.
- Refuse or queue; retry after the Retry-After delay if present.
During issuance in the browser, the script can also be refused with origin_denied, domain_unverified, action_denied, site_disabled (unknown site key), suspended (configuration and tokens of a suspended site), challenge_required, invalid_proof, challenge_expired or challenge_used. Those never reach your server: the page sees them as browser errors carrying the reason.
HTTP statuses #
| HTTP status | Body | Meaning |
|---|---|---|
| 200 | {success, reason, id} | A decision: success may be true or false |
| 400 | reason: "invalid_request" | Missing or malformed fields, unknown fields, invalid JSON |
| 401 | reason: "invalid_secret" | Missing, wrong or revoked secret key |
| 413 | reason: "invalid_request" | Body larger than 8 KiB |
| 415 | reason: "invalid_request" | Body not sent as application/json |
| 429 | reason: "rate_limited" | Too many requests; may carry Retry-After |
| 503 | reason: "service_unavailable" or "policy_unavailable" | Temporary failure |
| Other, timeout, network error | — | Treat as service_unavailable |
Retries and idempotency #
A token works once. If your call times out after BlockAlt consumed the token, a plain retry returns already_used. To retry safely, send an idempotencyKey: when a call with that key accepted the token, repeating the same key with the same token, within the token's lifetime, returns that accepted decision again (with the same id) instead of already_used, and it is billed once. Refusals are not stored: a retry of a refused token is checked again from scratch.
- Generate the key on your server for each attempt, for example from your request id.
- Retry at most once, on a timeout, a network error or a 5xx, with the same key and token.
- Never retry a decision:
success: falsewith a 200 is final for that token.
remoteip and wrong_client #
Each token remembers the network it was earned from (an IPv4 address, or an IPv6 /64), as a keyed hash. When you send remoteip, BlockAlt compares the two and refuses with wrong_client when they differ, so a token solved on one machine cannot be spent from another.
Send it only when your server sees the real visitor address (behind a proxy, take it from the header your proxy sets, never from arbitrary X-Forwarded-For). Legitimate visitors can change network between getting the token and submitting: mobile handovers, carrier NAT, VPNs, or a browser that reaches BlockAlt over IPv6 and your site over IPv4. Watch the decision log for wrong_client before you rely on it for an action.
Timeouts and failure policy #
- Use a short timeout, about 3 seconds. Do not follow redirects.
- Fail closed by default: on a timeout, network error or 5xx, refuse the action and ask the visitor to try again. The official SDK does this.
- For low-risk actions you may fail open (accept without a decision), but log it and alert on it, so an outage is not mistaken for quiet traffic.
- Verify on every attempt. Never cache a decision or reuse a token across requests.
Security checklist #
- Keep the secret key out of the browser, logs and source control; rotate it from the console if it leaks.
- Fix
hostnameandactionon the server. - Accept the action only on
success === true, never on HTTP 200 alone. - Verify before any side effect: before creating the account, sending the email or charging the card.
Something unclear or missing on this page? Write to support@blockalt.click.